64 lines
4.8 KiB
Markdown
64 lines
4.8 KiB
Markdown
# 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)
|