first commit
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
# `export-service.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Service-level orchestration for the export plugin. This module wires the three exporter classes (`CsvExporter`, `JsonExporter`, `XlsxExporter`) behind a single `getExporter(format)` dispatcher, derives default column definitions from raw data via `inferColumns(data)`, and enforces the row + byte-size guards in `runExport(...)`. All guard failures throw `ExportError` carrying one of the four service-level codes (`UNSUPPORTED_FORMAT`, `ROW_LIMIT_EXCEEDED`, `FILE_SIZE_EXCEEDED`, `PROVIDER_FETCH_FAILED`) rather than the raw `Error` upstream used.
|
||||
|
||||
## Features
|
||||
|
||||
- `getExporter(format)` returns a fresh `Exporter` instance per call — no shared state across exports.
|
||||
- `inferColumns(data)` collapses heterogeneous row keys into a stable column order (first-seen).
|
||||
- `runExport(provider, format, columns?, filters?, options?, config?)` is the single async path used by the route handler — every guard lives here.
|
||||
- `DEFAULT_EXPORT_CONFIG` captures the upstream defaults (50 000 rows, 50 MiB).
|
||||
- Failures from `provider.fetch` are wrapped in `ExportError(PROVIDER_FETCH_FAILED)` with the original error preserved as `cause`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import {
|
||||
runExport,
|
||||
DEFAULT_EXPORT_CONFIG,
|
||||
type DataProvider,
|
||||
} from "@suaveplan/api-plugin-export";
|
||||
|
||||
const provider: DataProvider = {
|
||||
resource: "users",
|
||||
fetch: async () => [{ id: 1 }],
|
||||
};
|
||||
const result = await runExport(provider, "csv", undefined, undefined, undefined, DEFAULT_EXPORT_CONFIG);
|
||||
console.log(result.content);
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import {
|
||||
runExport,
|
||||
type ExportServiceConfig,
|
||||
} from "@suaveplan/api-plugin-export";
|
||||
|
||||
const config: ExportServiceConfig = {
|
||||
maxRows: 1_000,
|
||||
maxFileSizeBytes: 1024 * 1024,
|
||||
defaultFormat: "csv",
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await runExport(
|
||||
myProvider,
|
||||
"json",
|
||||
[{ key: "id", header: "ID" }],
|
||||
{ active: true },
|
||||
{ pretty: true },
|
||||
config
|
||||
);
|
||||
// …
|
||||
} catch (err) {
|
||||
if (err instanceof ExportError && err.code === "EXPORT.ROW_LIMIT_EXCEEDED") {
|
||||
// tell the user to narrow their filters
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Notes |
|
||||
|---|---|---|
|
||||
| `ExportServiceConfig` | interface | `{ maxRows, maxFileSizeBytes, defaultFormat }`. |
|
||||
| `DEFAULT_EXPORT_CONFIG` | const | Upstream defaults: 50 000 rows, 50 MiB, `csv`. |
|
||||
| `getExporter(format)` | function | Throws `EXPORT.UNSUPPORTED_FORMAT` on unknown. |
|
||||
| `inferColumns(data)` | function | Returns `[]` on empty data. |
|
||||
| `runExport(provider, format, columns?, filters?, options?, config?)` | async function | Throws `EXPORT.*` on guard breach. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The two guards run in a specific order: row count is checked *before* the exporter runs so a runaway provider doesn't burn CPU encoding fields the route layer is about to reject. File size is checked *after* encoding because cell-level transforms and XML/JSON overhead can push a "small" row count over the byte cap. The `default:` branch in `getExporter` is reachable only if a caller bypasses the Zod boundary (the schema enumerates `csv|json|xlsx`); the `format as string` cast in the error message is intentional — we want the actual value the caller passed to surface in logs, not the post-narrowing `never`. `provider.fetch` errors are wrapped rather than thrown raw because the route layer needs to distinguish a *bad provider* (502-class) from a *bad request* (400-class), and the `ExportError` code is the contract that drives that branching upstream.
|
||||
@@ -0,0 +1,44 @@
|
||||
# `registry-augmentation.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
This file is a type-only `declare module "@suaveplan/api-plugin/service-registry"` block. It adds two slots to the shared `ServiceRegistry` interface so downstream plugins can resolve `ctx.getService("exportProviders")` and `ctx.getService("exportJobs")` with full typing — `Map<string, DataProvider>` and `Map<string, ExportJob>` respectively. The file produces no emitted runtime code; the only side effect is the type augmentation, which is why `src/index.ts` imports it for its side effects (so consumers using the package barrel pick up the slot types automatically).
|
||||
|
||||
## Features
|
||||
|
||||
- Adds `exportProviders: Map<string, DataProvider>` to `ServiceRegistry`.
|
||||
- Adds `exportJobs: Map<string, ExportJob>` to `ServiceRegistry`.
|
||||
- No runtime cost — the file emits no JavaScript.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import "@suaveplan/api-plugin-export"; // pulls the augmentation through the barrel
|
||||
// Now ctx.getService("exportProviders") is typed Map<string, DataProvider> | undefined
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```ts
|
||||
import "@suaveplan/api-plugin-export/services/registry-augmentation";
|
||||
|
||||
// In a downstream plugin's onInit:
|
||||
const providers = ctx.getService("exportProviders");
|
||||
if (!providers) throw new Error("export plugin not initialised");
|
||||
providers.set("orders", myOrderProvider);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Symbol | Kind | Notes |
|
||||
|---|---|---|
|
||||
| `ServiceRegistry.exportProviders` | augmentation | `Map<string, DataProvider>`. |
|
||||
| `ServiceRegistry.exportJobs` | augmentation | `Map<string, ExportJob>`. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The augmentation pattern is identical to `@suaveplan/api-plugin-database`'s `registry-augmentation.ts` — both files exist purely to add slots to the shared registry interface defined by `@suaveplan/api-plugin/service-registry`. Without this file, every call site would have to cast the `getService("exportProviders")` return value, defeating the type safety that the `ServiceRegistry` interface was designed to provide. The two slots cover the two distinct concerns the plugin owns: the *registry* of data providers (writable by every consumer that wants to expose a resource for export) and the *store* of jobs (mutated by the route layer, observable by anyone tracking export progress). Exposing both is a deliberate departure from upstream, which exposed them via `ctx.registerService(...)` at runtime but never declared the corresponding types.
|
||||
Reference in New Issue
Block a user