78 lines
7.7 KiB
Markdown
78 lines
7.7 KiB
Markdown
# Add @suaveplan/api-plugin-export
|
||
|
||
**Package**: `@suaveplan/api-plugin-export`
|
||
**Status**: PROPOSED
|
||
**Type**: new-package (migration from `../suaveplan/packages/server/api-plugin-export`)
|
||
|
||
- **Design**: ./design.md (n/a — straight upstream parity; rationale documented inline below)
|
||
- **Spec delta**: ./specs/api-plugin-export/spec.md
|
||
|
||
## Why
|
||
|
||
Genesis needs the upstream `api-plugin-export` capability so downstream consumers (admin dashboards, analytics surfaces, GDPR subject-access endpoints) can export any registered resource to CSV, JSON, or XLSX through a uniform OpenAPI route group. Upstream `1.0.5` ships three exporters + four routes + an in-process job store + a Zod-validated config schema. Migrating it at functional parity unblocks the api-plugin-{user, audit, ...} consumers that already type against `DataProvider`, `ExportJob`, and the `export:*` event surface.
|
||
|
||
## What changes
|
||
|
||
- ADDED: `createExportPlugin(options?)` — typed `PluginDescriptor<ExportPluginConfig>` factory, registers `exportProviders` and `exportJobs` services in `ServiceRegistry`, mounts `createExportRoutes(...)` onto `ctx.getService("app")` at the configured `basePath`, emits `export:initialized` / `export:ready` / `export:started` / `export:completed` on the bus.
|
||
- ADDED: `ExportPluginConfigSchema` + `ExportPluginConfig` — Zod `{ basePath, maxRows, maxFileSizeBytes, defaultFormat }` with defaults.
|
||
- ADDED: `registerDataProvider(providers, provider)` — typed registration helper.
|
||
- ADDED: `CsvExporter`, `JsonExporter`, `XlsxExporter` — `Exporter` implementations with helper functions (`escapeCsvField`, `formatCellValue`, `convertToCsv`, `projectRow`, `convertToJson`, `escapeXml`, `columnIndexToLetter`, `formatXlsxCell`, `buildSpreadsheetXml`).
|
||
- ADDED: `createExportRoutes(deps)` — `OpenAPIHono` with `GET /`, `POST /`, `GET /:id`, `DELETE /:id`.
|
||
- ADDED: `runExport`, `getExporter`, `inferColumns`, `DEFAULT_EXPORT_CONFIG`, `ExportServiceConfig` — service-layer surface.
|
||
- ADDED: Zod schemas `ExportFormatSchema`, `ExportStatusSchema`, `ColumnDefinitionSchema`, `CreateExportJobSchema`, `ExportJobSchema`, `ExportJobListQuerySchema`.
|
||
- ADDED: Types `ExportFormat`, `ExportStatus`, `ColumnDefinition`, `CreateExportJob`, `ExportJob`, `ExportJobListQuery`, `ExportOptions`, `ExportResult`, `Exporter`, `DataProvider`, `ExportEventMap`.
|
||
- ADDED: `EXPORT_CODES` + `ExportError` — 5 stable codes (`UNSUPPORTED_FORMAT`, `ROW_LIMIT_EXCEEDED`, `SIZE_LIMIT_EXCEEDED`, `PROVIDER_NOT_FOUND`, `PROVIDER_FETCH_FAILED`) extending `SuaveplanError`.
|
||
- ADDED: `getExportInstruments()` / `resetExportInstruments()` — OTel `exports_total{format,outcome}`, `export_rows_total{format}`, `export_duration_ms` histogram.
|
||
- ADDED: `ServiceRegistry` module augmentation for `exportProviders` and `exportJobs` slots.
|
||
|
||
## Out of scope
|
||
|
||
- SurrealDB-backed persistence, `GET /:id/download` streaming endpoint, OOXML-binary `.xlsx`, JWT/permissions/rate-limit/idempotency/audit middleware, and `surql/` migrations — the upstream `1.0.5` `src/` does not implement these. Enterprise audit doc (`../suaveplan/enterprise/api-plugin-export.md` §§3–4) lists them as future work for the upstream package itself; landing them here would diverge from the parity goal and pull in `@suaveplan/api-plugin-database` / `@suaveplan/api-plugin-audit` / `@suaveplan/api-plugin-rate-limit` which are themselves pending migration.
|
||
- Replacing upstream's plain `Error("…")` throws with a typed hierarchy — done in this change as a genesis-quality uplift (§7 Required enterprise-quality improvements item: "Error hierarchy"). The class names and codes shipped here mirror the upstream audit's call-out and become the canonical contract.
|
||
|
||
## Source mapping
|
||
|
||
This change derives from `../suaveplan/enterprise/api-plugin-export.md` and the upstream `src/` tree.
|
||
|
||
| Requirement | Source lines | Notes |
|
||
|---|---|---|
|
||
| Plugin factory + lifecycle | L17-L22 / `src/index.ts` L38-L104 | rewritten against the genesis `@suaveplan/api-plugin` descriptor shape (`configSchema: ZodType` + `defaults: Partial<T>`) |
|
||
| Zod config schema (basePath/maxRows/maxFileSizeBytes/defaultFormat) | L17-L18 / `src/index.ts` L21-L30 | verbatim |
|
||
| Three exporter classes (CSV / JSON / XLSX-XML) | L17 / `src/exporters/{csv,json,xlsx}` | verbatim |
|
||
| `runExport`, `getExporter`, `inferColumns`, `DEFAULT_EXPORT_CONFIG` | L24 / `src/services/export/export.service.ts` | verbatim |
|
||
| `OpenAPIHono` route group at `/exports` with GET/POST/GET/:id/DELETE/:id | L25 / `src/routes/exports/exports.ts` | verbatim |
|
||
| `DataProvider` registry as `Map<string, DataProvider>` | L23 / `src/types.ts` L127-L132 | verbatim |
|
||
| Job store as `Map<string, ExportJob>` (in-process) | L13 / `src/index.ts` L42, L66 | verbatim (memory-only is upstream's documented state; persistence is enterprise-future) |
|
||
| `export:*` event emissions | L25 / `src/index.ts` L65, L87-L92, L96 | verbatim |
|
||
| RFC 4180 CSV (delimiter / quoting / newlines) | L26 / `src/exporters/csv/csv.exporter.ts` | verbatim |
|
||
| SpreadsheetML XLSX (XML, not OOXML/ZIP) | L28, L38 / `src/exporters/xlsx/xlsx.exporter.ts` | verbatim — OOXML binary is enterprise-future |
|
||
| `registerDataProvider(providers, provider)` helper | `src/index.ts` L109-L114 | verbatim |
|
||
| `ExportEventMap` and `@suaveplan/api` `EventMap` declaration | `src/types.ts` L139-L170 | trimmed to genesis equivalent — augments `@suaveplan/api-plugin/service-registry` for `exportProviders`/`exportJobs` (genesis pattern) |
|
||
|
||
### Requirements with no source line
|
||
|
||
| Requirement | Justification |
|
||
|---|---|
|
||
| `EXPORT_CODES` registry + `ExportError` class | Required by genesis rule: every package extends `SuaveplanError` via `defineCodes`. Upstream uses `new Error(...)`; the audit doc (L52) explicitly flags this as a gap to fix during migration. |
|
||
| OTel instruments (`exports_total{format,outcome}`, `export_rows_total{format}`, `export_duration_ms`) | Required by genesis pre-archive checklist row 5 ("OTel instruments asserted with real in-memory `MeterProvider`"). Upstream audit doc L51 calls for this. |
|
||
| `ServiceRegistry` module augmentation for `exportProviders` / `exportJobs` | Required by genesis `@suaveplan/api-plugin` typed-slot contract; upstream's `@suaveplan/api` had a looser `services: Map<string, unknown>` so no augmentation was needed. |
|
||
| 100% Istanbul coverage; sibling `.md` per impl file | Genesis-wide policy, not package-specific. |
|
||
|
||
## Verification approach
|
||
|
||
Every requirement carries a `**Verified by:**` line in `specs/api-plugin-export/spec.md` pointing at concrete `it(...)` titles in the listed files:
|
||
|
||
- `src/exporters/csv/csv.test.ts` — RFC 4180 CSV, helpers, exporter shape.
|
||
- `src/exporters/json/json.test.ts` — projection, pretty-print, exporter shape.
|
||
- `src/exporters/xlsx/xlsx.test.ts` — XML escaping, column-letter math, cell formatting, builder.
|
||
- `src/services/export.service.test.ts` — `getExporter` (incl. unsupported-format error), `inferColumns`, `runExport` with row + size limits.
|
||
- `src/routes/exports.test.ts` — GET list with filters/pagination, POST create (CSV/JSON/XLSX, custom columns/filename), 404 on missing resource, 400 on limits, 400 on provider error, GET/:id, DELETE/:id.
|
||
- `src/plugin/plugin.test.ts` — descriptor metadata, `onInit` registers services + emits `export:initialized`, `onReady` mounts routes + emits `export:ready`, custom basePath, `onClose` clears state.
|
||
- `src/error-codes/error-codes.test.ts` — every code reachable from a production path.
|
||
- `src/otel/instruments.test.ts` — counters/histogram emit against a real in-memory `MeterProvider`.
|
||
- `src/api-plugin-export.spec-conformance.test.ts` — module augmentation, public surface, type-level claims (`expectTypeOf`).
|
||
|
||
## Migration impact
|
||
|
||
None — additive only. No genesis consumer currently imports `@suaveplan/api-plugin-export`; the package is brand new on this branch.
|