{"_id":"@cognitive-fab/sam-fsm","_rev":"5-faf8147d53d7b91d2832a614037905b3","name":"@cognitive-fab/sam-fsm","dist-tags":{"latest":"2.1.1"},"versions":{"1.0.0":{"name":"@cognitive-fab/sam-fsm","version":"1.0.0","keywords":["SAM","Pattern","State","Management","FSM","Finite","Machine"],"author":{"name":"Jean-Jacques Dubray"},"license":"ISC","_id":"@cognitive-fab/sam-fsm@1.0.0","maintainers":[{"name":"jdubray","email":"jdubray@gmail.com"}],"homepage":"https://github.com/jdubray/sam-fsm#readme","bugs":{"url":"https://github.com/jdubray/sam-fsm/issues"},"dist":{"shasum":"36a5eab2b17faa56580f9daef5f23f210cbf558d","tarball":"https://registry.npmjs.org/@cognitive-fab/sam-fsm/-/sam-fsm-1.0.0.tgz","fileCount":5,"integrity":"sha512-Eh8xjWYTWLz9TvJtyJ+SkM4WDQy80OaZEqRnXeb7Kic+fIXz46EcDVlMBj5lhpjfFV7yE7XPLHFPSkVD3527zA==","signatures":[{"sig":"MEUCIQDBLDvVEywEbsW+cWAo87nlk1r7QfR+Z5uFuUO6aVrc7wIgPfmkfGCdqt6Rn2bYNs/hV7wt/sI3VUfGeQ82QDS44hY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":48526},"main":"./dist/fsm.js","unpkg":"dist/fsm.js","gitHead":"fd299eafaceea70022a806e29a45d84648ea5a7b","scripts":{"test":"node node_modules/mocha/bin/mocha.js --require @babel/register","build":"rollup --bundleConfigAsCjs -c rollup.config.js","minify":"minify ./dist/fsm.js -d dist"},"_npmUser":{"name":"jdubray","email":"jdubray@gmail.com"},"repository":{"url":"git+ssh://git@github.com/jdubray/sam-fsm.git","type":"git"},"_npmVersion":"11.11.0","description":"Finite State Machine library for the SAM Pattern","directories":{},"_nodeVersion":"20.19.6","_hasShrinkwrap":false,"devDependencies":{"chai":"^4.2.0","mocha":"^11.7.5","eslint":"^6.0.1","rollup":"^4.60.0","nodemon":"^3.1.14","uglify-es":"^3.3.9","@babel/cli":"^7.4.4","@babel/core":"^7.4.5","@babel/node":"^7.4.5","babel-eslint":"^10.0.2","babel-minify":"^0.5.1","@babel/runtime":"^7.4.5","@babel/register":"^7.4.4","@babel/preset-env":"^7.4.5","eslint-config-babel":"^9.0.0","eslint-plugin-react":"^7.14.2","@rollup/plugin-babel":"^7.0.0","eslint-config-airbnb":"^17.1.1","eslint-plugin-import":"^2.18.0","eslint-config-prettier":"^6.0.0","eslint-plugin-jsx-a11y":"^6.2.3","@rollup/plugin-commonjs":"^29.0.2","@cognitive-fab/sam-pattern":"^1.6.0","@rollup/plugin-node-resolve":"^16.0.3","@babel/plugin-transform-runtime":"^7.4.4","@babel/plugin-transform-modules-umd":"^7.2.0","@babel/plugin-transform-optional-chaining":"^7.28.6","@babel/plugin-transform-async-to-generator":"^7.4.4","@babel/plugin-transform-nullish-coalescing-operator":"^7.28.6"},"_npmOperationalInternal":{"tmp":"tmp/sam-fsm_1.0.0_1774700731418_0.4286534139342857","host":"s3://npm-registry-packages-npm-production"}},"2.0.0":{"name":"@cognitive-fab/sam-fsm","version":"2.0.0","keywords":["SAM","Pattern","State","Management","FSM","Finite","Machine"],"author":{"name":"Jean-Jacques Dubray"},"license":"ISC","_id":"@cognitive-fab/sam-fsm@2.0.0","maintainers":[{"name":"jdubray","email":"jdubray@gmail.com"},{"name":"cognitivefab","email":"cto@cognitivefab.com"}],"homepage":"https://github.com/jdubray/sam-fsm#readme","bugs":{"url":"https://github.com/jdubray/sam-fsm/issues"},"dist":{"shasum":"a82bb436a2e80d31a744ab389370a0bcc0962a5f","tarball":"https://registry.npmjs.org/@cognitive-fab/sam-fsm/-/sam-fsm-2.0.0.tgz","fileCount":7,"integrity":"sha512-phdSFEmxLeQe597rQnFYp+0ZSrN6a/xg12JIg5is1yPMYKbrGGph+n1VOKzVLF7icL9GE2stammQmTxobho9Ug==","signatures":[{"sig":"MEYCIQDHA01dLjiIZXkxcxlFOGPWRxXD8lVFLFPlXuYukrmIBwIhALzJk4UFlgTiaP26jBZiRgp1/mD9h+HeWs71zcxGsZyl","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":60760},"main":"./dist/fsm.js","unpkg":"dist/fsm.js","gitHead":"9ed0d0f1c09d1c70fe9464389cb74541d11498de","scripts":{"test":"node node_modules/mocha/bin/mocha.js --require @babel/register","build":"rollup --bundleConfigAsCjs -c rollup.config.js","minify":"terser dist/fsm.js -o dist/fsm.js -c -m"},"_npmUser":{"name":"jdubray","email":"jdubray@gmail.com"},"overrides":{"diff":"^9.0.0","serialize-javascript":"^7.0.7"},"repository":{"url":"git+ssh://git@github.com/jdubray/sam-fsm.git","type":"git"},"_npmVersion":"11.12.1","description":"Finite State Machine library for the SAM Pattern","directories":{},"_nodeVersion":"24.12.0","_hasShrinkwrap":false,"devDependencies":{"chai":"^4.2.0","mocha":"^11.7.6","eslint":"^8.57.1","rollup":"^4.60.0","terser":"^5.49.0","nodemon":"^3.1.14","@babel/core":"^7.4.5","@babel/runtime":"^7.4.5","@babel/register":"^7.4.4","@babel/preset-env":"^7.4.5","eslint-config-babel":"^9.0.0","eslint-plugin-react":"^7.37.2","@rollup/plugin-babel":"^7.0.0","eslint-config-airbnb":"^19.0.4","eslint-plugin-import":"^2.31.0","eslint-config-prettier":"^6.0.0","eslint-plugin-jsx-a11y":"^6.10.2","@rollup/plugin-commonjs":"^29.0.2","@cognitive-fab/sam-pattern":"^2.0.0","@rollup/plugin-node-resolve":"^16.0.3","@babel/plugin-transform-runtime":"^7.4.4","@babel/plugin-transform-modules-umd":"^7.2.0","@babel/plugin-transform-optional-chaining":"^7.28.6","@babel/plugin-transform-async-to-generator":"^7.4.4","@babel/plugin-transform-nullish-coalescing-operator":"^7.28.6"},"_npmOperationalInternal":{"tmp":"tmp/sam-fsm_2.0.0_1783940359881_0.21446063581119446","host":"s3://npm-registry-packages-npm-production"}},"2.1.0":{"name":"@cognitive-fab/sam-fsm","version":"2.1.0","keywords":["SAM","Pattern","State","Management","FSM","Finite","Machine"],"author":{"name":"Jean-Jacques Dubray"},"license":"ISC","_id":"@cognitive-fab/sam-fsm@2.1.0","maintainers":[{"name":"jdubray","email":"jdubray@gmail.com"},{"name":"cognitivefab","email":"cto@cognitivefab.com"}],"homepage":"https://github.com/jdubray/sam-fsm#readme","bugs":{"url":"https://github.com/jdubray/sam-fsm/issues"},"dist":{"shasum":"7c36bf1e38219fc143c8fade729d3e418d07713e","tarball":"https://registry.npmjs.org/@cognitive-fab/sam-fsm/-/sam-fsm-2.1.0.tgz","fileCount":7,"integrity":"sha512-RgYYyPZHd2bnfnjcVu4DHuiwHPBz/Aak04jXaoJI5wnPCOhbkz7s7hf7XE/ZBcHk2hJtVLPFV+bsucBkO3bLgg==","signatures":[{"sig":"MEUCIEALljo97IgSAZlTiDP8PvmavvcOlyzkkkXNDIu4PZZVAiEAnsCOm2W2yS/BKovX/3ZD+0hszMeGhZDc8G7LmoL2wwk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":65567},"main":"./dist/fsm.js","unpkg":"dist/fsm.js","gitHead":"16fb38a93bbca287953768885c16c5fe799f8f9c","scripts":{"test":"node node_modules/mocha/bin/mocha.js --require @babel/register","build":"rollup --bundleConfigAsCjs -c rollup.config.js","minify":"terser dist/fsm.js -o dist/fsm.js -c -m"},"_npmUser":{"name":"jdubray","email":"jdubray@gmail.com"},"overrides":{"diff":"^9.0.0","serialize-javascript":"^7.0.7"},"repository":{"url":"git+ssh://git@github.com/jdubray/sam-fsm.git","type":"git"},"_npmVersion":"11.12.1","description":"Finite State Machine library for the SAM Pattern","directories":{},"_nodeVersion":"24.12.0","_hasShrinkwrap":false,"devDependencies":{"chai":"^4.2.0","mocha":"^11.7.6","eslint":"^8.57.1","rollup":"^4.60.0","terser":"^5.49.0","nodemon":"^3.1.14","@babel/core":"^7.4.5","@babel/runtime":"^7.4.5","@babel/register":"^7.4.4","@babel/preset-env":"^7.4.5","eslint-config-babel":"^9.0.0","eslint-plugin-react":"^7.37.2","@rollup/plugin-babel":"^7.0.0","eslint-config-airbnb":"^19.0.4","eslint-plugin-import":"^2.31.0","eslint-config-prettier":"^6.0.0","eslint-plugin-jsx-a11y":"^6.10.2","@rollup/plugin-commonjs":"^29.0.2","@cognitive-fab/sam-pattern":"^2.1.0","@rollup/plugin-node-resolve":"^16.0.3","@babel/plugin-transform-runtime":"^7.4.4","@babel/plugin-transform-modules-umd":"^7.2.0","@babel/plugin-transform-optional-chaining":"^7.28.6","@babel/plugin-transform-async-to-generator":"^7.4.4","@babel/plugin-transform-nullish-coalescing-operator":"^7.28.6"},"_npmOperationalInternal":{"tmp":"tmp/sam-fsm_2.1.0_1784727296095_0.7618198932743707","host":"s3://npm-registry-packages-npm-production"}},"2.1.1":{"name":"@cognitive-fab/sam-fsm","version":"2.1.1","description":"Finite State Machine library for the SAM Pattern","main":"./dist/fsm.js","scripts":{"build":"rollup --bundleConfigAsCjs -c rollup.config.js","test":"node node_modules/mocha/bin/mocha.js --require @babel/register","minify":"terser dist/fsm.js -o dist/fsm.js -c -m"},"repository":{"type":"git","url":"git+ssh://git@github.com/cognitive-fab/sam-fsm.git"},"keywords":["SAM","Pattern","State","Management","FSM","Finite","Machine"],"author":{"name":"Jean-Jacques Dubray"},"license":"ISC","bugs":{"url":"https://github.com/cognitive-fab/sam-fsm/issues"},"homepage":"https://github.com/cognitive-fab/sam-fsm#readme","devDependencies":{"@babel/core":"^7.4.5","@babel/plugin-transform-async-to-generator":"^7.4.4","@babel/plugin-transform-modules-umd":"^7.2.0","@babel/plugin-transform-nullish-coalescing-operator":"^7.28.6","@babel/plugin-transform-optional-chaining":"^7.28.6","@babel/plugin-transform-runtime":"^7.4.4","@babel/preset-env":"^7.4.5","@babel/register":"^7.4.4","@babel/runtime":"^7.4.5","@cognitive-fab/sam-pattern":"^2.1.0","@rollup/plugin-babel":"^7.0.0","@rollup/plugin-commonjs":"^29.0.2","@rollup/plugin-node-resolve":"^16.0.3","chai":"^4.2.0","eslint":"^8.57.1","eslint-config-airbnb":"^19.0.4","eslint-config-babel":"^9.0.0","eslint-config-prettier":"^6.0.0","eslint-plugin-import":"^2.31.0","eslint-plugin-jsx-a11y":"^6.10.2","eslint-plugin-react":"^7.37.2","mocha":"^11.7.6","nodemon":"^3.1.14","rollup":"^4.60.0","terser":"^5.49.0"},"unpkg":"dist/fsm.js","overrides":{"diff":"^9.0.0","serialize-javascript":"^7.0.7"},"gitHead":"9b596e5bb6483ca1420ea34d949899bc63449b65","_id":"@cognitive-fab/sam-fsm@2.1.1","_nodeVersion":"24.12.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-G2slydbmK8bb63c3LaRhUlswLkxZqL6jCBq9OoWDR7O0lGc+CvZMnXcoyCfILs1Ahel4KuUCftOAz1i13SK7MQ==","shasum":"bef73839db13ba8917214ca523367cb6421f2bb9","tarball":"https://registry.npmjs.org/@cognitive-fab/sam-fsm/-/sam-fsm-2.1.1.tgz","fileCount":7,"unpackedSize":66128,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCnhMEpzjmL4Oh560yoG2apnf4KNmTCxDIxD3m/MQ73+QIgClHhoO+7VKY+AajK4/Ko0c0Y0cfPJ2E5KzVkuOLkVqg="}]},"_npmUser":{"name":"jdubray","email":"jdubray@gmail.com"},"directories":{},"maintainers":[{"name":"jdubray","email":"jdubray@gmail.com"},{"name":"cognitivefab","email":"cto@cognitivefab.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sam-fsm_2.1.1_1785673850863_0.6432609297597645"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-28T12:25:31.329Z","modified":"2026-08-02T12:30:51.241Z","1.0.0":"2026-03-28T12:25:31.721Z","2.0.0":"2026-07-13T10:59:20.023Z","2.1.0":"2026-07-22T13:34:56.235Z","2.1.1":"2026-08-02T12:30:51.028Z"},"bugs":{"url":"https://github.com/cognitive-fab/sam-fsm/issues"},"author":{"name":"Jean-Jacques Dubray"},"license":"ISC","homepage":"https://github.com/cognitive-fab/sam-fsm#readme","keywords":["SAM","Pattern","State","Management","FSM","Finite","Machine"],"repository":{"type":"git","url":"git+ssh://git@github.com/cognitive-fab/sam-fsm.git"},"description":"Finite State Machine library for the SAM Pattern","maintainers":[{"name":"jdubray","email":"jdubray@gmail.com"},{"name":"cognitivefab","email":"cto@cognitivefab.com"}],"readme":"# SAM FSM Library\n\n`sam-fsm` is a companion library to [sam-pattern](https://www.npmjs.com/package/sam-pattern). It provides a finite state machine implementation on top of the [SAM Pattern](http://sam.js.org) (which is itself a robust state machine structure based on TLA+). `sam-fsm` supports both deterministic and non-deterministic state machines. Several FSMs can run concurrently in the same SAM instance, making it easy to build sophisticated applications with complex state management needs.\n\nThe two libraries combined let you use control states where they make sense without being forced to model your entire application as a single FSM. It is too cumbersome to specify a control state for every action — `sam-fsm` + `sam-pattern` solves that problem.\n\n## v2 — sam-pattern strict-profile support\n\nVersion 2.0 makes an fsm a first-class citizen of [sam-pattern 2.0's strict profile](https://github.com/cognitive-fab/sam-lib/blob/v2/docs/MIGRATION.md). `fsm()` now additionally returns:\n\n- **`modelShape`** (#1) — the fsm's own state declaration (`pc`, plus `pc_1` marked `internal`); spread it into a strict component the way you already spread `acceptors`\n- **`namedActions(creators?)`** (#2, #3) — the FSM alphabet as a v2 named-intent map with per-action `schema` and `domain` (defaults: empty-payload creator, permissive schema, `[[]]` domain), so a bare fsm passes `validate()` and is model-checkable by the sam-pattern `checker` with zero configuration\n\nand one new option:\n\n- **`rejectUnexpectedActions: true`** (#4) — on a v2 instance, an invalid transition presents and is `reject`ed with `unexpected action X for state: Y` (observable via `lastStep()` / `stepListener`) instead of landing in the `__error` slot; on a v1 instance it falls back to `__error`\n\n```js\nconst clock = fsm({ pc0: 'TICKED', actions: {...}, states: {...}, deterministic: true, enforceAllowedTransitions: true, rejectUnexpectedActions: true })\n\ninstance({\n  initialState: clock.initialState({}),\n  component: {\n    modelShape: clock.modelShape,\n    actions: clock.namedActions(),\n    acceptors: clock.acceptors,\n    reactors: clock.stateMachine\n  }\n})\n```\n\nAll v1 forms are unchanged.\n\n## Table of Contents\n- [Installation](#installation)\n  - [Node.js](#nodejs)\n  - [Browsers](#browsers)\n  - [Getting Started](#getting-started)\n- [Library](#library)\n  - [Constructor](#constructor)\n    - [Parameters](#parameters)\n  - [Integration with SAM](#integration-with-sam)\n    - [Next-Action Predicates](#next-action-predicates)\n    - [Transition Guards](#transition-guards)\n    - [Composite State](#composite-state)\n    - [Exception Handling](#exception-handling)\n  - [Alternative Specification Formats](#alternative-specification-formats)\n  - [State Diagram](#state-diagram)\n- [Code Samples](#code-samples)\n- [Support](#support)\n- [Change Log](#change-log)\n- [Copyright and License](#copyright-and-license)\n\n## Installation\n\n### Node.js\nThe library is available on [npm](https://www.npmjs.com/package/sam-fsm). To install it, type:\n\n```sh\n$ npm install --save sam-fsm\n```\n\n```javascript\nconst { fsm } = require('sam-fsm')\n\nconst simpleFsm = fsm({\n  pc0: 'START_STATE',\n  actions: {\n    DO_SOMETHING: ['END_STATE']\n  },\n  states: {\n    START_STATE: {\n      transitions: ['DO_SOMETHING']\n    },\n    END_STATE: {\n      transitions: []\n    }\n  },\n  deterministic: true,\n  enforceAllowedActions: true\n})\n```\n\n### Browsers\nInstall via npm and reference the pre-built bundle:\n\n```html\n<script src=\"./node_modules/sam-fsm/dist/fsm.js\"></script>\n```\n\nThe library's global name is `tpFSM`:\n\n```javascript\nconst { fsm } = tpFSM\n\nconst simpleFsm = fsm({\n  pc0: 'START_STATE',\n  actions: {\n    DO_SOMETHING: ['END_STATE']\n  },\n  states: {\n    START_STATE: {\n      transitions: ['DO_SOMETHING']\n    },\n    END_STATE: {\n      transitions: []\n    }\n  },\n  deterministic: true,\n  enforceAllowedActions: true\n})\n```\n\n### Getting Started\n\nThe FSM descriptor specifies:\n- **actions** and their possible resulting states (one state only for deterministic machines)\n- **states** and their respective allowed action transitions\n- **pc0** — the initial control state\n- **deterministic** — whether the FSM is deterministic\n- **enforceAllowedActions** — whether to reject transitions not in the allowed set\n- **componentName** (optional) — deploys the FSM into a SAM component's local state tree\n\nDeterministic FSMs mutate the `pc` variable automatically. Non-deterministic FSMs require you to provide one or more acceptors that mutate `pc` with the current control state value.\n\n> **Note:** `pc` follows TLA+ convention (program counter, in reference to [John Von Neumann's](https://en.wikipedia.org/wiki/Program_counter) instruction pointer).\n\n`sam-fsm` supports both action and event semantics since actions are full-fledged SAM actions:\n\n```javascript\nactions: {\n  CALL_API:   ['called'],\n  ON_SUCCESS: ['succeeded'],\n  ON_ERROR:   ['failed']\n},\nstates: {\n  called:    { transitions: ['ON_SUCCESS', 'ON_ERROR'] },\n  succeeded: { transitions: ['...'] },\n  failed:    { transitions: ['CALL_API'] }\n}\n```\n\nHere is a minimal clock example:\n\n```javascript\nconst {\n  createInstance, utils: { E }\n} = require('sam-pattern')\n\nconst { fsm } = require('sam-fsm')\n\nconst clock = fsm({\n  pc0: 'TOCKED',\n  actions: {\n    TICK: ['TICKED'],\n    TOCK: ['TOCKED']\n  },\n  states: {\n    TICKED: { transitions: ['TOCK'] },\n    TOCKED: { transitions: ['TICK'] }\n  },\n  deterministic: true,\n  enforceAllowedTransitions: true\n})\n\nconst FSMTest = createInstance({ instanceName: 'FSMTest' })\n\nconst { intents } = FSMTest({\n  initialState: clock.initialState({}),\n  component: {\n    actions: [\n      ['TICK', () => ({ tick: true, tock: false })],\n      ['TOCK', () => ({ tock: true, tick: false })]\n    ],\n    acceptors: clock.acceptors,\n    reactors: clock.stateMachine\n  },\n  render: state => console.log(state.pc)\n})\n\nconst [tick, tock] = intents\n\ntick() // -> TICKED\ntock() // -> TOCKED\n```\n\nSee also the [Rocket Launcher example](https://codepen.io/sam-pattern/pen/XWNGNBy).\n\n## Library\n\n### Constructor\n- `fsm` — instantiates a new FSM\n\n#### Parameters\n- `pc0`                    — initial control state\n- `actions`                — object where keys are action labels and values are arrays of possible resulting states (one element for deterministic machines)\n- `states`                 — object where keys are state labels and values are objects with `transitions` (array of action labels) and optional `naps`\n- `transitions`            — alternative way to define the FSM (see [Alternative Specification Formats](#alternative-specification-formats))\n- `composite`              — marks this FSM as a composite state of another FSM\n- `deterministic`          — `true` if the FSM is deterministic\n- `enforceAllowedActions`  — when `true`, acceptors validate that only allowed transitions are used\n- `pc`                     — renames the control state variable (e.g. `{ pc: 'status' }` uses `model.status`)\n- `componentName`          — deploys the FSM in the named SAM component's local state tree\n- `blockUnexpectedActions` — uses SAM's `allowedActions` mechanism to block unexpected actions; when several FSMs run together, their allowed action sets are unioned\n\n### Integration with SAM\n\nStart by creating a SAM instance:\n\n```javascript\nconst SAMFSM = createInstance({ instanceName: 'SAMFSM' })\n```\n\n`sam-fsm` provides five integration points: `initialState`, `addAction`, `event`, `acceptors`, and the `stateMachine` reactor.\n\n```javascript\nconst { intents } = SAMFSM({\n  initialState: myFsm.initialState(yourRegularSAMInitialState),\n  component: {\n    actions: [\n      action1,                                  // unrelated SAM action\n      action2,\n      ['ACTION3', action3],                     // labeled SAM action\n      ['ACTION4', action4, mySecondFSM],        // labeled action bound to a specific FSM\n      myFsm.addAction(action5, 'ACTION_5'),     // alternate labeling syntax\n      myFsm.event('ON_SUCCESS')                 // SAM action that publishes an event\n    ],\n    acceptors: [\n      ...myFsm.acceptors,   // FSM control-state acceptors\n      acceptor1,\n      acceptor2\n    ],\n    reactors: [\n      ...myFsm.stateMachine, // FSM reactor\n      reactor1,\n      reactor2\n    ]\n  },\n  render: state => console.log(state)\n})\n```\n\n**FSM instance methods:**\n\n| Method           | Description |\n|------------------|-------------|\n| `initialState`   | Wraps the SAM initial state with FSM-internal variables (e.g. `pc`) |\n| `addAction`      | Wraps a regular SAM action with a label |\n| `event`          | Creates a SAM action that publishes a named event |\n| `acceptors`      | Returns the FSM acceptors as an array |\n| `stateMachine`   | Returns the FSM reactor as a single-element array |\n| `naps`           | Returns all FSM next-action predicates as a flat array |\n\nEverything beyond that is standard SAM: add additional acceptors, reactors, and NAPs before or after the FSM ones.\n\n`sam-fsm` supports SAM component local state. Multiple FSMs can share the same SAM instance as long as each uses a distinct `pc` variable — and they can share actions.\n\n#### Next-Action Predicates\n\nNAPs can be defined inline in the state descriptor:\n\n```javascript\nstates: {\n  ticking: {\n    transitions: ['TICK', 'LAUNCH', 'ABORT'],\n    naps: [\n      {\n        condition: ({ counter }) => counter > 0,\n        nextAction: (state) => setTimeout(_tick, 1000)\n      },\n      {\n        condition: ({ counter }) => counter === 0,\n        nextAction: (state) => setTimeout(_launch, 100)\n      }\n    ]\n  }\n}\n```\n\nA NAP's condition is only evaluated when the FSM is in the parent state. Intents must be wired manually due to the circular dependency between the FSM and the SAM instance.\n\n#### Transition Guards\n\nGuards can be attached to specific transitions:\n\n```javascript\nconst clock = fsm({\n  pc: 'status',\n  pc0: 'TOCKED',\n  actions: {\n    TICK_GUARDED: ['TICKED'],\n    TOCK_GUARDED: ['TOCKED']\n  },\n  states: {\n    TICKED: {\n      transitions: ['TOCK_GUARDED'],\n      guards: [{\n        action: 'TOCK_GUARDED',\n        condition: ({ counter }) => counter < 5\n      }]\n    },\n    TOCKED: {\n      transitions: ['TICK_GUARDED'],\n      guards: [{\n        // action can be omitted — defaults to first transition\n        condition: ({ counter }) => counter < 5\n      }]\n    }\n  },\n  deterministic: true,\n  lax: false,\n  enforceAllowedTransitions: true,\n  blockUnexpectedActions: true\n})\n```\n\nThe transition is only allowed while the guard condition is `true`. In the example above, once `counter >= 5`, neither `TICK_GUARDED` nor `TOCK_GUARDED` can fire.\n\n#### Composite State\n\nA SAM instance can run multiple state machines. `sam-fsm` supports **composite states**, where one FSM can only accept actions when a parent FSM is in a specific state. The composite FSM can also automatically trigger actions on the parent FSM when it reaches terminal states.\n\nThe `composite` descriptor names the parent FSM's composite state label. The composite FSM restarts from `pc0` every time the parent enters that state.\n\n```javascript\nconst parentFSM = fsm({\n  pc: 'parentStatus',\n  states: {\n    COMPOSITE_STATE: { ... },\n    ...\n  },\n  ...\n})\n\nconst compositeStateFSM = fsm({\n  ...\n  composite: {\n    of: parentFSM,\n    onState: { pc: 'parentStatus', label: 'COMPOSITE_STATE', component: 'optionalParentComponentName' },\n    transitions: [\n      // When composite FSM reaches END, trigger intentToTrigger with { counter } from the model\n      { onState: 'END', action: intentToTrigger, proposal: ['counter'] }\n    ]\n  }\n})\n\nconst { intents } = SAMFSM({\n  ...\n  component: {\n    actions: [\n      ['ACTION1', action1, parentFSM],\n      ['ACTION2', action2, parentFSM],\n      ['ACTION3', action3, compositeStateFSM]\n    ],\n    ...\n  }\n})\n```\n\nWhen the parent and/or composite FSMs use local state, the same scoping rules apply.\n\n#### Exception Handling\n\nExceptions are reported as SAM exceptions. Access them via the standard SAM methods:\n\n```javascript\nsetRender((state) => {\n  if (state.hasError()) {\n    console.log(state.errorMessage())\n    state.clearError()\n  }\n})\n```\n\n### Alternative Specification Formats\n\nSome developers prefer a transition-list format. `sam-fsm` supports two styles.\n\n**Array of transitions:**\n\n```javascript\nconst transitions = [\n  { from: 'ready',   to: 'started', on: 'START'  },\n  { from: 'started', to: 'ticking', on: 'TICK'   },\n  { from: 'ticking', to: 'ticking', on: 'TICK'   },\n  { from: 'ticking', to: 'aborted', on: 'ABORT'  },\n  { from: 'ticking', to: 'launched',on: 'LAUNCH' },\n  { from: 'aborted', to: 'ready',   on: 'RESET'  },\n  { from: 'launched',to: 'ready',   on: 'RESET'  }\n]\n\nconst rocketFSM = fsm({ pc0: 'ready', transitions, deterministic: true })\n```\n\n**State-action-state object:**\n\n```javascript\nconst stateActionState = {\n  ready:    { START:  'started'  },\n  started:  { TICK:   'ticking'  },\n  ticking:  { TICK:   'ticking', ABORT: 'aborted', LAUNCH: 'launched' },\n  aborted:  { RESET:  'ready'    },\n  launched: { RESET:  'ready'    }\n}\n\nconst rocketFSM = fsm({ pc0: 'ready', transitions: stateActionState, deterministic: true })\n```\n\nBoth formats are detected automatically. You can also use `fsm.actionsAndStatesFor` to convert them to the canonical `{ pc0, states, actions }` form:\n\n```javascript\nconst { pc0, states, actions } = fsm.actionsAndStatesFor(transitions)\nconst rocketFSM = fsm({ pc0, states, actions })\n```\n\n> **Note:** Transition-list formats automatically set `deterministic: true` and `enforceAllowedTransitions: true`, but do not support NAPs.\n\n### State Diagram\n\nEach FSM instance exposes a [GraphViz-formatted](https://edotor.net/) state diagram:\n\n```javascript\nconst clock = fsm({ ... })\n\nconsole.log(clock.stateDiagram)\n\n// Output:\n// digraph fsm_diagram {\n// rankdir=LR;\n// size=\"8,5\"\n// READY [shape = circle margin=0 fixedsize=true width=0.33 fontcolor=black style=filled color=black label=\"\\n\\n\\nREADY\"]\n// END [shape = doublecircle margin=0 style=filled fontcolor=white color=black]\n// node [shape = Mrecord];\n// READY -> TICKED [label = \"START\"];\n// TICKED -> TOCKED [label = \"TOCK\"];\n// TOCKED -> TICKED [label = \"TICK\"];\n// TOCKED -> END [label = \"STOP\\n counter > 5\"];\n// }\n```\n\nUse [edotor.net](https://edotor.net/) or [magjac.com/graphviz-visual-editor](http://magjac.com/graphviz-visual-editor/) to render the diagram. Guard conditions are included in the output when present.\n\nA runtime state diagram (reflecting the current state) is also available:\n\n```javascript\nclock.runtimeStateDiagram()\n```\n\n<img src=\"graphviz.png\" style=\"width: 300px\" />\n\n## Code Samples\n\n- [Rocket Launcher](https://codepen.io/sam-pattern/pen/XWNGNBy)\n- [sam-fsm without sam-pattern](https://codepen.io/sam-pattern/pen/abBejoV)\n- [Unit tests](https://github.com/cognitive-fab/sam-fsm/tree/master/test)\n\n## Support\n\nPlease post your questions and comments on the [SAM-pattern forum](https://gitter.im/jdubray/sam).\n\n## Change Log\n- 2.1.1   Documentation only — backfills the change log, which had stopped at 0.9.24 and carried no record of the v2 line\n- 2.1.0   Next-state (prime) compatibility with `@cognitive-fab/sam-pattern` 2.1: the deterministic acceptor writes through `stepApi.next` when present (falling back to in-place mutation on v1/default instances), captures the target state before writing, and declares `pc`/`pc_1` `unchanged` on steps the machine does not act on so multi-machine strict instances satisfy the explicit frame. Non-deterministic (user-supplied) acceptors migrate like any strict acceptor\n- 2.0.0   Strict-profile support: `fsm()` emits a `modelShape` and `namedActions(creators)`, with `schemas` and `domains` options, plus `rejectUnexpectedActions`. Verified against `@cognitive-fab/sam-pattern` 2.0. Package republished under the `@cognitive-fab` scope as `@cognitive-fab/sam-fsm`\n- 1.0.0   Fixes fragile guard-condition parsing (no longer assumes a `return` keyword); security fixes; extended test suite\n- 0.9.24  RC2 — `sam-fsm` is ready for production use\n- 0.9.23  Adds indexed actions to runtime state diagrams\n- 0.9.20  Adds support for runtime state diagrams\n- 0.9.19  Adds support for composite state machines\n- 0.9.17  Adds GraphViz state diagram generation\n- 0.9.15  Adds tests for labeled SAM actions\n- 0.9.12  Minifies the bundle (~3.4 kB)\n- 0.9.11  Fixes minor defect; adds CodePen sample without `sam-pattern`\n- 0.9.10  RC1 — `sam-fsm` is feature complete\n- 0.9.9   Adds support for SAM `allowedActions` (blocking unexpected actions)\n          **Breaking:** `send` instance method renamed to `event`\n- 0.9.8   Adds support for `transitions` constructor format\n- 0.9.7   Adds local state support; new unit tests; doc and code-sample cleanup\n- 0.9.2   Adds `actionsAndStatesFor` and `flattenTransitions` helpers\n- 0.9.1   Adds next-action predicates in the FSM specification\n- 0.8.9   Ready for community review\n\n## Copyright and License\nCode and documentation copyright 2021 Jean-Jacques Dubray. Code released under the [ISC license](https://opensource.org/licenses/ISC). Docs released under Creative Commons.\n","readmeFilename":"README.md"}