first commit
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# `BindingRegistry`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user