3.6 KiB
loader.ts — createIrLoader pipeline orchestration
(no submodules)
Purpose
This module is the entry point for the package. createIrLoader(options)
returns a typed IrLoader that composes the IR family pipeline:
IrRegistry.get(id) → cache.get(id) (on hit, fast path) →
StorageUriResolver.read(uri) → decoders[ext](bytes) →
validateAndFinalize(entry, decoded) → cache.put →
return frozen ImpulseResponse. Every layer's errors pass through
verbatim — the loader only owns the six AUDIO_IR_LOADER.* codes from
its own contract.
Features
- Zod-validated options at construction (
createIrLoaderthrowsZodErroron misuse). - Decoder keys normalised to lowercase so callers can write
{ WAV: decodeWav }and have it work. - Optional in-process LRU cache controlled by
options.cache.maxBytes. AbortSignalthreading: short-circuits a pre-aborted signal, passes the signal through to bothresolver.readand the decoder.- Real OpenTelemetry instrumentation: span + duration histogram + bytes / frames / cache / error counters.
- Bounded-concurrency
preload(ids, { concurrency })with aggregate failure reporting (delegated topreload.ts).
Usage: Basic
import { createStorageUriResolver } from "@suaveplan/storage-uri";
import { bundleUriPlugin } from "@suaveplan/storage-uri-plugin-bundle";
import { decodeWav } from "@suaveplan/audio-decode-wav";
import { createIrLoader } from "@suaveplan/audio-ir-loader";
const resolver = createStorageUriResolver([
bundleUriPlugin({ packageSlug: "club", rootUrl: new URL("file:///opt/ir/club/") }),
]);
const loader = createIrLoader({
registry,
resolver,
decoders: { wav: decodeWav },
});
const ir = await loader.load("club-club1");
Usage: Advanced
The loader composes well with an LRU cache and with bounded-concurrency preload for app bootstrap:
const loader = createIrLoader({
registry,
resolver,
decoders: { wav: decodeWav, flac: decodeFlac },
cache: { maxBytes: 64 * 1024 * 1024 },
});
const controller = new AbortController();
const [a, b, c] = await loader.preload(
["club-a", "club-b", "club-c"],
{ signal: controller.signal, concurrency: 2 }
);
// Later, free a specific IR's slot:
loader.evict("club-c");
console.log(loader.cacheStats());
API Reference
createIrLoader(options): IrLoader
options.registry: IrRegistryoptions.resolver: StorageUriResolveroptions.decoders: Record<string, AudioDecoder>— must be non-empty; keys are case-insensitive.options.cache?: { maxBytes: number }— positive integer; enables LRU.
IrLoader
load(id, { signal? }?): Promise<ImpulseResponse>preload(ids, { signal?, concurrency? }?): Promise<readonly ImpulseResponse[]>evict(id): booleancacheStats(): { entries, bytes, hits, misses }
AudioDecoder
(bytes: Uint8Array, opts?: { signal?: AbortSignal }) => Promise<DecodedAudioLike>.
Implementation Notes
The OTel span for every load is started before the cache hit-check so
even cache hits emit a span; the outcome=cache_hit histogram label
distinguishes those from the outcome=success "did real work" path. The
SuaveplanError-cast on errors lets the loader's errors_total counter
attribute the throw to its originating namespace (AUDIO_IR.*,
STORAGE_URI.*, BUNDLE_URI.*, FALLBACK_URI.*, AUDIO_DECODE.<FMT>.*,
or AUDIO_IR_LOADER.*) rather than collapsing every miss into a single
opaque bucket. The pre-abort check throws synchronously before any
registry / resolver / decoder call, satisfying the spec's "never invoked
when signal is already aborted" requirement.