Files
suaveplan/openspec/archive/2026-05-20-add-import-sse-progress/proposal.md
2026-05-27 23:29:53 +01:00

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 ImportProgress payload type,
  • the existing import.progress / import.completed / import.failed events,
  • 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