Files
skills/docs/foundation/flow/streams-metadata/src/metadata/metadata.md
2026-05-29 08:20:56 +01:00

4.3 KiB

metadata/metadata

(no submodules)

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

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

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.