Files
suaveplan/openspec/archive/2026-05-20-add-export-download-handler/tasks.md
2026-05-27 23:29:53 +01:00

4.8 KiB

Tasks — add-export-download-handler

1. Error codes

  • Add JOB_NOT_READY to EXPORT_CODES registry in src/error-codes/error-codes.ts
  • Update the JSDoc registry summary to describe JOB_NOT_READY semantics (raised by download handler when job is not in completed state)
  • Update src/error-codes/error-codes.test.ts to assert JOB_NOT_READY narrows through the public type union

2. Problem-details renderer

  • Author src/routes/problem-json/problem-json.ts mirroring the api-plugin-import sibling (PROBLEM_JSON_CONTENT_TYPE, ProblemDetails, renderProblem, respondProblemJson)
  • Map SuaveplanError → status via context.statusCode (404 for JOB_NOT_FOUND, 409 for JOB_NOT_READY, 400 for everything else under 500)
  • Author src/routes/problem-json/problem-json.test.ts covering: SuaveplanError mapping, plain Error fallback, unknown throwable fallback, missing statusCode defaults to 500
  • Author src/routes/problem-json/problem-json.md (≥200 words, Purpose / Features / Usage Basic / Usage Advanced / API Reference / Implementation Notes)
  • Add ./routes/problem-json/problem-json subpath export to package.json
  • Re-export renderProblem, respondProblemJson, PROBLEM_JSON_CONTENT_TYPE, ProblemDetails from src/index.ts

3. OTel instruments

  • Extend ExportInstruments in src/otel/instruments.ts with downloadServed: Counter and downloadFailed: Counter
  • Register the two counters via meter.createCounter("export.download.served", …) and meter.createCounter("export.download.failed", …)
  • Update src/otel/instruments.md with the two new instrument descriptions
  • Extend src/otel/instruments.test.ts with: "emits download.served on a successful download" and "emits download.failed{reason} on a not-ready download"

4. Download handler

  • Create src/routes/download/download.ts exporting runDownloadExport(deps, jobId) returning a discriminated DownloadOutcome union ({ kind: "ok"; body; contentType; filename } / { kind: "not-found" } / { kind: "not-ready"; status } / { kind: "provider-not-found"; resource } / { kind: "failed"; error })
  • Implementation flow: jobStore.get(id) → null ⇒ not-found raising EXPORT.JOB_NOT_FOUND; status !== "completed" ⇒ not-ready raising EXPORT.JOB_NOT_READY; providers.get(job.resource) → null ⇒ provider-not-found raising EXPORT.PROVIDER_NOT_FOUND; on any exception during fetch/render emit EXPORT.PROVIDER_FETCH_FAILED
  • Author src/routes/download/download.md (≥200 words)
  • Author src/routes/download/download.test.ts covering every branch with real CSV / JSON / XLSX renders

5. Route registration

  • Extend ExportRoutesDeps in src/routes/exports/exports.ts with optional jobStore?: ExportJobStore
  • Mount GET /{id}/download after the existing GET /{id} route; build the inline Map<string, ExportJob> adapter when jobStore is absent
  • Wire the download outcome through respondProblemJson for every non-ok branch
  • OpenAPI route definition: params: z.object({ id: z.string() }), responses: 200 (binary), 404, 409, 502, all carrying application/problem+json schema for the failure shapes
  • Update src/routes/exports/exports.md with a paragraph on the new endpoint
  • Extend src/routes/exports/exports.test.ts with an end-to-end happy-path download + a 404 + a 409 + a 502 test using a real provider + real MemoryExportJobStore

6. Plugin wiring

  • In src/plugin/plugin.ts runOnReady, look up ctx.getService("exportJobStore") and thread it into createExportRoutes({ …, jobStore }). Absent slot ⇒ omit; existing behaviour preserved
  • Extend src/plugin/plugin.test.ts with "download route uses exportJobStore service when wired" using a MemoryExportJobStore

7. Spec conformance

  • src/api-plugin-export.spec-conformance.test.ts already pins every EXPORT_* code; verify JOB_NOT_READY is thrown by the download handler so the pin holds
  • Run bun scripts/openspec-conformance.ts @suaveplan/api-plugin-export — expect zero errors

8. Pre-archive gates

  • bunx biome check src/ clean
  • bunx turbo lint typecheck test --filter=@suaveplan/api-plugin-export green
  • bunx turbo test:coverage --filter=@suaveplan/api-plugin-export ≥100/100/100/100 on touched modules
  • bunx turbo build --filter=@suaveplan/api-plugin-export green
  • bun run docs:check (package-scoped, via package.json script) clean

9. Archive

  • Fold specs/api-plugin-export/spec.md delta into openspec/specs/api-plugin-export/spec.md
  • Move openspec/changes/add-export-download-handler → openspec/archive/2026-05-20-add-export-download-handler
  • Single commit feat(api-plugin-export): add GET /:id/download handler (was §4-deferred) and push (pre-pull-rebase autostash)