3.1 KiB
BindingRegistry
(no submodules)
Purpose
BindingRegistry is the discriminated dispatcher that links every bound
control on a surface to the consumer-side logic that actually performs the
side-effect. Atomic controls call registry.dispatch(binding, value)
whenever a user-initiated onChange fires; the registry resolves the
binding's kind and invokes the registered handler.
The constructor accepts a partial handler table. Any kind left unset falls
back to the built-in default handler, which is the appropriate consumer
hook adapter (for pluginParam, transport, midiOut, midiIn) or a
working fan-out (for compound). Every leaf default throws
REACT_CONTROL_SURFACE.BINDING_HANDLER_MISSING until a consumer registers
production wiring — silently dropping a control update is never
acceptable.
Features
- Closed kind dispatch for all six
Binding["kind"]values. - Default compound fan-out via
defaultCompoundHandler. - Per-kind override via the constructor or
withHandler(kind, handler)builder. - Type-narrowed handler signatures so consumers can't accidentally cross binding kinds.
Usage: Basic
import { BindingRegistry } from "@suaveplan/react-control-surface/runtime/binding-registry";
const registry = new BindingRegistry({
pluginParam: (binding, value) =>
chain.get(binding.pluginId)?.setParameter(binding.paramPath, value),
});
registry.dispatch(binding, knob.value);
Usage: Advanced
import { BindingRegistry } from "@suaveplan/react-control-surface/runtime/binding-registry";
const base = new BindingRegistry({
midiOut: (binding, value) =>
output.sendMessage({
kind: "controlChange",
channel: binding.channel,
controller: binding.cc,
value: Math.round(value * 127),
}),
});
// Layer a recording spy on top of the production handler for an audit run.
const spy = new BindingRegistry();
const audited = base.withHandler("pluginParam", (binding, value) => {
audit.push({ binding, value });
spy.dispatch(binding, value);
});
API Reference
new BindingRegistry(table?: BindingHandlerTable)— constructor.registry.dispatch(binding: Binding, value: number): void— full dispatch, including compound fan-out.registry.dispatchLeaf(binding: LeafBinding, value: number): void— leaf-only dispatch; called by the default compound handler to avoid re-entering the compound check.registry.has(kind: BindingKind): boolean—truewhen a handler is registered.registry.withHandler(kind, handler)— returns a new registry with the given handler overridden.
Implementation Notes
The dispatcher is built around an exhaustive switch over the leaf
discriminator so adding a new binding kind is a compile-time fail — the
switch needs a new branch and BindingHandlerTable needs a new optional
slot. The compound default takes the owning registry as a parameter so it
can route per-leaf calls through dispatchLeaf, sidestepping a recursive
compound check that would otherwise be required by every test path.