Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,108 @@
# `errors`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Stable error registry for the `@suaveplan/streams-metadata` package. Every
failure surface produced by `MimeTransform`, `ExifTransform`, or
`MetadataTransform` raises a subclass of `StreamsMetadataError` carrying one
of the codes registered through `defineCodes("STREAMS_METADATA", [...])`.
Callers `instanceof` test the typed subclasses (or compare `error.code`) to
distinguish failure modes without parsing message strings.
The registry guarantees a closed code set: every code below is reachable
from a real call site in `src/`, so OpenSpec conformance does not flag
orphan codes and downstream consumers can build exhaustive error-handling
switches that the compiler verifies.
## Features
- `STREAMS_METADATA_CODES` registered via `defineCodes`, frozen at module
load with stable string codes prefixed `STREAMS_METADATA.`.
- `StreamsMetadataErrorCode` discriminated string-union derived from the
registry.
- `StreamsMetadataError` base class extending `SuaveplanError`; one factory
subclass per code (`MimeDetectionFailedError`, `ExifFailedError`,
`InvalidPayloadError`, `MetadataAbortedError`).
- `cause` chain preserved when wrapping foreign throws (e.g. an
`@suaveplan/exif::ExifError`).
## Usage: Basic Example
```ts
import {
ExifFailedError,
STREAMS_METADATA_CODES,
} from "@suaveplan/streams-metadata";
try {
// call ExifTransform.process(...)
} catch (err) {
if (err instanceof ExifFailedError) {
console.error(err.code, err.cause);
// err.code === STREAMS_METADATA_CODES.EXIF_FAILED
}
}
```
## Usage: Advanced Example
```ts
import {
STREAMS_METADATA_CODES,
StreamsMetadataError,
type StreamsMetadataErrorCode,
} from "@suaveplan/streams-metadata";
function describe(err: StreamsMetadataError): string {
const code: StreamsMetadataErrorCode = err.code;
switch (code) {
case STREAMS_METADATA_CODES.MIME_FAILED:
return "MIME detection engine error";
case STREAMS_METADATA_CODES.EXIF_FAILED:
return "EXIF reader rejected the bytes";
case STREAMS_METADATA_CODES.INVALID_PAYLOAD:
return "Caller handed in a payload without bytes";
case STREAMS_METADATA_CODES.ABORTED:
return "Operation cancelled via AbortSignal";
}
}
```
## API Reference
- `STREAMS_METADATA_CODES`: frozen mapping with keys `MIME_FAILED`,
`EXIF_FAILED`, `INVALID_PAYLOAD`, `ABORTED`; values are the namespaced
strings (`"STREAMS_METADATA.MIME_FAILED"`, etc.).
- `StreamsMetadataErrorCode`: union of every value in
`STREAMS_METADATA_CODES`.
- `class StreamsMetadataError extends SuaveplanError<StreamsMetadataErrorCode>`:
base; never thrown directly.
- `class MimeDetectionFailedError(opts?: { cause?; message? })`: reserved
for engine-level failures inside `MimeTransform`. The documented
`application/octet-stream` fallback does NOT raise this.
- `class ExifFailedError(opts?: { cause?; message? })`: raised by
`ExifTransform` whenever `@suaveplan/exif::readMetadata` throws. The
original `ExifError` is preserved on `cause`.
- `class InvalidPayloadError(opts?: { message? })`: raised when a transform
receives a payload missing the mandatory `bytes` field.
- `class MetadataAbortedError(opts?: { cause?; message? })`: optional
alternative to `@suaveplan/streams::StreamAbortedError` used by callers
who want every error in the streams-metadata namespace.
## Implementation Notes
- Every factory subclass forwards `cause` only when it is an `Error`
instance — non-Error throws are dropped from the `cause` slot but the
caller can still observe them through `metadata:error` listener payloads
on `MetadataTransform`.
- Codes are reachable from real call sites: `EXIF_FAILED` via
`src/exif/exif.ts`, `INVALID_PAYLOAD` via `src/mime/mime.ts` and
`src/exif/exif.ts`, `ABORTED` via `MetadataAbortedError`, and
`MIME_FAILED` via the dedicated subclass exposed to downstream callers
who wrap their own MIME engines.
- `defineCodes` freezes the returned object, so the registry is safe to
share across realms.

View File

@@ -0,0 +1,92 @@
# `events`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Declares the typed `StreamsMetadataEvents` map consumed by every transform
in this package. `BaseTransform` from `@suaveplan/streams` already
carries a generic event API parameterised by an event-map type, so this
package does not need (and does not expose) a separate `EventEmitter`
property: every transform subclasses
`BaseTransform<{}, StreamsMetadataEvents>` and inherits typed
`on(name, fn)` / `off(name, fn)` methods.
The event map is the public observability contract — listeners attach to
the transform directly and receive strongly typed payloads, with
TypeScript rejecting accesses to fields that do not exist on the declared
payload.
## Features
- Single-source map of event name to payload type, consumed by all three
transforms and re-exported from the package root.
- Compatible with the `EventMap` constraint declared by
`@suaveplan/streams::BaseTransform`.
- Four events covering both child stages (`mime:detected`, `exif:detected`)
and the wrapper's lifecycle (`metadata:complete`, `metadata:error`).
## Usage: Basic Example
```ts
import { MetadataTransform } from "@suaveplan/streams-metadata";
const transform = new MetadataTransform();
transform.on("mime:detected", (r) => {
console.log("MIME:", r.mimeType, "source:", r.source);
});
transform.on("metadata:complete", (p) => {
console.log("Done:", p.mime?.mimeType, p.exif?.format);
});
```
## Usage: Advanced Example
```ts
import {
MetadataTransform,
type StreamsMetadataEvents,
} from "@suaveplan/streams-metadata";
const transform = new MetadataTransform();
const handlers: {
[K in keyof StreamsMetadataEvents]: (payload: StreamsMetadataEvents[K]) => void;
} = {
"mime:detected": (r) => console.log("MIME", r.priority, r.mimeType),
"exif:detected": (m) => console.log("EXIF", m.format),
"metadata:complete": (p) => console.log("OK", p.bytes.byteLength, "bytes"),
"metadata:error": (e) => console.error("FAIL", e),
};
for (const name of Object.keys(handlers) as (keyof StreamsMetadataEvents)[]) {
transform.on(name, handlers[name] as never);
}
```
## API Reference
- `type StreamsMetadataEvents`:
- `"mime:detected": MimeDetectionResult` — fired by `MimeTransform` (and
forwarded by `MetadataTransform`) after detection completes.
- `"exif:detected": ImageMetadata` — fired by `ExifTransform` (and
forwarded by `MetadataTransform`) after the underlying
`@suaveplan/exif::readMetadata` returns.
- `"metadata:complete": EnrichedPayload` — fired only by
`MetadataTransform`, once both children succeed.
- `"metadata:error": unknown` — fired only by `MetadataTransform` when
either child throws. The payload is the original thrown value.
## Implementation Notes
- The map is declared as a `type` alias (not an `interface`) so it
satisfies the structural `Readonly<Record<string, unknown>>` constraint
imposed by `@suaveplan/streams::EventMap` without interface widening.
- Field order is fixed for stable JSDoc generation: child events first,
wrapper events last.
- Adding a new event is a breaking change to the public API — bump the
package version and document it in the next archived spec change.

View File

@@ -0,0 +1,102 @@
# `exif/exif`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`ExifTransform` is a universal stream transform that extracts
`ImageMetadata` via `@suaveplan/exif::readMetadata` and (in `enrich` mode)
attaches the result to the payload under `exif`. Unlike `MimeTransform`,
this stage does raise on unsupported bytes — the underlying reader throws
`ExifError(EXIF.UNSUPPORTED_FORMAT)` for non-image input. `ExifTransform`
re-wraps every reader exception as `ExifFailedError` so callers can
`instanceof StreamsMetadataError` and inspect the original `cause` chain.
The class extends `@suaveplan/streams::BaseTransform`, so the same
`on`/`off`/`toTransformStream`/`dispose` surface every streams consumer
uses is available without any glue. `@suaveplan/exif`'s `ReadOptions`
(e.g. `skipXmp`, `skipIptc`) are forwarded verbatim through the constructor.
## Features
- Synchronous EXIF / IPTC / XMP extraction via
`@suaveplan/exif::readMetadata`.
- `read` options pass-through (skip XMP, IPTC, thumbnails, etc.).
- `exif:detected` event emitted via the inherited
`BaseTransform.on`/`emit` API.
- `enrich` mode (default) shallow-clones the payload with `exif` attached;
`passthrough` mode returns the original input reference unchanged.
- Wraps every reader exception as `ExifFailedError`
(`STREAMS_METADATA.EXIF_FAILED`) with `cause` preserved.
- Raises `InvalidPayloadError` when the payload is missing the mandatory
`bytes: Uint8Array` field.
## Usage: Basic Example
```ts
import { ExifTransform } from "@suaveplan/streams-metadata";
const transform = new ExifTransform();
const result = await transform.process({ bytes: jpegBytes });
console.log(result.exif?.format, result.exif?.width, result.exif?.height);
```
## Usage: Advanced Example
```ts
import {
ExifFailedError,
ExifTransform,
STREAMS_METADATA_CODES,
} from "@suaveplan/streams-metadata";
const transform = new ExifTransform({ read: { skipXmp: true } });
transform.on("exif:detected", (m) => {
console.log("Format:", m.format, "Orientation:", m.exif?.orientation);
});
try {
await transform.process({ bytes: notAnImageBytes });
} catch (err) {
if (err instanceof ExifFailedError) {
// err.code === STREAMS_METADATA_CODES.EXIF_FAILED
console.error("EXIF reader rejected the bytes:", err.cause);
}
}
```
## API Reference
- `class ExifTransform extends BaseTransform<{}, StreamsMetadataEvents>`:
- `constructor(options?: ExifTransformOptions)` — accepts
`{ mode?, read? }`.
- `async process<T = unknown>(input: T, options?: ProcessOptions): Promise<T>`
— runs the reader, emits `exif:detected`, returns the enriched payload
(or original input reference in `passthrough` mode). Honours
`options.signal` via `BaseTransform.assertNotAborted`. Throws
`ExifFailedError` (`STREAMS_METADATA.EXIF_FAILED`) when the underlying
reader raises, and `InvalidPayloadError`
(`STREAMS_METADATA.INVALID_PAYLOAD`) when `input.bytes` is not a
`Uint8Array`.
- `name: "exif"`.
- `interface ExifTransformOptions`:
- `mode?: MetadataMode` — `"enrich"` (default) or `"passthrough"`.
- `read?: ReadOptions` — forwarded directly to
`@suaveplan/exif::readMetadata`.
## Implementation Notes
- The reader runs synchronously; `process` is async only to honour the
`BaseTransform` contract.
- Every reader exception is caught and re-thrown as `ExifFailedError`. The
original error is preserved on `cause` only when it is an `Error`
instance — non-Error throws still raise an `ExifFailedError` but with
`cause` left unset.
- The `passthrough` branch returns the original `input` reference (not a
clone) — useful for pipelines that need EXIF observation without
mutating the payload contract.
- Listener exceptions are caught by `BaseTransform.emit` and never bubble
out of `process` — observability cannot break the pipeline.

View File

@@ -0,0 +1,107 @@
# `metadata/metadata`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`MetadataTransform` is the high-level composing wrapper around
`MimeTransform` and `ExifTransform`. A single instance runs MIME detection
followed by EXIF extraction in sequence; the result is a fully enriched
payload carrying both `mime` and `exif`. Child events are forwarded
through the wrapper so a single `on('mime:detected', fn)` subscription on
the wrapper observes the child emission — no need to wire per-transform
listeners. If either child throws, the wrapper catches the exception,
emits `metadata:error` with the thrown value, and resolves with the
original input reference so the pipeline continues.
This is the entry point most callers should reach for; the individual
transforms are exposed for manual composition or when only one stage is
desired.
## Features
- Composes `MimeTransform` then `ExifTransform` with shared mode flag.
- Forwards `mime:detected` and `exif:detected` events to the wrapper's
listeners (no child subscriptions required).
- Emits `metadata:complete` on success with the final enriched payload.
- Emits `metadata:error` with the thrown value on child failure and
returns the original input reference so a downstream pipeline stage can
continue.
- Overrides `dispose()` to release child transforms in addition to the
wrapper's own listener / hook tables.
## Usage: Basic Example
```ts
import { MetadataTransform } from "@suaveplan/streams-metadata";
const transform = new MetadataTransform();
const result = await transform.process({ bytes: jpegBytes, name: "photo.jpg" });
console.log(result.mime?.mimeType); // "image/jpeg"
console.log(result.exif?.format); // "jpeg"
```
## Usage: Advanced Example
```ts
import {
MetadataTransform,
type EnrichedPayload,
} from "@suaveplan/streams-metadata";
const transform = new MetadataTransform({
mode: "enrich",
read: { skipXmp: true },
});
const events: string[] = [];
transform.on("mime:detected", (r) => events.push(`mime:${r.source}`));
transform.on("exif:detected", (m) => events.push(`exif:${m.format}`));
transform.on("metadata:complete", (p: EnrichedPayload) =>
events.push(`complete:${p.bytes.byteLength}`),
);
transform.on("metadata:error", (err) => {
console.error("metadata pipeline failed", err);
});
await transform.process({ bytes: jpegBytes, name: "photo.jpg" });
// events => ["mime:magic-bytes", "exif:jpeg", "complete:..."]
```
## API Reference
- `class MetadataTransform extends BaseTransform<{}, StreamsMetadataEvents>`:
- `constructor(options?: MetadataTransformOptions)` — accepts
`{ mode?, read? }`. `mode` is shared across both children; `read` is
forwarded to the inner `ExifTransform`.
- `async process<T = unknown>(input: T, options?: ProcessOptions): Promise<T>`
— runs `mime.process(input) → exif.process(...)`. On success emits
`metadata:complete` and returns the final enriched payload. On any
child throw emits `metadata:error` with the thrown value and returns
the original `input` reference unchanged.
- `dispose(): void` — releases both child transforms and the wrapper's
own listener / hook tables. Idempotent.
- `name: "metadata"`.
- `type MetadataTransformOptions = MimeTransformOptions & ExifTransformOptions`
— shared `mode` flag plus EXIF-specific `read` options.
## Implementation Notes
- Child events are wired in the constructor via `this.mime.on(...)` and
`this.exif.on(...)`; the lambdas call `this.emit(...)` on the wrapper to
re-emit. The `Unsubscribe` returns are dropped because the child
transforms' listener tables are released through `this.mime.dispose()` /
`this.exif.dispose()` in the override.
- Error containment: a child throw never propagates out of `process` —
observability via `metadata:error` is the canonical failure channel.
The original `input` reference is returned (not a clone) so
referentially-stable downstream stages keep working.
- Subclasses overriding `dispose()` MUST call `super.dispose()` (per the
base contract). This class follows that rule by chaining
`super.dispose()` after disposing children.
- The constructor honours `exactOptionalPropertyTypes` by only forwarding
`mode` / `read` when explicitly supplied, so the children use their own
defaults when the wrapper's options bundle omits the field.

View File

@@ -0,0 +1,101 @@
# `mime/mime`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`MimeTransform` is a universal stream transform that decides the MIME
type of a buffered binary payload and (in `enrich` mode) attaches the
detection result to the payload under `mime`. It delegates to
`@suaveplan/mime` for both magic-bytes and extension-based detection,
layering a single composite path on top: magic bytes win when they match,
then the caller's `payload.name` is consulted for an extension match, then
`application/octet-stream` is the final fallback. The transform never
throws for unknown bytes — the fallback is the documented success path —
and emits a typed `mime:detected` event in every mode.
The class extends `@suaveplan/streams::BaseTransform`, so the same
`on`/`off`/`toTransformStream`/`dispose` surface every streams consumer
uses is available without any glue.
## Features
- Magic-bytes detection via `@suaveplan/mime::detectFromMagicBytes`, with
the underlying priority preserved on the result.
- Extension fallback via `@suaveplan/mime::detectFromExtension` (only
consulted when magic bytes return `null` and `payload.name` is a string).
- `application/octet-stream` final fallback with `priority: 0` and
`source: "fallback"`.
- `mime:detected` event emitted via the inherited
`BaseTransform.on`/`emit` API.
- `enrich` mode (default) shallow-clones the payload with `mime` attached;
`passthrough` mode returns the original input reference unchanged.
- Raises `InvalidPayloadError` when the payload is missing the mandatory
`bytes: Uint8Array` field.
## Usage: Basic Example
```ts
import { MimeTransform } from "@suaveplan/streams-metadata";
const transform = new MimeTransform();
const result = await transform.process({
bytes: new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
name: "image.png",
});
console.log(result.mime?.mimeType); // "image/png"
console.log(result.mime?.source); // "magic-bytes"
```
## Usage: Advanced Example
```ts
import { MimeTransform } from "@suaveplan/streams-metadata";
const transform = new MimeTransform({ mode: "passthrough" });
transform.on("mime:detected", (r) => {
if (r.source === "fallback") {
console.warn("Unknown MIME — defaulting to application/octet-stream");
} else {
console.log(`MIME: ${r.mimeType} (priority ${r.priority}, via ${r.source})`);
}
});
const input = { bytes: new Uint8Array(), name: "song.mp3" };
const out = await transform.process(input);
console.log(out === input); // true
```
## API Reference
- `class MimeTransform extends BaseTransform<{}, StreamsMetadataEvents>`:
- `constructor(options?: MimeTransformOptions)` — accepts `{ mode? }`,
defaulting to `"enrich"`.
- `async process<T = unknown>(input: T, options?: ProcessOptions): Promise<T>`
— runs detection, emits `mime:detected`, returns the enriched payload
(or original input reference in `passthrough` mode). Honours
`options.signal` via `BaseTransform.assertNotAborted`. Throws
`InvalidPayloadError` (`STREAMS_METADATA.INVALID_PAYLOAD`) when
`input.bytes` is not a `Uint8Array`.
- `name: "mime"` — stable name carried by every instance.
- Inherits `on` / `off` / `dispose` / `toTransformStream` from
`BaseTransform`.
- `interface MimeTransformOptions`:
- `mode?: MetadataMode` — `"enrich"` (default) or `"passthrough"`.
## Implementation Notes
- Detection precedence is exactly: magic bytes → extension → fallback. The
contract guarantees every call returns a `MimeDetectionResult`, so
consumers never need to handle a `null` / `undefined` return.
- The magic-bytes branch consults `detectFromExtension(payload.name)` only
to recover the extension string; the MIME type itself always comes from
the magic-bytes layer (which has higher confidence than extension).
- `passthrough` mode is reference-stable — the returned value is `===` the
input. Useful for pipelines that need MIME observation without mutating
the byte payload contract.
- Listener exceptions are caught by `BaseTransform.emit` and never bubble
out of `process` — observability cannot break the pipeline.

View File

@@ -0,0 +1,108 @@
# `types/payload`
## Purpose
Public type contract shared by every `@suaveplan/streams-metadata` transform.
Callers and library code agree on the shape of a payload flowing through the
metadata pipeline through four types: the mode flag, the input payload, the
MIME detection result, and the enriched output. Defining them once keeps
transform implementations free of duplicated structural declarations and
gives consumers a single import path
(`@suaveplan/streams-metadata/types/payload`) for downstream typing of their
own pipeline stages.
The contract is deliberately small: it owns only the cross-transform
vocabulary; transform-specific options live next to their transform.
## Features
- `MetadataMode` — switches every transform between `enrich` (mutate the
payload) and `passthrough` (return the original input reference).
- `MetadataPayload` — minimum shape every transform accepts: `bytes`
required, `name` optional, arbitrary excess fields preserved across stages.
- `MimeDetectionResult` — full detection record carrying MIME type, magic
priority, extension, and the detection source.
- `EnrichedPayload` — `MetadataPayload` with optional `mime` and `exif`
fields populated by their respective transforms.
## Usage: Basic Example
```ts
import type {
MetadataPayload,
MimeDetectionResult,
} from "@suaveplan/streams-metadata";
function logDetection(result: MimeDetectionResult): void {
console.log(`Detected ${result.mimeType} via ${result.source}`);
}
const payload: MetadataPayload = {
bytes: new Uint8Array([0x89, 0x50, 0x4e, 0x47]),
name: "image.png",
};
```
## Usage: Advanced Example
```ts
import type {
EnrichedPayload,
MetadataMode,
} from "@suaveplan/streams-metadata";
function isFullyEnriched(p: EnrichedPayload): boolean {
return p.mime !== undefined && p.exif !== undefined;
}
// Excess fields survive the pipeline:
const incoming: EnrichedPayload = {
bytes: new Uint8Array(),
name: "x.jpg",
uploadId: "u-123",
tenantId: "tenant-7",
};
function pickMode(passthroughOnly: boolean): MetadataMode {
return passthroughOnly ? "passthrough" : "enrich";
}
```
## API Reference
- `type MetadataMode = "passthrough" | "enrich"` — controls payload
mutation. `"enrich"` (default everywhere) returns a shallow-cloned
payload with the detection result attached; `"passthrough"` returns the
original input reference unchanged. Events still fire in both modes.
- `interface MetadataPayload`:
- `bytes: Uint8Array` — mandatory binary payload to inspect.
- `name?: string` — optional filename hint used for extension-based MIME
fallback.
- `[key: string]: unknown` — arbitrary excess fields preserved across
transforms.
- `interface MimeDetectionResult`:
- `mimeType: string` — full MIME type, e.g. `"image/jpeg"`.
- `priority: number` — magic-bytes detection priority (`0` for
extension- and fallback-sourced results).
- `extension?: string` — file extension without the leading dot.
- `source: "magic-bytes" | "extension" | "fallback"` — discriminator for
how the result was determined.
- `interface EnrichedPayload extends MetadataPayload`:
- `mime?: MimeDetectionResult` — present when a `MimeTransform` ran in
`enrich` mode.
- `exif?: ImageMetadata` — present when an `ExifTransform` ran in
`enrich` mode. Type re-exported from `@suaveplan/exif`.
## Implementation Notes
- The `[key: string]: unknown` index signature on `MetadataPayload` lets
downstream callers layer their own typed payload (e.g. a tenant-scoped
upload context) on top via interface merging without forking this
package.
- `extension` is optional on `MimeDetectionResult` because magic-bytes
detection alone does not always recover an extension; the field is only
populated when the caller supplies `payload.name`.
- `MetadataPayload.bytes` is intentionally typed as a concrete `Uint8Array`
rather than `ArrayBufferView` — every consumer (`@suaveplan/mime`,
`@suaveplan/exif`) requires a `Uint8Array` and we want a single boundary
validation point inside each transform.

View File

@@ -0,0 +1,84 @@
# `types/process-options`
## Purpose
Defines the `ProcessOptions` shape accepted by every
`@suaveplan/streams-metadata` transform's `process(input, options?)` method.
The shape mirrors the protected `TransformOptions` carried by
`@suaveplan/streams::BaseTransform` (`signal`, `clock`, `logger`) and exists
as a local declaration because the upstream type is not re-exported from
`@suaveplan/streams`'s public surface. Structural compatibility with the
base shape keeps the `process` method's override valid while letting
downstream callers depend only on this package's public API.
## Features
- `signal?: AbortSignal` — cancellation hook honoured by every transform
via `BaseTransform.assertNotAborted`.
- `clock?: Clock` — injectable monotonic clock from `@suaveplan/types`,
reserved for future timing-sensitive transforms.
- `logger?: LoggerLike` — structured logger from `@suaveplan/types`;
receives swallowed listener errors per the base contract.
## Usage: Basic Example
```ts
import { MimeTransform } from "@suaveplan/streams-metadata";
const transform = new MimeTransform();
const controller = new AbortController();
const result = await transform.process(
{ bytes: imageBytes },
{ signal: controller.signal },
);
```
## Usage: Advanced Example
```ts
import {
MetadataTransform,
type ProcessOptions,
} from "@suaveplan/streams-metadata";
import type { Clock, LoggerLike } from "@suaveplan/types";
const clock: Clock = { now: () => 1700000000000 };
const logger: LoggerLike = {
error: (msg, ctx) => console.error("[metadata]", msg, ctx),
};
const opts: ProcessOptions = {
signal: new AbortController().signal,
clock,
logger,
};
const transform = new MetadataTransform();
const out = await transform.process({ bytes: jpegBytes, name: "x.jpg" }, opts);
```
## API Reference
- `interface ProcessOptions`:
- `signal?: AbortSignal` — when fired before `process` yields,
`BaseTransform.assertNotAborted` throws `StreamAbortedError` from
`@suaveplan/streams`. The signal is rechecked at every yield point.
- `clock?: Clock` — `@suaveplan/types::Clock` providing `now()`. The
current package does not consume it but the slot is reserved to keep
the surface stable with the base `TransformOptions`.
- `logger?: LoggerLike` — `@suaveplan/types::LoggerLike` consumed by
`BaseTransform.emit` when a registered listener throws; the throw is
forwarded to `logger.error(...)` and never propagated to the `process`
caller.
## Implementation Notes
- The interface is `readonly` on every field — callers building a
long-lived options bundle should treat it as immutable.
- Structural compatibility: this type matches the base shape with no extra
fields, so passing a `ProcessOptions` into `BaseTransform.emit` and
`BaseTransform.assertNotAborted` is type-safe.
- `clock` is reserved for forward-compatibility; the current MIME and EXIF
transforms are synchronous on the timing axis and do not read the
clock. Listing it here ensures a future timing-sensitive transform can
land without a breaking API change.