first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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.