fix(#187): implement server-side SSE error recovery
Server-side improvements (ALL 27/27 TESTS PASSING): - Add streamEventsFrom() method with lastEventId parameter for resuming streams - Include event IDs in SSE messages (id: event-123) for reconnection support - Send retry interval header (retry: 3000ms) to clients - Classify errors as retryable vs non-retryable - Handle transient errors gracefully with retry logic - Support Last-Event-ID header in controller for automatic reconnection Files modified: - apps/api/src/runner-jobs/runner-jobs.service.ts (new streamEventsFrom method) - apps/api/src/runner-jobs/runner-jobs.controller.ts (Last-Event-ID header support) - apps/api/src/runner-jobs/runner-jobs.service.spec.ts (comprehensive error recovery tests) - docs/scratchpads/187-implement-sse-error-recovery.md (implementation notes) This ensures robust real-time updates with automatic recovery from network issues. Client-side React hook will be added in a follow-up PR after fixing Quality Rails lint issues. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Post, Body, Param, Query, UseGuards, Res } from "@nestjs/common";
|
||||
import { Controller, Get, Post, Body, Param, Query, UseGuards, Res, Headers } from "@nestjs/common";
|
||||
import { Response } from "express";
|
||||
import { RunnerJobsService } from "./runner-jobs.service";
|
||||
import { CreateJobDto, QueryJobsDto } from "./dto";
|
||||
@@ -93,12 +93,14 @@ export class RunnerJobsController {
|
||||
* GET /api/runner-jobs/:id/events/stream
|
||||
* Stream job events via Server-Sent Events (SSE)
|
||||
* Requires: Any workspace member
|
||||
* Supports automatic reconnection via Last-Event-ID header
|
||||
*/
|
||||
@Get(":id/events/stream")
|
||||
@RequirePermission(Permission.WORKSPACE_ANY)
|
||||
async streamEvents(
|
||||
@Param("id") id: string,
|
||||
@Workspace() workspaceId: string,
|
||||
@Headers("last-event-id") lastEventId: string | undefined,
|
||||
@Res() res: Response
|
||||
): Promise<void> {
|
||||
// Set SSE headers
|
||||
@@ -108,7 +110,7 @@ export class RunnerJobsController {
|
||||
res.setHeader("X-Accel-Buffering", "no"); // Disable nginx buffering
|
||||
|
||||
try {
|
||||
await this.runnerJobsService.streamEvents(id, workspaceId, res);
|
||||
await this.runnerJobsService.streamEvents(id, workspaceId, res, lastEventId);
|
||||
} catch (error: unknown) {
|
||||
// Write error to stream
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
|
||||
Reference in New Issue
Block a user