3.6 KiB
Change: add-import-sse-progress
Why
@suaveplan/api-plugin-import shipped (archive 2026-05-19-add-api-plugin-import) with the
## Excluded from this migration clause explicitly deferring SSE progress push:
Foreign-key resolution, parallel batch workers, resumable checkpoints, S3/URL source adapters, SSE progress push
This change lifts that single deferral. Consumers can now subscribe to a job's progress
via GET /imports/:id/progress and receive text/event-stream frames driven directly off
the existing ImportService.bus events — no polling. The other deferred items
(foreign-key resolution, parallel workers, checkpoints, S3 sources) remain deferred.
The endpoint reuses:
- the canonical
ImportProgresspayload type, - the existing
import.progress/import.completed/import.failedevents, - the existing
IMPORT.*error registry, - the owner-scoping invariants of
ImportService.getJob(jobId, ownerId).
The endpoint introduces three new OTel counters scoped to the new feature module:
import.sse.connected, import.sse.events_sent{kind}, import.sse.disconnected{reason}.
Affected requirements
ADDED requirement: SSE progress push (one new requirement; no MODIFIED / REMOVED entries).
Source mapping
| Spec scenario | Implementation | Test |
|---|---|---|
GET /imports/:id/progress opens an event-stream |
src/routes/progress-sse/progress-sse.ts |
src/routes/progress-sse/progress-sse.test.ts::"opens a text/event-stream and emits an initial progress frame" |
Each import.progress bus emit produces an event: progress frame |
src/routes/progress-sse/progress-sse.ts |
src/routes/progress-sse/progress-sse.test.ts::"emits a progress frame for every bus progress event" |
Terminal import.completed emits event: complete then closes |
src/routes/progress-sse/progress-sse.ts |
src/routes/progress-sse/progress-sse.test.ts::"emits a complete frame and closes on import.completed" |
Terminal import.failed emits event: failed then closes |
src/routes/progress-sse/progress-sse.ts |
src/routes/progress-sse/progress-sse.test.ts::"emits a failed frame and closes on import.failed" |
| Foreign-owner request returns 404 problem+json | src/routes/progress-sse/progress-sse.ts |
src/routes/progress-sse/progress-sse.test.ts::"returns 404 problem+json for a foreign-owner job" |
| Anonymous request returns 401 problem+json | route is mounted under requireAuth() in build-routes.ts |
src/routes/progress-sse/progress-sse.test.ts::"returns 401 problem+json when the caller is anonymous" |
| Client disconnect cleans up the bus listener | AbortSignal wired into the ReadableStream.cancel path |
src/routes/progress-sse/progress-sse.test.ts::"detaches the listener and increments disconnect counter on client abort" |
| OTel counters increment on connect / event / disconnect | src/routes/progress-sse/progress-sse.ts (new instruments wired through getImportInstruments()) |
src/routes/progress-sse/progress-sse.test.ts::"increments import.sse counters on connect, send, and disconnect" |
Impact
Affected dependents
Run bunx turbo ls --filter=...@suaveplan/api-plugin-import to enumerate.
@suaveplan/api— [ ] tests run / [ ] docs reviewed / [x] no action needed (reason: additive route under an existing prefix; no signature changes to the consumed surface)
Foundation package? No. This is a feature-additive change on a server-tier plugin.
Out of scope (still deferred from the original Excluded list)
- Foreign-key resolution
- Parallel batch workers
- Resumable checkpoints
- S3 / URL source adapters
- Streaming input parsing
- Benchmarks