# `registry/registry` In-memory plugin registry that owns audio-codec lookup and dispatch for the whole package. _(no submodules)_ ## Purpose `CodecRegistry` is the single entry point through which `decodeSource`, `encodeTo`, `progressiveDecode`, and the streaming transform factories resolve a codec from raw bytes, a format hint, an extension, or a MIME type and then dispatch decode/encode operations to it. Without a single registry, every public entry point would re-implement the lookup + dispatch + error mapping triad — duplicating wiring and risking divergence. The registry also owns the package's OpenTelemetry instrumentation surface for decode/encode calls (`codec.decode` / `codec.encode` spans, the `audio_codec_*` counter and histogram pair). Codecs themselves stay free of OTel concerns; the registry observes the boundary. ## Features - Three lookup indexes — by canonical `name`, by file extension, by MIME type. `get(key)` checks all three so callers do not need to know which shape they hold. - Eager initial registration via `new CodecRegistry({ codecs: [...] })` plus imperative `register(codec)` / `unregister(name)`. - `detect(bytes)` walks every registered codec's `canDecode` in registration order and returns the first match's `name` (or `undefined`). - `decode(bytes, opts?)` resolves the codec, awaits its `decode`, and builds an `AudioDocument` from the resulting `Float32Array[]` PCM. Non-API codec errors are wrapped in `AudioCodecsError("AUDIO_CODECS.DECODE_FAILED")` with the original on `cause`; `AudioCodecsError` instances pass through. - `encode(doc, format, opts?)` materialises the document's PCM, dispatches to the codec registered for `format`, and returns the encoded bytes. Unknown format → `AUDIO_CODECS.NOT_SUPPORTED`. Non-API encode error → `AUDIO_CODECS.ENCODE_FAILED` with `cause`. - `decodeStream(source, format)` returns a `ReadableStream` whose pull loop drives the codec's `createDecoder()` streaming factory. Codecs without that factory throw `AUDIO_CODECS.STREAM_ERROR`. - `encodeStream(doc, format)` returns a `ReadableStream` whose pull loop feeds the document's pages (sorted by `startSample`) into the codec's `createEncoder()`. - `list()` returns every registered codec for introspection. - Zod-validated codec registration via `AudioCodecSchema` — a malformed codec object is rejected at `register()` with `INVALID_SOURCE`. - OTel spans, counter, and duration histogram on every successful decode and encode; spans carry the resolved `format` as an attribute. ## Usage: Basic Example ```ts import { CodecRegistry } from "@suaveplan/audio-codecs/registry/registry"; import { WavCodec } from "@suaveplan/audio-codecs/codecs/wav"; const registry = new CodecRegistry({ codecs: [WavCodec] }); // decode WAV bytes into an AudioDocument const doc = await registry.decode(wavBytes); // re-encode back to WAV at 24-bit PCM const wav = await registry.encode(doc, "wav", { bitDepth: 24, format: "pcm" }); ``` ## Usage: Advanced Example Hot-swap a codec and pipeline a streaming decode into a custom transform: ```ts import { CodecRegistry } from "@suaveplan/audio-codecs/registry/registry"; import { Mp3Codec, WavCodec, FlacCodec } from "@suaveplan/audio-codecs"; const registry = new CodecRegistry({ codecs: [WavCodec, Mp3Codec] }); registry.register(FlacCodec); // additive — now decodes FLAC too const sourceStream: ReadableStream = await fetchAudio(); const pageStream = registry.decodeStream(sourceStream, "mp3"); // pipe through a downstream normaliser (defined elsewhere) const normalised = pageStream.pipeThrough(makeNormaliser()); for await (const page of normalised) { consumePage(page); } // detect-then-dispatch for unknown bytes const fmt = registry.detect(unknownBytes); if (fmt) { const doc = await registry.decode(unknownBytes, { format: fmt }); useDoc(doc); } ``` ## API Reference - `class CodecRegistry` - `constructor(options?: CodecRegistryOptions)` — `options.codecs` are each `register`-ed eagerly. - `register(codec: AudioCodec): void` — throws `AudioCodecsError("AUDIO_CODECS.INVALID_SOURCE")` if `codec` fails `AudioCodecSchema` validation. Replaces any prior codec sharing the same `name`; extension and MIME indexes additively expand. - `unregister(name: string): void` — no-op for unknown names; clears matching extension and MIME entries. - `get(nameOrExtOrMime: string): AudioCodec | undefined` - `list(): readonly AudioCodec[]` - `detect(bytes: Uint8Array): string | undefined` - `decode(bytes, opts?): Promise` — `opts.format` is a hint that wins over `canDecode` detection when supplied; `opts.metadata` is forwarded onto the resulting `AudioDocument`. - `encode(doc, format, opts?): Promise` - `decodeStream(source, format): ReadableStream` - `encodeStream(doc, format): ReadableStream` ## Implementation Notes - The three lookup indexes are kept independently — `unregister` walks both extension and MIME maps and deletes only entries that still point at the removed codec, so registering two codecs that share an extension and unregistering one leaves the other intact. - `detect` iteration order is the order codecs were registered. Callers that need a different priority (e.g. AIFF before WAV when both canDecode an ambiguous prefix) should re-`register` the higher-priority codec last so its `canDecode` runs first. - The `#instrumented` helper centralises span lifetime, counter, and histogram observations. It catches any thrown error, sets the span status to ERROR, records the exception, then re-throws — so the outer-most caller still observes the original (or wrapped) error. - `pcmFromDocument` walks the document's pages in their stored order, not by `startSample`. For documents produced by `buildDocument` this is the same; for hand-constructed documents the caller is responsible for ordered insertion. `encodeStream` does sort by `startSample` explicitly to handle reordering at the streaming boundary. - The constants `TRACER`, `METER`, `DECODE_COUNTER`, `ENCODE_COUNTER`, `DECODE_HIST`, `ENCODE_HIST` are acquired at module evaluation time but the underlying OTel SDK is a no-op when no exporter is registered, so importing the registry has no observable runtime cost in unconfigured environments.