4.0 KiB
Add MutableUriPluginRegistrar to @suaveplan/audio-ir-core
Package: @suaveplan/audio-ir-core
Status: PROPOSED
Type: feature
Why
Stage-3 ship of @suaveplan/audio-ir-room-plugin-pool discovered that StorageUriResolver is frozen at construction — there is no resolver.use(plugin) after construction. Pool worked around this by introducing a tiny collector interface, locally:
interface MutableUriPluginRegistrar {
use(plugin: UriPlugin): void;
}
Consumers pre-construct a collector (typically const ps: UriPlugin[] = []; const target = { use: (p) => ps.push(p) };), call every IR content package's register(registry, target), then pass ps to createStorageUriResolver(...) once. This is the canonical pattern for the whole audio-ir-{family}-plugin-<name> family.
audio-ir-room-plugin-club shipped with a different shape (separate createPlugin() export + register(registry, resolver) with a structural resolver.hasScheme("bundle") check). Two divergent shapes for the same job will balloon as we add more content packages, so the registrar interface must live in one place: this foundation package. A small createUriPluginCollector() helper makes the pattern obvious at call sites.
What changes
- ADDED:
MutableUriPluginRegistrarinterface —{ use(plugin: UriPlugin): void }. Exported from@suaveplan/audio-ir-core(and re-exported from the root barrel). - ADDED:
createUriPluginCollector()factory returningMutableUriPluginRegistrar & { readonly plugins: readonly UriPlugin[] }. Wraps an internal array;use()pushes,pluginsexposes a frozen snapshot forcreateStorageUriResolver. - ADDED: dep on
@suaveplan/storage-urifor theUriPlugintype import (currently absent —audio-ir-corehas nostorage-uridep today; this is the first edge). Universal-tier compatible. - MODIFIED: package
README.md— new## Composing IR content packagessection documenting the collector pattern.
Out of scope
- Refactoring
audio-ir-room-plugin-cluband-poolto use the new interface — those are separate per-package changes (align-audio-ir-room-plugin-{club,pool}-register-signature) per AGENTS §193. - Any change to the
IrRegistryshape,IrManifestschema, or error codes.
Source mapping
Genesis-original — derived from the as-shipped MutableUriPluginRegistrar interface in packages/audio-ir/audio-ir-room-plugin-pool/src/register/register.ts. The interface body moves into audio-ir-core; pool then imports it.
Requirements with no source line
| Requirement | Justification |
|---|---|
| every requirement | Promotion of an in-tree pattern to its foundation |
Verification approach
src/registrar/registrar.test.ts—createUriPluginCollector()returns an object satisfyingMutableUriPluginRegistrar;use()pushes;pluginsis a frozen array snapshot; multipleuse()calls preserve order.src/registrar/registrar.spec-conformance.test.ts— type-level: anyaudio-ir-{family}-plugin-<name>register(registry, target, options?)accepts the collector verbatim (asserted viaexpectTypeOf).
Impact
Affected dependents
Run bunx turbo ls --filter=...@suaveplan/audio-ir-core to enumerate. Today's dependents:
@suaveplan/audio-ir-loader— [x] no action needed (reason: loader doesn't compose URI plugins at register time, only consumes them via a pre-built resolver; the new interface doesn't intersect its surface)@suaveplan/audio-ir-room-plugin-club— [ ] tests run / [ ] docs reviewed / [ ] no action needed (separatealign-audio-ir-room-plugin-club-register-signaturechange refactors it to use this new interface)@suaveplan/audio-ir-room-plugin-pool— [ ] tests run / [ ] docs reviewed / [ ] no action needed (separatealign-audio-ir-room-plugin-pool-register-signaturechange switches its local-copy interface to import this one)
Both content-package changes block on this one. They WILL be run as part of their own gates; this change ships independently.