# connection-fsm ## Purpose Builds the controller connection-state machine consumed by every `MidiControllerPlugin`. The FSM models the lifecycle of a hardware controller's MIDI access: a fresh controller starts `disconnected`, moves through `connecting`, settles in `connected`, and falls into `error` on any access failure or rejected transition. Because the FSM is driven from both the adapter (real connect/disconnect) and from `handleStateChange` (hot-plug events from `navigator.requestMIDIAccess().onstatechange`-shaped sources), centralising the rules here keeps every device subclass ignorant of state-machine details. ## Features - `createConnectionMachine()` returns an `@suaveplan/fsm` interpreter with the four canonical states pre-wired. - `sendOrThrow(machine, event)` converts the FSM's underlying `INVALID_TRANSITION` into a domain-stable `AudioControllersError("AUDIO_CONTROLLERS.INVALID_STATE")` with the offending `from` and `event` carried in `context`. - Transition table mirrors the spec exactly: `DISCONNECT`, `CONNECT`, `CONNECTED`, `ERROR`, `RESET`. ## Usage: Basic ```ts import { createConnectionMachine, sendOrThrow } from "@suaveplan/audio-controllers"; const m = createConnectionMachine(); sendOrThrow(m, "CONNECT"); sendOrThrow(m, "CONNECTED"); ``` ## Usage: Advanced ```ts const m = createConnectionMachine(); m.onTransition((state) => log.debug({ value: state.value }, "controller-fsm")); try { sendOrThrow(m, "CONNECTED"); // illegal from `disconnected` } catch (err) { if (err instanceof AudioControllersError) { expect(err.code).toBe(AUDIO_CONTROLLERS_CODES.INVALID_STATE); } } ``` ## API Reference - `createConnectionMachine()` → `FSMInterpreter<{ lastError?: string }>`. - `sendOrThrow(machine, event)` → `void`. - `ConnectionMachine` — re-exported interpreter type alias. ## Implementation Notes A local `TRANSITIONS` table is consulted before delegating to the FSM's own dispatcher so the package surface throws an `AUDIO_CONTROLLERS.*` error rather than the raw `FSM.INVALID_TRANSITION` code. The state graph deliberately omits transitions out of `error` other than `RESET` to force callers to explicitly acknowledge recovery — accidental re-`CONNECT` from `error` would mask the original failure.