Skip to content

Commit fb654b6

Browse files
committed
feat: implement asynchronous session processing with PENDING status and background execution
1 parent 739fb66 commit fb654b6

3 files changed

Lines changed: 99 additions & 4 deletions

File tree

src/controllers/session-controller.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ const CreateSessionSchema = z.object({
4747
experienceLevel: z.enum(['beginner', 'intermediate', 'advanced']).optional(),
4848
})
4949
.optional(),
50+
/**
51+
* If true, returns immediately with PENDING status and processes in background.
52+
* Client must poll GET /api/sessions/:id to check completion.
53+
* Default: false (synchronous processing for small files)
54+
*/
55+
async: z.boolean().optional().default(false),
5056
});
5157

5258
/**
@@ -133,7 +139,28 @@ export class SessionController {
133139
return res.status(401).json({ message: 'Unauthorized' });
134140
}
135141

136-
// Run workflow and persist result
142+
// Async mode: Create PENDING session and process in background
143+
if (payload.async) {
144+
const pendingSession = await this.sessionRepository.createPending({
145+
sessionId: payload.sessionId,
146+
userId,
147+
uploadUrl: payload.uploadUrl,
148+
userContext: payload.userContext,
149+
});
150+
151+
// Process in background (don't await)
152+
this.sessionService.runSession(payload, userId).catch((error) => {
153+
console.error(`Background session ${payload.sessionId} failed:`, error);
154+
});
155+
156+
return res.status(202).json({
157+
sessionId: pendingSession.sessionId,
158+
status: 'PENDING',
159+
message: 'Session queued for processing. Poll GET /api/sessions/:id to check status.',
160+
});
161+
}
162+
163+
// Sync mode: Run workflow and wait for completion (original behavior)
137164
const result = await this.sessionService.runSession(payload, userId);
138165
return res.status(201).json(result);
139166
} catch (error) {
@@ -258,9 +285,12 @@ export class SessionController {
258285
return res.status(404).json({ message: 'Session not found' });
259286
}
260287

261-
// Parse and return complete EngineState
288+
// Parse EngineState and include database status
262289
const state = JSON.parse(session.payload) as EngineState;
263-
return res.status(200).json(state);
290+
return res.status(200).json({
291+
...state,
292+
status: session.status, // PENDING, PROCESSING, COMPLETED, FAILED
293+
});
264294
} catch (error) {
265295
return next(error);
266296
}

src/repositories/session-repository.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,22 @@ export class SessionRepository {
6868
* @throws Prisma errors if database operation fails
6969
*/
7070
async save(state: EngineState, userId: string) {
71+
const status = state.error ? 'FAILED' : 'COMPLETED';
72+
7173
await prisma.session.upsert({
7274
where: { sessionId: state.sessionId },
7375
create: {
7476
sessionId: state.sessionId,
7577
userId,
7678
uploadUrl: state.uploadUrl,
7779
payload: JSON.stringify(state),
80+
status,
7881
error: state.error,
7982
},
8083
update: {
8184
uploadUrl: state.uploadUrl,
8285
payload: JSON.stringify(state),
86+
status,
8387
error: state.error,
8488
},
8589
});
@@ -111,6 +115,44 @@ export class SessionRepository {
111115
});
112116
}
113117

118+
/**
119+
* Creates a new PENDING session for async processing.
120+
*
121+
* Used when client requests async processing (large files).
122+
* Creates minimal session record with PENDING status that will be
123+
* updated once background processing completes.
124+
*
125+
* **Workflow:**
126+
* 1. Client sends POST /api/sessions with async=true
127+
* 2. Controller calls createPending (returns immediately)
128+
* 3. Background job processes the session
129+
* 4. Background job calls save() to update with results
130+
* 5. Client polls GET /api/sessions/:id to check status
131+
*
132+
* @param data - Session initialization data
133+
* @returns Promise resolving to created Session record
134+
*/
135+
async createPending(data: {
136+
sessionId: string;
137+
userId: string;
138+
uploadUrl: string;
139+
userContext?: Record<string, unknown>;
140+
}): Promise<Session> {
141+
return prisma.session.create({
142+
data: {
143+
sessionId: data.sessionId,
144+
userId: data.userId,
145+
uploadUrl: data.uploadUrl,
146+
payload: JSON.stringify({
147+
sessionId: data.sessionId,
148+
uploadUrl: data.uploadUrl,
149+
userContext: data.userContext,
150+
}),
151+
status: 'PENDING',
152+
},
153+
});
154+
}
155+
114156
/**
115157
* Retrieves a specific session by ID, with user ownership verification.
116158
*
@@ -142,4 +184,20 @@ export class SessionRepository {
142184
},
143185
});
144186
}
187+
188+
/**
189+
* Updates session status to PROCESSING when workflow starts.
190+
*
191+
* Called at the beginning of async job processing to indicate
192+
* that work has begun. This allows clients polling the status
193+
* to see progress beyond just PENDING.
194+
*
195+
* @param sessionId - Session to mark as processing
196+
*/
197+
async markProcessing(sessionId: string): Promise<void> {
198+
await prisma.session.update({
199+
where: { sessionId },
200+
data: { status: 'PROCESSING' },
201+
});
202+
}
145203
}

src/services/session-service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,17 @@ export class SessionService {
104104
* ```
105105
*/
106106
async runSession(input: RunSessionInput, userId: string): Promise<EngineState> {
107+
// Mark as PROCESSING if session exists (async workflow)
108+
try {
109+
await this.repository.markProcessing(input.sessionId);
110+
} catch {
111+
// Ignore error if session doesn't exist yet (sync workflow will create it)
112+
}
113+
107114
// Execute complete workflow (metadata → analysis → feedback)
108115
const state = await runAuralyzeSession(input, this.deps);
109116

110-
// Persist result (success or error) to database
117+
// Persist result (success or error) to database with final status
111118
await this.repository.save(state, userId);
112119

113120
return state;

0 commit comments

Comments
 (0)