# 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` 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`) | | 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` | L23 / `src/types.ts` L127-L132 | verbatim | | Job store as `Map` (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` 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.