first commit
This commit is contained in:
13
docs/foundation/etl/README.md
Normal file
13
docs/foundation/etl/README.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# `etl`
|
||||
|
||||
Extract-Transform-Load pipeline engine with typed stages.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Packages in `etl`
|
||||
|
||||
1 package.
|
||||
|
||||
| Package | Description |
|
||||
|---|---|
|
||||
| [`@suaveplan/etl`](./etl/README.md) | Extract-Transform-Load pipeline engine with typed stages, error handling, observable progress, and OTel instrumentation. |
|
||||
<!-- end:auto-docs -->
|
||||
1
docs/foundation/etl/_intro.md
Normal file
1
docs/foundation/etl/_intro.md
Normal file
@@ -0,0 +1 @@
|
||||
Extract-Transform-Load pipeline engine with typed stages.
|
||||
46
docs/foundation/etl/etl/README.md
Normal file
46
docs/foundation/etl/etl/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# @suaveplan/etl - Extract-Transform-Load pipeline engine with typed stages, error handling, observable progress, and OTel instrumentation
|
||||
|
||||
## Summary
|
||||
|
||||
Extract-Transform-Load pipeline engine with typed stages, error handling, observable progress, and OTel instrumentation.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [pipeline](./src/core/pipeline/pipeline.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [pipeline](./src/core/pipeline/pipeline.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [errors](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [etl-error](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [etl.schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_ <!-- end:auto-docs -->
|
||||
<!-- end:auto-docs --> - [pipeline](./src/core/pipeline/pipeline.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [etl-error](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [etl.schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_- [pipeline](./src/core/pipeline/pipeline.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [etl-error](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [etl.schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_- [pipeline](./src/core/pipeline/pipeline.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [etl-error](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [etl.schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_- [pipeline](./src/core/pipeline/pipeline.md) — <!-- begin:auto-docs --> _(no submodules)_- [errors](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_ - [etl-error](./src/errors/etl-error.md) — <!-- begin:auto-docs --> _(no submodules)_- [schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_ - [etl.schemas](./src/schemas/etl.schemas.md) — <!-- begin:auto-docs --> _(no submodules)_- [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_ - [sink](./src/stages/sink/sink.md) — <!-- begin:auto-docs --> _(no submodules)_- [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_ - [source](./src/stages/source/source.md) — <!-- begin:auto-docs --> _(no submodules)_- [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_ - [transform](./src/stages/transform/transform.md) — <!-- begin:auto-docs --> _(no submodules)_
|
||||
## Modules
|
||||
|
||||
- [Pipeline](src/core/pipeline/pipeline.md) — pipeline orchestrator with fluent API
|
||||
- [Source](src/stages/source/source.md) — array and generator source factories
|
||||
- [Transform](src/stages/transform/transform.md) — map, filter, validate, flat-map transforms
|
||||
- [Sink](src/stages/sink/sink.md) — array, null, callback, and batch sinks
|
||||
- [EtlError](src/errors/etl-error.md) — structured errors with stable codes
|
||||
- [Schemas](src/schemas/etl.schemas.md) — Zod schemas for pipeline types
|
||||
- [Types](src/types/etl.types.md) — shared TypeScript interfaces
|
||||
|
||||
## Quick Start
|
||||
|
||||
```ts
|
||||
import { createPipeline, arraySource, mapTransform, ArraySink } from "@suaveplan/etl";
|
||||
|
||||
const sink = new ArraySink<number>();
|
||||
const result = await createPipeline({ source: arraySource([1, 2, 3]) })
|
||||
.pipe(mapTransform((n) => n * 2))
|
||||
.to(sink)
|
||||
.run();
|
||||
|
||||
console.log(result.stats.loaded); // 3
|
||||
console.log(sink.records); // [2, 4, 6]
|
||||
```
|
||||
82
docs/foundation/etl/etl/src/core/pipeline/pipeline.md
Normal file
82
docs/foundation/etl/etl/src/core/pipeline/pipeline.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# pipeline
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides `createPipeline`, the primary orchestrator for the ETL engine. A pipeline connects a `Source`, zero or more `Transform` stages, and a `Sink` into a single async execution graph. Records flow from source through transforms to sink with AbortSignal cancellation, per-record error handling, optional bus event emission, and OTel instrumentation.
|
||||
|
||||
## Features
|
||||
|
||||
- `createPipeline(opts)` — creates a `Pipeline<TIn>` from a source and options
|
||||
- `.pipe(transform)` — adds a transform stage; returns a new typed `Pipeline<TIn, TNext>`
|
||||
- `.to(sink)` — attaches a sink; returns a `RunnablePipeline`
|
||||
- `.run(signal?)` — executes the pipeline; returns `PipelineResult` with stats and non-fatal errors
|
||||
- `.dryRun(maxRecords?)` — runs with a limited record count for preview/validation
|
||||
- `onError` callback — returns `"skip" | "abort" | "retry"` per error
|
||||
- Bus events: `etl:pipeline:started`, `etl:pipeline:completed`, `etl:pipeline:failed`
|
||||
- OTel span `etl.pipeline.run` wrapping the full execution
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { createPipeline } from "@suaveplan/etl";
|
||||
import { arraySource } from "@suaveplan/etl";
|
||||
import { mapTransform } from "@suaveplan/etl";
|
||||
import { ArraySink } from "@suaveplan/etl";
|
||||
|
||||
const sink = new ArraySink<number>();
|
||||
const result = await createPipeline({ source: arraySource([1, 2, 3]) })
|
||||
.pipe(mapTransform((n) => n * 2))
|
||||
.to(sink)
|
||||
.run();
|
||||
|
||||
console.log(result.stats.loaded); // 3
|
||||
console.log(sink.records); // [2, 4, 6]
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { createPipeline, arraySource, validateTransform, ArraySink, ETL_CODES } from "@suaveplan/etl";
|
||||
import z from "zod";
|
||||
|
||||
const schema = z.object({ id: z.string() });
|
||||
const sink = new ArraySink();
|
||||
|
||||
const result = await createPipeline({
|
||||
source: arraySource([{ id: "1" }, { id: 2 }, { id: "3" }] as unknown[]),
|
||||
onError: (err) => err.code === ETL_CODES.VALIDATION_FAILED ? "skip" : "abort",
|
||||
})
|
||||
.pipe(validateTransform(schema))
|
||||
.to(sink)
|
||||
.run();
|
||||
|
||||
console.log(result.stats.loaded); // 2 (one invalid skipped)
|
||||
console.log(result.errors.length); // 1
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `createPipeline<TIn>(opts)`
|
||||
|
||||
- `opts.source: Source<TIn>` — extraction source
|
||||
- `opts.batchSize?: number` — not used internally (BatchSink handles batching)
|
||||
- `opts.onError?: ErrorHandler` — per-record error callback
|
||||
- `opts.clock?: Clock` — injectable clock for duration calculation
|
||||
- `opts.bus?: { publish }` — optional event bus
|
||||
|
||||
### `Pipeline<TIn, TOut>`
|
||||
|
||||
- `pipe<TNext>(transform): Pipeline<TIn, TNext>`
|
||||
- `to(sink: Sink<TOut>): RunnablePipeline<TIn, TOut>`
|
||||
|
||||
### `RunnablePipeline<TIn, TOut>`
|
||||
|
||||
- `run(signal?): Promise<PipelineResult>`
|
||||
- `dryRun(maxRecords?): Promise<PipelineResult>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The pipeline accumulates transforms in order without composing them eagerly — each record is passed through the array sequentially on every iteration. This allows clean type inference via the `.pipe()` fluent API. The `dryRun` method installs an internal abort controller that fires after `maxRecords` writes.
|
||||
67
docs/foundation/etl/etl/src/errors/etl-error.md
Normal file
67
docs/foundation/etl/etl/src/errors/etl-error.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# etl-error
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Defines the structured error type and stable error-code registry for `@suaveplan/etl`. All pipeline failures — extraction errors, transformation errors, load errors, validation failures, and aborts — surface as `EtlError` instances with a machine-readable `code` field, enabling callers to discriminate by stage without fragile message parsing.
|
||||
|
||||
## Features
|
||||
|
||||
- `ETL_CODES` registry via `@suaveplan/error`'s `defineCodes` — codes namespaced `ETL.*`
|
||||
- `EtlError` extends `SuaveplanError` for cause-chain traversal and wire serialisation
|
||||
- Six codes: `SOURCE_FAILED`, `TRANSFORM_FAILED`, `SINK_FAILED`, `PIPELINE_ABORTED`, `VALIDATION_FAILED`, `BATCH_OVERFLOW`
|
||||
- `EtlCode` union type for exhaustive error handling
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { EtlError, ETL_CODES } from "@suaveplan/etl";
|
||||
|
||||
throw new EtlError(
|
||||
ETL_CODES.VALIDATION_FAILED,
|
||||
"Record failed schema validation",
|
||||
{ context: { field: "email", value: "not-an-email" } }
|
||||
);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { EtlError, ETL_CODES } from "@suaveplan/etl";
|
||||
|
||||
function handleError(err: unknown): void {
|
||||
if (err instanceof EtlError) {
|
||||
switch (err.code) {
|
||||
case ETL_CODES.PIPELINE_ABORTED:
|
||||
console.log("Pipeline was cancelled");
|
||||
break;
|
||||
case ETL_CODES.SOURCE_FAILED:
|
||||
console.error("Could not read source:", err.cause);
|
||||
break;
|
||||
default:
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ETL_CODES`
|
||||
|
||||
- `ETL_CODES.SOURCE_FAILED` — extraction stage error
|
||||
- `ETL_CODES.TRANSFORM_FAILED` — transformation stage error
|
||||
- `ETL_CODES.SINK_FAILED` — load/sink stage error
|
||||
- `ETL_CODES.PIPELINE_ABORTED` — AbortSignal fired or explicit abort
|
||||
- `ETL_CODES.VALIDATION_FAILED` — Zod or custom schema validation failure
|
||||
- `ETL_CODES.BATCH_OVERFLOW` — batch size exceeded configured limit
|
||||
|
||||
### `EtlError`
|
||||
|
||||
Extends `SuaveplanError<EtlCode>`. Constructor: `(code, message, options?)`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Error codes are prefixed with `"ETL."` by `defineCodes`, producing strings like `"ETL.SOURCE_FAILED"`. Wrap underlying errors via `options.cause` to preserve the full error chain.
|
||||
50
docs/foundation/etl/etl/src/schemas/etl.schemas.md
Normal file
50
docs/foundation/etl/etl/src/schemas/etl.schemas.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# etl.schemas
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides Zod validation schemas for the public option and result types in `@suaveplan/etl`. These schemas are used at integration boundaries — for validating pipeline configuration at runtime and for serialising pipeline result statistics for audit trails or dashboards.
|
||||
|
||||
## Features
|
||||
|
||||
- `PipelineStatsSchema` — validates the `PipelineStats` result including non-negative integer counters and duration
|
||||
- `PipelineOptionsSchema` — validates configurable options with passthrough for injectable dependencies
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { PipelineStatsSchema } from "@suaveplan/etl";
|
||||
|
||||
const raw = JSON.parse(statsJson);
|
||||
const stats = PipelineStatsSchema.parse(raw);
|
||||
console.log(stats.loaded);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { PipelineOptionsSchema } from "@suaveplan/etl";
|
||||
|
||||
function validateConfig(config: unknown) {
|
||||
return PipelineOptionsSchema.safeParse(config);
|
||||
}
|
||||
|
||||
const result = validateConfig({ batchSize: 500 });
|
||||
if (!result.success) throw new Error("Invalid pipeline config");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `PipelineStatsSchema`
|
||||
|
||||
Validates `{ extracted, transformed, loaded, skipped, durationMs }`. All counts are non-negative integers; `durationMs` is a non-negative number.
|
||||
|
||||
### `PipelineOptionsSchema`
|
||||
|
||||
Validates `{ batchSize? }`. Uses `.passthrough()` for injectable fields like `source`, `clock`, and `bus`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Injectable dependencies (`Source`, `Sink`, `Transform`, `Clock`, `Bus`) cannot be validated with Zod since they are runtime objects with function members. The schemas cover only the serialisable configuration fields.
|
||||
67
docs/foundation/etl/etl/src/stages/sink/sink.md
Normal file
67
docs/foundation/etl/etl/src/stages/sink/sink.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# sink
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides built-in `Sink` implementations for the ETL pipeline load stage. Sinks receive transformed records and persist or collect them. This module ships four implementations: an array collector, a null discard sink, a callback-backed sink, and a batching sink that accumulates records before flushing to an inner sink.
|
||||
|
||||
## Features
|
||||
|
||||
- `ArraySink<T>` — collects all written records into `sink.records`; ideal for tests
|
||||
- `nullSink<T>()` — discards all records; useful for dry-run benchmarking
|
||||
- `callbackSink<T>(fn)` — calls `fn(record)` for each written record
|
||||
- `BatchSink<T>` — buffers records and flushes them as arrays to an inner `Sink<T[]>` when batch is full or `flush()` is called
|
||||
- All sinks implement the full `Sink<T>` lifecycle: `open`, `write`, `flush`, `close`
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { ArraySink } from "@suaveplan/etl";
|
||||
|
||||
const sink = new ArraySink<number>();
|
||||
await sink.write(1);
|
||||
await sink.write(2);
|
||||
await sink.flush();
|
||||
console.log(sink.records); // [1, 2]
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { BatchSink, ArraySink } from "@suaveplan/etl";
|
||||
|
||||
const inner = new ArraySink<number[]>();
|
||||
const batch = new BatchSink(100, inner);
|
||||
|
||||
for (let i = 0; i < 250; i++) await batch.write(i);
|
||||
await batch.flush(); // flushes remaining 50
|
||||
|
||||
console.log(inner.records.length); // 3 batches: 100 + 100 + 50
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `ArraySink<T>`
|
||||
|
||||
- `records: T[]` — accumulated records
|
||||
- `write(record)` — appends to `records`
|
||||
|
||||
### `nullSink<T>(name?)`
|
||||
|
||||
Discards all writes. `open`, `flush`, `close` are no-ops.
|
||||
|
||||
### `callbackSink<T>(fn, name?)`
|
||||
|
||||
`fn: (record: T) => void | Promise<void>` — called for each write.
|
||||
|
||||
### `BatchSink<T>`
|
||||
|
||||
- `constructor(batchSize, inner, name?)`
|
||||
- Flushes to `inner.write(batch)` when buffer reaches `batchSize`
|
||||
- `flush()` — sends remaining buffer to inner sink
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`BatchSink.close()` calls `flush()` first to ensure no records are lost when the pipeline closes. The inner sink receives `T[]` batches, not individual records — callers must implement the inner sink accordingly.
|
||||
59
docs/foundation/etl/etl/src/stages/source/source.md
Normal file
59
docs/foundation/etl/etl/src/stages/source/source.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# source
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides built-in `Source` implementations for the ETL pipeline. Sources are the extraction stage — they yield records via an `AsyncGenerator`. This module ships two general-purpose factories: `arraySource` for in-memory datasets and `generatorSource` for custom async generators.
|
||||
|
||||
## Features
|
||||
|
||||
- `arraySource(items, name?)` — wraps a plain array as a pipeline source; no I/O, zero latency
|
||||
- `generatorSource(gen, name?)` — wraps an async generator factory for custom extraction logic
|
||||
- Both implement the full `Source<T>` interface with `open`/`read`/`close` lifecycle
|
||||
- Named for OTel span attribution
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { arraySource } from "@suaveplan/etl";
|
||||
|
||||
const source = arraySource([{ id: "1", name: "Alice" }, { id: "2", name: "Bob" }]);
|
||||
for await (const record of source.read()) {
|
||||
console.log(record.name);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { generatorSource } from "@suaveplan/etl";
|
||||
|
||||
const pagingSource = generatorSource(async function* () {
|
||||
let cursor: string | undefined;
|
||||
do {
|
||||
const page = await fetchPage(cursor);
|
||||
yield* page.records;
|
||||
cursor = page.nextCursor;
|
||||
} while (cursor);
|
||||
}, "ApiPagingSource");
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `arraySource<T>(items, name?)`
|
||||
|
||||
- `items: T[]` — records to yield
|
||||
- `name?: string` — source name for OTel (default: `"ArraySource"`)
|
||||
- Returns `Source<T>`
|
||||
|
||||
### `generatorSource<T>(gen, name?)`
|
||||
|
||||
- `gen: () => AsyncGenerator<T>` — factory called each time `read()` is invoked
|
||||
- `name?: string` — source name for OTel (default: `"GeneratorSource"`)
|
||||
- Returns `Source<T>`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Both factories produce no-op `open` and `close` methods. If your source requires real I/O lifecycle management (database cursors, file handles), implement the `Source<T>` interface directly rather than using these convenience factories.
|
||||
60
docs/foundation/etl/etl/src/stages/transform/transform.md
Normal file
60
docs/foundation/etl/etl/src/stages/transform/transform.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# transform
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Provides built-in `Transform` implementations for the ETL pipeline. Transforms are the middle stage that convert, filter, validate, or expand records. Each transform is a pure function wrapped in the `Transform<TIn, TOut>` interface. The `SKIP` sentinel enables filtering without throwing errors.
|
||||
|
||||
## Features
|
||||
|
||||
- `mapTransform(fn, name?)` — maps each record via `fn`; return `null` to skip the record
|
||||
- `filterTransform(fn, name?)` — keeps records where `fn` returns `true`
|
||||
- `validateTransform(schema, name?)` — parses with a Zod-compatible schema; throws `EtlError(ETL.VALIDATION_FAILED)` on failure
|
||||
- `flatMapTransform(fn, name?)` — expands a record to multiple output records
|
||||
- All transforms are async-capable and accept a `TransformContext` with index and abort signal
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { mapTransform, filterTransform } from "@suaveplan/etl";
|
||||
|
||||
const toUpperCase = mapTransform((s: string) => s.toUpperCase());
|
||||
const nonEmpty = filterTransform((s: string) => s.length > 0);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { validateTransform } from "@suaveplan/etl";
|
||||
import z from "zod";
|
||||
|
||||
const UserSchema = z.object({ id: z.string(), email: z.string().email() });
|
||||
|
||||
const validateUser = validateTransform(UserSchema, "ValidateUser");
|
||||
|
||||
// In a pipeline, invalid records throw EtlError(ETL.VALIDATION_FAILED)
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `mapTransform<TIn, TOut>(fn, name?)`
|
||||
|
||||
`fn: (record: TIn, ctx: TransformContext) => TOut | null | Promise<TOut | null>` — return `null` to skip.
|
||||
|
||||
### `filterTransform<T>(fn, name?)`
|
||||
|
||||
`fn: (record: T, ctx: TransformContext) => boolean | Promise<boolean>` — return `false` to skip.
|
||||
|
||||
### `validateTransform<T>(schema, name?)`
|
||||
|
||||
`schema: { parse: (data: unknown) => T }` — any Zod schema or compatible object.
|
||||
|
||||
### `flatMapTransform<TIn, TOut>(fn, name?)`
|
||||
|
||||
`fn: (record: TIn, ctx: TransformContext) => TOut[] | Promise<TOut[]>` — empty array skips.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`validateTransform` re-throws validation failures as `EtlError` to integrate with the pipeline's `onError` callback. The underlying schema error is preserved as `cause`. The `SKIP` sentinel from `etl.types` is a unique symbol, avoiding confusion with falsy return values.
|
||||
75
docs/foundation/etl/etl/src/types/etl.types.md
Normal file
75
docs/foundation/etl/etl/src/types/etl.types.md
Normal file
@@ -0,0 +1,75 @@
|
||||
# etl.types
|
||||
|
||||
## Purpose
|
||||
|
||||
Shared TypeScript type definitions for the `@suaveplan/etl` pipeline engine. Defines the core interfaces (`Source`, `Transform`, `Sink`), the `SKIP` sentinel, pipeline statistics, result types, and error handling contracts. All ETL implementations depend on these types, which are kept in a separate module to avoid circular imports.
|
||||
|
||||
## Features
|
||||
|
||||
- `Source<T>` — async generator source with `open`/`read`/`close` lifecycle
|
||||
- `Transform<TIn, TOut>` — transforms a single record, returns `TOut | SKIP`
|
||||
- `Sink<T>` — writes records with `open`/`write`/`flush`/`close` lifecycle
|
||||
- `SKIP` unique symbol — filter records from the pipeline without throwing
|
||||
- `TransformContext` — carries the record index and optional abort signal
|
||||
- `PipelineStats` — counters for extracted, transformed, loaded, skipped records and duration
|
||||
- `PipelineResult` — combines stats with a list of non-fatal errors
|
||||
- `ErrorHandler` — per-record error callback returning `"skip" | "abort" | "retry"`
|
||||
- `PipelineOptions<TIn>` — full configuration including batchSize, clock, bus
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { Source, Transform, Sink } from "@suaveplan/etl";
|
||||
import { SKIP } from "@suaveplan/etl";
|
||||
|
||||
const myTransform: Transform<string, number> = {
|
||||
name: "parse-int",
|
||||
transform: (s) => {
|
||||
const n = parseInt(s, 10);
|
||||
return isNaN(n) ? SKIP : n;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { ErrorHandler, PipelineOptions } from "@suaveplan/etl";
|
||||
|
||||
const opts: PipelineOptions<string> = {
|
||||
source: mySource,
|
||||
batchSize: 500,
|
||||
onError: (err, record, stage) => {
|
||||
if (err.code === "ETL.VALIDATION_FAILED") return "skip";
|
||||
if (stage === "source") return "abort";
|
||||
return "retry";
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `Source<T>`
|
||||
|
||||
- `name: string` — display name for OTel spans
|
||||
- `open(signal?): Promise<void>` — open connection
|
||||
- `read(): AsyncGenerator<T>` — yield records
|
||||
- `close(): Promise<void>` — release resources
|
||||
|
||||
### `Transform<TIn, TOut>`
|
||||
|
||||
- `name: string`
|
||||
- `transform(record, ctx): Promise<TOut | SKIP> | TOut | SKIP`
|
||||
|
||||
### `Sink<T>`
|
||||
|
||||
- `name: string`
|
||||
- `open(signal?)`, `write(record)`, `flush()`, `close()`
|
||||
|
||||
### `SKIP`
|
||||
|
||||
Unique symbol. Return from a transform to drop the record from the pipeline.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `SKIP` sentinel is a `unique symbol` rather than `null` or `undefined` to avoid ambiguity with valid `null` payloads. All lifecycle methods (`open`, `close`, `flush`) are async even when no real I/O occurs, to keep adapters interchangeable.
|
||||
Reference in New Issue
Block a user