{"_id":"@bazariodev/fsm-hierarchy","_rev":"3-c3777edd8966b6ff70970ff98813e471","name":"@bazariodev/fsm-hierarchy","dist-tags":{"latest":"1.1.0"},"versions":{"1.0.0":{"name":"@bazariodev/fsm-hierarchy","version":"1.0.0","keywords":["fsm","finite-state-machine","state-machine","hierarchy","typescript","realtime","telephony","sip","ucaas"],"author":{"name":"Bazario"},"license":"MIT","_id":"@bazariodev/fsm-hierarchy@1.0.0","maintainers":[{"name":"bazario","email":"bazario.dev@gmail.com"}],"homepage":"https://github.com/Bazariodev/bazario-labs/tree/main/packages/fsm-hierarchy#readme","bugs":{"url":"https://github.com/Bazariodev/bazario-labs/issues"},"dist":{"shasum":"da66359edee1759f7937388c1361ea151f70d6eb","tarball":"https://registry.npmjs.org/@bazariodev/fsm-hierarchy/-/fsm-hierarchy-1.0.0.tgz","fileCount":9,"integrity":"sha512-lfduQZtYmr5BtvFzig8FnbfOCBoS6xJ0txLxhSf7K78RjBekGn4FmGu2hxgj57bPP0/p7HbTntTcaKbnVoIIBQ==","signatures":[{"sig":"MEUCIAntooMm80e0cuYHSB3+JbZTlkXe6OtJR7KjKl9QIuSJAiEAlrM8TfEcWBuX7Nv1fKgjj70TV4HUxsunxJunweuIuNM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":138952},"main":"./dist/index.cjs","type":"module","_from":"file:bazariodev-fsm-hierarchy-1.0.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/fb8ee25934647f6585eec11f6c8cba17/bazariodev-fsm-hierarchy-1.0.0.tgz","_integrity":"sha512-lfduQZtYmr5BtvFzig8FnbfOCBoS6xJ0txLxhSf7K78RjBekGn4FmGu2hxgj57bPP0/p7HbTntTcaKbnVoIIBQ==","repository":{"url":"git+https://github.com/Bazariodev/bazario-labs.git","type":"git","directory":"packages/fsm-hierarchy"},"_npmVersion":"11.5.1","description":"Hierarchical composition layer for @bazariodev/fsm with post-commit child reconciliation and bubbling event routing.","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@bazariodev/fsm":"^1.0.0","@bazariodev/fsm-effects":"^1.0.0"},"peerDependencies":{"@bazariodev/fsm":"^1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/fsm-hierarchy_1.0.0_1783331025678_0.03926428379025304","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@bazariodev/fsm-hierarchy","version":"1.0.1","keywords":["fsm","finite-state-machine","state-machine","hierarchy","typescript","realtime","telephony","sip","ucaas"],"author":{"name":"Bazario"},"license":"MIT","_id":"@bazariodev/fsm-hierarchy@1.0.1","maintainers":[{"name":"bazario","email":"bazario.dev@gmail.com"}],"homepage":"https://github.com/Bazariodev/bazario-labs/tree/main/packages/fsm-hierarchy#readme","bugs":{"url":"https://github.com/Bazariodev/bazario-labs/issues"},"dist":{"shasum":"57d1d911619d6b85c4261c4e70b23923d967412b","tarball":"https://registry.npmjs.org/@bazariodev/fsm-hierarchy/-/fsm-hierarchy-1.0.1.tgz","fileCount":9,"integrity":"sha512-rGj23GunMyKYyyhqVHz9sQ8Q8uWRGTxiNazmX53FRTYsQuYZypnvP1p36XxeYtkku4jfOv90hoYy5vLkyM6rTA==","signatures":[{"sig":"MEUCIH2yTQfPdYa+3sQ5hRURpidClrJ3BgFl/HNJ4Lpok84TAiEA/7Ol2phuLPjvAIrXHcsNzRvLRWIrrvLiIz+BH0CPPBI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIA+4NAHiXJM+cRfCXCBDYrZ9taoPI4CvX/mwsuTs8lxuAiEArb4aTIKxNupt90nOM+hqnO8Ni6S5UIUXKk8C9WMZXn8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":139196},"main":"./dist/index.cjs","type":"module","_from":"file:bazariodev-fsm-hierarchy-1.0.1.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/2745bc98dd9d64de437d046256ca5611/bazariodev-fsm-hierarchy-1.0.1.tgz","_integrity":"sha512-rGj23GunMyKYyyhqVHz9sQ8Q8uWRGTxiNazmX53FRTYsQuYZypnvP1p36XxeYtkku4jfOv90hoYy5vLkyM6rTA==","repository":{"url":"git+https://github.com/Bazariodev/bazario-labs.git","type":"git","directory":"packages/fsm-hierarchy"},"_npmVersion":"11.5.1","description":"Hierarchical composition layer for @bazariodev/fsm with post-commit child reconciliation and bubbling event routing.","directories":{},"sideEffects":false,"_nodeVersion":"24.7.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@bazariodev/fsm":"^1.1.0","@bazariodev/fsm-effects":"^1.1.0"},"peerDependencies":{"@bazariodev/fsm":"^1.1.0"},"_npmOperationalInternal":{"tmp":"tmp/fsm-hierarchy_1.0.1_1790108010504_0.36020306812884995","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"_id":"@bazariodev/fsm-hierarchy@1.1.0","bugs":{"url":"https://github.com/Bazariodev/bazario-labs/issues"},"dist":{"shasum":"578df82c783b1648fdc85e4b191475653724a830","tarball":"https://registry.npmjs.org/@bazariodev/fsm-hierarchy/-/fsm-hierarchy-1.1.0.tgz","fileCount":9,"integrity":"sha512-eJnTkusrSchAnmbi9xJsyPp/d01pClH25WAkW/YzwH/oO06SGWwQ4rWKwXeIw8l9C77P2qGq1YGQllhNG1OtXw==","signatures":[{"sig":"MEUCIQDr/9l3FckKOjDPuaY7sIvHGkw9EQqzh+f+2LvmvnIfaAIgL3pIfjCPdIF+J6JiHQGWDhc1IpmRJ7BYMK85QjxhZ3I=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDntY5o03liIODh1I00YuUFMYQg3/r6hz8caAo4TA4u4AIhAIayCMqLTJQkgtYWc8z6Baz+lymtnEcRRe9QL7b3iMXt"}],"unpackedSize":105895},"main":"./dist/index.cjs","name":"@bazariodev/fsm-hierarchy","type":"module","_from":"file:bazariodev-fsm-hierarchy-1.1.0.tgz","types":"./dist/index.d.ts","author":{"name":"Bazario"},"module":"./dist/index.js","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}}},"license":"MIT","scripts":{"test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest"},"version":"1.1.0","_npmUser":{"name":"bazario","email":"bazario.dev@gmail.com"},"homepage":"https://github.com/Bazariodev/bazario-labs/tree/main/packages/fsm-hierarchy#readme","keywords":["fsm","finite-state-machine","state-machine","hierarchy","typescript","realtime","telephony","sip","ucaas"],"_resolved":"/private/var/folders/xk/pm_239nx0cjgz296_sbs_f1c0000gn/T/3019b47eb360518bc2fe17441afff0a8/bazariodev-fsm-hierarchy-1.1.0.tgz","_integrity":"sha512-eJnTkusrSchAnmbi9xJsyPp/d01pClH25WAkW/YzwH/oO06SGWwQ4rWKwXeIw8l9C77P2qGq1YGQllhNG1OtXw==","repository":{"url":"git+https://github.com/Bazariodev/bazario-labs.git","type":"git","directory":"packages/fsm-hierarchy"},"_npmVersion":"11.5.1","description":"Hierarchical composition layer for @bazariodev/fsm with post-commit child reconciliation and bubbling event routing.","directories":{},"maintainers":[{"name":"bazario","email":"bazario.dev@gmail.com"}],"sideEffects":false,"_nodeVersion":"24.7.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@bazariodev/fsm":"^1.1.0","@bazariodev/fsm-effects":"^2.0.0"},"peerDependencies":{"@bazariodev/fsm":"^1.1.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/fsm-hierarchy_1.1.0_1790588778856_0.9030522258809948"}}},"time":{"created":"2026-07-06T09:43:45.529Z","modified":"2026-09-28T09:46:19.090Z","1.0.0":"2026-07-06T09:43:45.823Z","1.0.1":"2026-09-22T20:13:30.612Z","1.1.0":"2026-09-28T09:46:18.939Z"},"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-hierarchy#readme","keywords":["fsm","finite-state-machine","state-machine","hierarchy","typescript","realtime","telephony","sip","ucaas"],"repository":{"url":"git+https://github.com/Bazariodev/bazario-labs.git","type":"git","directory":"packages/fsm-hierarchy"},"description":"Hierarchical composition layer for @bazariodev/fsm with post-commit child reconciliation and bubbling event routing.","maintainers":[{"name":"bazario","email":"bazario.dev@gmail.com"}],"readme":"# @bazariodev/fsm-hierarchy\n\nHierarchical composition layer for [`@bazariodev/fsm`](https://github.com/Bazariodev/bazario-labs/tree/main/packages/fsm). Orchestrates an active spine of flat machines with post-commit child reconciliation, deepest-first routing with bubbling, routed node handles, and composed snapshots.\n\nFull design rationale: [`Hierarchy.md` ADR](https://github.com/Bazariodev/bazario-labs/blob/main/.agent/ADR/Modules/Hierarchy.md).\n\n## Install\n\n```sh\npnpm add @bazariodev/fsm-hierarchy @bazariodev/fsm\n```\n\n`@bazariodev/fsm` is a peer dependency.\n\n## Usage\n\n```ts\nimport { FsmHierarchy } from '@bazariodev/fsm-hierarchy';\n\nconst call = new FsmHierarchy({\n  name: 'call',\n  initial: 'idle',\n  context: {},\n  states: {\n    idle: {},\n    connected: {},\n    failed: {},\n  },\n  transitions: {\n    idle: { CONNECT: { target: 'connected' } },\n    connected: {},\n    failed: {},\n    '*': { HANGUP: { target: 'failed' } },\n  },\n  children: {\n    connected: {\n      name: 'connected-flow',\n      initial: 'active',\n      context: {},\n      states: {\n        active: {},\n        muted: {},\n      },\n      transitions: {\n        active: { MUTE: { target: 'muted' } },\n        muted: { UNMUTE: { target: 'active' } },\n        '*': {},\n      },\n    },\n  },\n});\n\ncall.send({ type: 'CONNECT' });\ncall.snapshot.path; // \"connected.active\"\n\ncall.send({ type: 'MUTE' });\ncall.matches('connected'); // true\ncall.snapshot.path; // \"connected.muted\"\n\ncall.send({ type: 'HANGUP' }); // bubbles to root\ncall.snapshot.path; // \"failed\"\n```\n\n## Design decisions\n\n- **Composition, not core.** Every node is backed by a normal `Fsm`; the hierarchy owns routing, child lifecycle, and snapshot composition.\n- **Post-commit reconciliation.** A routed handle calls the underlying node `send()`, compares the node version before/after, then reconciles children after a successful commit.\n- **Active spine only.** v1 supports one child region per active state. No parallel regions, history, explicit cross-boundary targets, or root-priority events.\n- **Innermost-first routing.** Events start at the active leaf. If that node cannot handle the event, routing bubbles upward until a node accepts it or the event is rejected.\n- **Routed node handles.** `nodeFor(path)` and `onNodeSpawned` expose `FsmCore`-compatible handles. Effects/delays can attach to those handles and still send through hierarchy bubbling.\n- **Affected notifications only.** Node-handle subscribers run only for the node that accepted the transition and nodes newly spawned by reconciliation; unchanged active ancestors are not notified.\n- **Ordered delivery.** If a subscriber sends again, the resulting snapshots are published synchronously, before the outer pass finishes. The outer pass then skips anything older, so neither a node handle nor the hierarchy ever delivers an older snapshot after a newer one. Every node still ends on its latest snapshot; an intermediate snapshot may be skipped. This is the same rule as core `Fsm`.\n- **Eager validation.** The full config tree is validated at construction, so a bad child config fails immediately rather than from the send that first enters it. It covers the state graph (non-empty names, no empty, `.`, or `*` state names, a declared string initial state, child keys that reference declared states, transition sources and targets that exist) and anything that would break a later spawn or silently disable behavior: state definitions and child configs must be objects, `onEnter`/`onLeave` must be functions, and `states`, `transitions`, `children`, and event maps must be plain objects (prototype `Object.prototype` or `null`; arrays, `Map`s, `Date`s, and other class instances are rejected). Guard and reducer types are left to TypeScript; a bad one throws from the send that uses it. Only own `children` entries participate in reconciliation.\n- **No sends during reconciliation.** `subscribe()` is allowed from `onNodeSpawned`, but synchronous `send()` through the hierarchy or any node handle is rejected until reconciliation finishes.\n\n## API\n\n```ts\nclass FsmHierarchy<TEvent extends FsmEvent> {\n  constructor(\n    config: AnyHierarchyConfig<TEvent>,\n    options?: {\n      logger?: Logger;\n      onNodeSpawned?: (node: HierarchyNode<TEvent>) => void | (() => void);\n    },\n  );\n\n  readonly snapshot: HierarchySnapshot;\n  send(event: TEvent): void;\n  can(event: TEvent): boolean;\n  matches(path: string): boolean;\n  subscribe(listener: (snapshot: HierarchySnapshot) => void): Unsubscribe;\n  nodeFor(path: string): FsmCore<string, TEvent, unknown> | undefined;\n  stop(): void;\n  [Symbol.dispose](): void;\n}\n```\n\n```ts\ntype HierarchyNode<TEvent extends FsmEvent> = Readonly<{\n  path: string;\n  handle: FsmCore<string, TEvent, unknown>;\n}>;\n```\n\n`onNodeSpawned` receives a routed `handle`, not the raw internal `Fsm`. `nodeFor(path)` returns the same kind of routed handle for active paths and `undefined` for inactive, unknown, or disposed paths.\n\nRouted handles intentionally differ from a raw `FsmCore` in three places: `send` bubbles from that node toward the root, disposed handles are inert, and subscribers registered during `onNodeSpawned` receive the spawned node's version-0 birth snapshot after reconciliation completes.\n\n`stop()` disposes the active spine deepest-first, runs returned `onNodeSpawned` cleanups best-effort, marks handles inert, and clears subscribers.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}