machina-inspect 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +69 -0
- package/README.md +185 -0
- package/dist/index.cjs +589 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +200 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +200 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +556 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
Copyright (c) 2011-2023 Jim Cowart (MIT License)
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
|
4
|
+
obtaining a copy of this software and associated documentation
|
|
5
|
+
files (the "Software"), to deal in the Software without
|
|
6
|
+
restriction, including without limitation the rights to use,
|
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the
|
|
9
|
+
Software is furnished to do so, subject to the following
|
|
10
|
+
conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be
|
|
13
|
+
included in all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
See http://opensource.org/licenses/MIT for more details.
|
|
25
|
+
|
|
26
|
+
--------------------------------------------------------------------
|
|
27
|
+
Also available under GPL 2.0
|
|
28
|
+
Copyright (c) 2011-2015 Jim Cowart (GPL-2.0 License)
|
|
29
|
+
|
|
30
|
+
This program is free software; you can redistribute it and/or modify
|
|
31
|
+
it under the terms of the GNU General Public License as published by
|
|
32
|
+
the Free Software Foundation; either version 2 of the License, or (at
|
|
33
|
+
your option) any later version.
|
|
34
|
+
|
|
35
|
+
This program is distributed in the hope that it will be useful, but
|
|
36
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
37
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
38
|
+
General Public License for more details.
|
|
39
|
+
|
|
40
|
+
See http://opensource.org/licenses/GPL-2.0 for more details
|
|
41
|
+
|
|
42
|
+
--------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
The extend function included in machina was very heavily borrowed
|
|
45
|
+
from the backbone.js project, which can be found at http://backbonejs.org.
|
|
46
|
+
Due to its origin, the backbone license is included below as well.
|
|
47
|
+
|
|
48
|
+
Copyright (c) 2010-2012 Jeremy Ashkenas, DocumentCloud
|
|
49
|
+
|
|
50
|
+
Permission is hereby granted, free of charge, to any person
|
|
51
|
+
obtaining a copy of this software and associated documentation
|
|
52
|
+
files (the "Software"), to deal in the Software without
|
|
53
|
+
restriction, including without limitation the rights to use,
|
|
54
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
55
|
+
copies of the Software, and to permit persons to whom the
|
|
56
|
+
Software is furnished to do so, subject to the following
|
|
57
|
+
conditions:
|
|
58
|
+
|
|
59
|
+
The above copyright notice and this permission notice shall be
|
|
60
|
+
included in all copies or substantial portions of the Software.
|
|
61
|
+
|
|
62
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
63
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
64
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
65
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
66
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
67
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
68
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
69
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# machina-inspect
|
|
2
|
+
|
|
3
|
+
Static analysis for [machina](https://machina-js.org) FSM configs. Catches structural bugs — unreachable states, infinite `_onEnter` loops, missing handlers — without running the machine.
|
|
4
|
+
|
|
5
|
+
Parses your FSM config (or a live instance) into a directed graph IR, then runs checks against it. The graph is a first-class export, so other tools (diagram generators, linters, [eslint-plugin-machina](https://www.npmjs.com/package/eslint-plugin-machina)) can build on the same representation.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install machina-inspect
|
|
11
|
+
# or
|
|
12
|
+
pnpm add machina-inspect
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`machina` >= 6.0.0 is a peer dependency.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createFsm } from "machina";
|
|
21
|
+
import { inspect } from "machina-inspect";
|
|
22
|
+
|
|
23
|
+
const config = {
|
|
24
|
+
id: "traffic-light",
|
|
25
|
+
initialState: "green",
|
|
26
|
+
states: {
|
|
27
|
+
green: { timeout: "yellow" },
|
|
28
|
+
yellow: { timeout: "red" },
|
|
29
|
+
red: { timeout: "green" },
|
|
30
|
+
broken: {}, // unreachable — no transitions lead here
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const findings = inspect(config);
|
|
35
|
+
// [{ type: "unreachable-state", states: ["broken"], ... }]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Works with live instances too:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
const fsm = createFsm(config);
|
|
42
|
+
const findings = inspect(fsm);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
### `inspect(input): Finding[]`
|
|
48
|
+
|
|
49
|
+
Build a state graph and run all checks. Returns an array of findings — empty means no issues.
|
|
50
|
+
|
|
51
|
+
`input` is either a config object (`{ id, initialState, states }`) or a live `Fsm` / `BehavioralFsm` instance.
|
|
52
|
+
|
|
53
|
+
### `buildStateGraph(input): StateGraph`
|
|
54
|
+
|
|
55
|
+
Build the graph IR without running checks. Use this when you want the graph for other purposes (visualization, custom analysis) and want to avoid building it twice.
|
|
56
|
+
|
|
57
|
+
### `inspectGraph(graph): Finding[]`
|
|
58
|
+
|
|
59
|
+
Run all checks against a pre-built `StateGraph`. Pair with `buildStateGraph()` when you need both the graph and the findings.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { buildStateGraph, inspectGraph } from "machina-inspect";
|
|
63
|
+
|
|
64
|
+
const graph = buildStateGraph(config);
|
|
65
|
+
// use `graph` for diagram export, etc.
|
|
66
|
+
const findings = inspectGraph(graph);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `analyzeHandler(handler): HandlerTarget[]`
|
|
70
|
+
|
|
71
|
+
Parse a handler function for transition targets via acorn AST analysis of `handler.toString()`. Returns `{ target, confidence }` pairs.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { analyzeHandler } from "machina-inspect";
|
|
75
|
+
|
|
76
|
+
const targets = analyzeHandler(({ ctx }) => {
|
|
77
|
+
if (ctx.ready) {
|
|
78
|
+
return "active";
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// [{ target: "active", confidence: "possible" }]
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `walkHandlerAst(node): HandlerTarget[]`
|
|
85
|
+
|
|
86
|
+
Walk a pre-parsed ESTree/acorn function AST node to extract transition targets. This is the shared core used by both the runtime path (`analyzeHandler`) and the ESLint plugin. Accepts `AstFunctionNode` — a union of `FunctionDeclaration`, `FunctionExpression`, and `ArrowFunctionExpression`.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { walkHandlerAst } from "machina-inspect";
|
|
90
|
+
import type { AstFunctionNode } from "machina-inspect";
|
|
91
|
+
|
|
92
|
+
// Use with a pre-parsed AST node (e.g., from ESLint or acorn)
|
|
93
|
+
const targets = walkHandlerAst(functionNode as AstFunctionNode);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Checks
|
|
97
|
+
|
|
98
|
+
### Unreachable States
|
|
99
|
+
|
|
100
|
+
BFS from `initialState`. Any state with no inbound path is reported. Both `"definite"` and `"possible"` edges count — if there's any path, the state is reachable.
|
|
101
|
+
|
|
102
|
+
### `_onEnter` Loops
|
|
103
|
+
|
|
104
|
+
DFS cycle detection on the subgraph of `_onEnter` transitions. Only reports cycles where **every** edge is `"definite"` (unconditional). Conditional bounces like `if (ctx.error) return "failed"` are intentional patterns, not bugs.
|
|
105
|
+
|
|
106
|
+
Same-state self-loops are excluded — the machina runtime ignores same-state transitions, so they're no-ops.
|
|
107
|
+
|
|
108
|
+
### Missing Handlers
|
|
109
|
+
|
|
110
|
+
Collects the union of all input names across all states in the FSM. States that don't handle inputs present elsewhere are flagged. This is a best-effort check — only inputs visible as graph edges (string shorthand or statically extractable function returns) are included.
|
|
111
|
+
|
|
112
|
+
States with a `*` catch-all handler are excluded (they implicitly handle everything). `_onEnter`, `_onExit`, and `*` are excluded from the input union. Child graphs are checked independently with their own input sets.
|
|
113
|
+
|
|
114
|
+
## Graph IR
|
|
115
|
+
|
|
116
|
+
The `StateGraph` is designed to be consumed by downstream tools:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
interface StateGraph {
|
|
120
|
+
fsmId: string;
|
|
121
|
+
initialState: string;
|
|
122
|
+
nodes: Record<string, StateNode>;
|
|
123
|
+
children: Record<string, StateGraph>; // child FSMs, keyed by parent state
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface StateNode {
|
|
127
|
+
name: string;
|
|
128
|
+
edges: TransitionEdge[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface TransitionEdge {
|
|
132
|
+
inputName: string; // handler name, "_onEnter", or "*"
|
|
133
|
+
from: string;
|
|
134
|
+
to: string;
|
|
135
|
+
confidence: "definite" | "possible";
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Confidence Levels
|
|
140
|
+
|
|
141
|
+
- **`"definite"`** — Unconditional transition. String shorthands (`timeout: "yellow"`) and functions with a single top-level return.
|
|
142
|
+
- **`"possible"`** — Conditional transition. Returns inside `if`, `switch`, ternary, logical expressions, or `try` blocks. Also applies when a function has multiple return statements.
|
|
143
|
+
|
|
144
|
+
Function handlers are analyzed via [acorn](https://github.com/acornjs/acorn) (AST parsing of `handler.toString()`). Non-string returns, template literals, and variable returns are ignored — the analysis is best-effort, not exhaustive.
|
|
145
|
+
|
|
146
|
+
## Child FSMs
|
|
147
|
+
|
|
148
|
+
`_child` declarations are followed recursively. The resulting child graphs appear in `StateGraph.children`, keyed by the parent state name. Each child graph is analyzed independently (its own `initialState`, its own reachability).
|
|
149
|
+
|
|
150
|
+
## Findings
|
|
151
|
+
|
|
152
|
+
Findings are a discriminated union — narrow on `finding.type` to access type-specific fields.
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
interface BaseFinding {
|
|
156
|
+
message: string;
|
|
157
|
+
fsmId: string;
|
|
158
|
+
states: string[];
|
|
159
|
+
parentState?: string; // set for child FSM findings
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
interface UnreachableFinding extends BaseFinding {
|
|
163
|
+
type: "unreachable-state";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface OnEnterLoopFinding extends BaseFinding {
|
|
167
|
+
type: "onenter-loop";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface MissingHandlerFinding extends BaseFinding {
|
|
171
|
+
type: "missing-handler";
|
|
172
|
+
inputs: string[]; // the input names this state is missing
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
type Finding = UnreachableFinding | OnEnterLoopFinding | MissingHandlerFinding;
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## See also
|
|
179
|
+
|
|
180
|
+
- [eslint-plugin-machina](https://www.npmjs.com/package/eslint-plugin-machina) — get these checks inline in your editor via ESLint
|
|
181
|
+
- [machina-explorer](https://machina-js.org/examples/machina-explorer/) — browser-based paste-and-analyze UI built on machina-inspect
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT
|