{"_id":"@bazariodev/fsm-effects","_rev":"2-8cb1fff1cd4e3e64030594eedae1c157","name":"@bazariodev/fsm-effects","dist-tags":{"latest":"1.0.0"},"versions":{"0.1.0":{"name":"@bazariodev/fsm-effects","version":"0.1.0","keywords":["fsm","finite-state-machine","state-machine","effects","typescript","realtime","telephony","sip","ucaas"],"author":{"name":"Bazario"},"license":"MIT","_id":"@bazariodev/fsm-effects@0.1.0","maintainers":[{"name":"bazario","email":"bazario.dev@gmail.com"}],"homepage":"https://github.com/Bazariodev/bazario-labs/tree/main/packages/fsm-effects#readme","bugs":{"url":"https://github.com/Bazariodev/bazario-labs/issues"},"dist":{"shasum":"0af1897a76d1dc25744e95202aa851fef299319b","tarball":"https://registry.npmjs.org/@bazariodev/fsm-effects/-/fsm-effects-0.1.0.tgz","fileCount":9,"integrity":"sha512-QxYwURJCoflFKHKrlPaJmAjWu+PAmMQj40CoFXSUcLeplRw7FuoV4xWO+dQZc0iKIz7dKegwFFdkhNZvLzEYXw==","signatures":[{"sig":"MEYCIQCE45iUED6/AREXLqh6BIuwmVLWXac/bjpqSSkutsBeYgIhAJzrxLaKa1ddi6fC5s+fugrmazsqXKCTC9kJ/+rKMbyD","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":43853},"main":"./dist/index.cjs","type":"module","_from":"file:bazariodev-fsm-effects-0.1.0.tgz","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"bazario","email":"bazario.dev@gmail.com"},"_resolved":"/private/var/folders/xk/pm_239nx0cjgz296_sbs_f1c0000gn/T/ebef2eff281cde95e997b44c6449b042/bazariodev-fsm-effects-0.1.0.tgz","_integrity":"sha512-QxYwURJCoflFKHKrlPaJmAjWu+PAmMQj40CoFXSUcLeplRw7FuoV4xWO+dQZc0iKIz7dKegwFFdkhNZvLzEYXw==","repository":{"url":"git+https://github.com/Bazariodev/bazario-labs.git","type":"git","directory":"packages/fsm-effects"},"_npmVersion":"11.5.1","description":"Effects runner for @bazariodev/fsm — state-entry effects with cancellation, cleanup, and re-entrant send support for realtime communication workflows.","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@bazariodev/fsm":"^0.1.0"},"peerDependencies":{"@bazariodev/fsm":"^0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/fsm-effects_0.1.0_1780235667586_0.71747807535873","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@bazariodev/fsm-effects","version":"1.0.0","description":"Effects runner for @bazariodev/fsm — state-entry effects with cancellation, cleanup, and re-entrant send support for realtime communication workflows.","license":"MIT","author":{"name":"Bazario"},"repository":{"type":"git","url":"git+https://github.com/Bazariodev/bazario-labs.git","directory":"packages/fsm-effects"},"homepage":"https://github.com/Bazariodev/bazario-labs/tree/main/packages/fsm-effects#readme","bugs":{"url":"https://github.com/Bazariodev/bazario-labs/issues"},"keywords":["fsm","finite-state-machine","state-machine","effects","typescript","realtime","telephony","sip","ucaas"],"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"sideEffects":false,"peerDependencies":{"@bazariodev/fsm":"^1.0.0"},"devDependencies":{"@bazariodev/fsm":"^1.0.0"},"publishConfig":{"access":"public"},"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit"},"_id":"@bazariodev/fsm-effects@1.0.0","_integrity":"sha512-/D3cZZDEDBbmBLYl3GPKDLadOBiUQUww6i4Shb1aZG+dMkbg1xZsy/LlT0Xl4vO3jeopgcoiRWMYNfpo5pycBg==","_resolved":"/private/var/folders/xk/pm_239nx0cjgz296_sbs_f1c0000gn/T/02807a61d30de6e524fd73794532645f/bazariodev-fsm-effects-1.0.0.tgz","_from":"file:bazariodev-fsm-effects-1.0.0.tgz","_nodeVersion":"24.7.0","_npmVersion":"11.5.1","dist":{"integrity":"sha512-/D3cZZDEDBbmBLYl3GPKDLadOBiUQUww6i4Shb1aZG+dMkbg1xZsy/LlT0Xl4vO3jeopgcoiRWMYNfpo5pycBg==","shasum":"0432cd7dde023e3aa8ac0d1c7f5c5cfd226f0818","tarball":"https://registry.npmjs.org/@bazariodev/fsm-effects/-/fsm-effects-1.0.0.tgz","fileCount":9,"unpackedSize":47019,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCZmuvxx8rJ2opGF5vZgqA4zDbDursDlDycWfGBZRtY/wIgI84lW3gfrAhKzCy49oSFcHNblzucuFcPjtVe1Svxw1M="}]},"_npmUser":{"name":"bazario","email":"bazario.dev@gmail.com"},"directories":{},"maintainers":[{"name":"bazario","email":"bazario.dev@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/fsm-effects_1.0.0_1783331031101_0.21828653633688622"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-31T13:54:27.400Z","modified":"2026-07-06T09:43:51.355Z","0.1.0":"2026-05-31T13:54:27.739Z","1.0.0":"2026-07-06T09:43:51.223Z"},"bugs":{"url":"https://github.com/Bazariodev/bazario-labs/issues"},"author":{"name":"Bazario"},"license":"MIT","homepage":"https://github.com/Bazariodev/bazario-labs/tree/main/packages/fsm-effects#readme","keywords":["fsm","finite-state-machine","state-machine","effects","typescript","realtime","telephony","sip","ucaas"],"repository":{"type":"git","url":"git+https://github.com/Bazariodev/bazario-labs.git","directory":"packages/fsm-effects"},"description":"Effects runner for @bazariodev/fsm — state-entry effects with cancellation, cleanup, and re-entrant send support for realtime communication workflows.","maintainers":[{"name":"bazario","email":"bazario.dev@gmail.com"}],"readme":"# @bazariodev/fsm-effects\n\nEffects runner for [`@bazariodev/fsm`](https://github.com/Bazariodev/bazario-labs/tree/main/packages/fsm). Runs declarative state-entry effects with `AbortSignal` cancellation, sync or async bodies, optional cleanup callbacks, and a guarded `send` for driving the machine from inside an effect.\n\nFull design rationale: [`Effects.md` ADR](https://github.com/Bazariodev/bazario-labs/blob/main/.agent/ADR/Modules/Effects.md).\n\n## Install\n\n```sh\npnpm add @bazariodev/fsm-effects @bazariodev/fsm\n```\n\n`@bazariodev/fsm` is a peer dependency.\n\n## Usage\n\n```ts\nimport { FsmEffects } from '@bazariodev/fsm-effects';\n\nconst effects = new FsmEffects(callMachine, {\n  effects: {\n    // sync body + cleanup: cleanup runs when the state is left\n    ringing: () => {\n      const tone = startRingback();\n      return () => tone.stop();\n    },\n    // async body: abort the work when the state is left via `signal`\n    dialing: async (snapshot, { signal, send }) => {\n      const res = await fetch('/invite', { signal });\n      send(res.ok ? { type: 'ANSWERED' } : { type: 'FAILED' });\n    },\n    // interval driven by `send`; cleanup clears it on leave\n    connected: (snapshot, { send }) => {\n      const id = setInterval(() => send({ type: 'PING' }), 5_000);\n      return () => clearInterval(id);\n    },\n    // wildcard runs on every entry, after the state-specific effects\n    '*': (snapshot) => log(snapshot.value),\n  },\n});\n\neffects.stop(); // or `using effects = new FsmEffects(...)`\n```\n\nAn effect gets the committed `snapshot` and `{ signal, send }`. Return nothing, a cleanup function, or a promise of either. Multiple effects per state run in declaration order; state-specific effects run before `*`.\n\n## Design decisions\n\n- **Composition, not core.** The runner is a `subscribe` consumer — it never touches machine internals. The base core stays synchronous and effect-free, and effect bugs can't corrupt machine state.\n- **State-entry only (v1).** Effects attach to states, not transitions. Need transition awareness? Read `snapshot.previousValue` inside the effect.\n- **Cancellation.** One `AbortController` per active state. Leaving the state (or `stop()`) aborts its `signal`. A returned sync cleanup runs on abort; an async cleanup runs when its promise settles, even if abort already fired.\n- **Guarded `send`.** `api.send` no-ops once the signal is aborted, so effects needn't guard every call site. Machine errors raised by `send` surface at the effect's call site; if uncaught they're contained like any effect throw (logged, runner survives). No error event is auto-sent.\n\n### Re-entrancy: the drain loop\n\nA `send()` from inside an effect or a cleanup runs synchronously, so it can re-enter the runner before the current transition's work is done. The runner serializes all of it through a single drain loop: a nested `send()` only records the latest target and returns; the active loop owns every controller swap and spawn. This guarantees:\n\n- **all leaving-state cleanups finish before any entered-state effect runs** — they never interleave;\n- **pass-through states are skipped** — in a synchronous `A → B → C` cascade only the resting state's (`C`) effects spawn;\n- **stale callbacks are dropped** via a monotonic version guard.\n\nSelf-transitions (`A → A`) don't restart effects. They're detected by comparing the incoming snapshot's value against `#processedState`, a notification tracker updated when each non-self snapshot is observed — kept deliberately separate from the per-turn `AbortController`, so the provisional controller swap never influences a self-transition decision.\n\n### Error policy\n\n- sync throw from an effect → logged at `error`, contained, siblings still run;\n- async rejection → `error`, or `debug` if the signal already aborted (e.g. a wrapped `fetch` `AbortError`);\n- cleanup throw → logged, doesn't block other cleanups;\n- nothing is auto-sent — `send({ type: 'EFFECT_FAILED' })` from your own `catch` if you want that.\n\n### `stop()`\n\nAborts the current controller (running its cleanups), unsubscribes, and ignores later transitions. Idempotent. `[Symbol.dispose]` aliases it for `using`.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}