Files
suaveplan/openspec/changes/add-api-plugin-export/proposal.md
2026-05-27 23:29:53 +01:00

7.7 KiB
Raw Blame History

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.