# `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 ```ts 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 ```ts 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` — `true` when 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.