xstate 6.0.0-alpha.70 → 6.0.0-alpha.72

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.
Files changed (71) hide show
  1. package/README.md +28 -0
  2. package/bin/agent.js +347 -0
  3. package/bin/xstate.js +16 -0
  4. package/dist/{createActor-11e36865.development.cjs.js → createActor-2136a8ad.development.cjs.js} +528 -461
  5. package/dist/{createActor-eaa95ae0.esm.js → createActor-22a0534b.esm.js} +528 -456
  6. package/dist/{createActor-9529bbaf.development.esm.js → createActor-286a0dbb.development.esm.js} +528 -462
  7. package/dist/{createActor-54296cf2.cjs.js → createActor-f4978450.cjs.js} +528 -455
  8. package/dist/declarations/src/StateMachine.d.ts +5 -3
  9. package/dist/declarations/src/base.types.d.ts +20 -20
  10. package/dist/declarations/src/durable/index.d.ts +16 -7
  11. package/dist/declarations/src/graph/types.d.ts +22 -1
  12. package/dist/declarations/src/index.d.ts +2 -2
  13. package/dist/declarations/src/serialize.d.ts +14 -0
  14. package/dist/declarations/src/setup.d.ts +3 -1
  15. package/dist/declarations/src/system.d.ts +14 -1
  16. package/dist/declarations/src/types.d.ts +5 -2
  17. package/dist/{effectDescriptor-97802bb8.esm.js → effectDescriptor-2ae7e5e3.esm.js} +1 -1
  18. package/dist/{effectDescriptor-df768c71.development.esm.js → effectDescriptor-86f04325.development.esm.js} +1 -1
  19. package/dist/{effectDescriptor-425644eb.cjs.js → effectDescriptor-a6247f92.cjs.js} +1 -1
  20. package/dist/{effectDescriptor-fd2aaf45.development.cjs.js → effectDescriptor-e41e1627.development.cjs.js} +1 -1
  21. package/dist/{index-9826ee1d.development.cjs.js → index-31b3c878.cjs.js} +3 -3
  22. package/dist/{index-986372f2.cjs.js → index-5ce9cbf9.development.cjs.js} +3 -3
  23. package/dist/{index-e30fe161.development.esm.js → index-6923de25.development.esm.js} +3 -3
  24. package/dist/{index-967ad7fa.esm.js → index-74b77771.esm.js} +3 -3
  25. package/dist/xstate-actors.cjs.js +2 -2
  26. package/dist/xstate-actors.development.cjs.js +2 -2
  27. package/dist/xstate-actors.development.esm.js +2 -2
  28. package/dist/xstate-actors.esm.js +2 -2
  29. package/dist/xstate-actors.umd.min.js +1 -1
  30. package/dist/xstate-actors.umd.min.js.map +1 -1
  31. package/dist/xstate-durable.cjs.js +11 -14
  32. package/dist/xstate-durable.development.cjs.js +11 -14
  33. package/dist/xstate-durable.development.esm.js +11 -14
  34. package/dist/xstate-durable.esm.js +11 -14
  35. package/dist/xstate-durable.umd.min.js +1 -1
  36. package/dist/xstate-durable.umd.min.js.map +1 -1
  37. package/dist/xstate-graph.cjs.js +32 -6
  38. package/dist/xstate-graph.development.cjs.js +32 -6
  39. package/dist/xstate-graph.development.esm.js +33 -7
  40. package/dist/xstate-graph.esm.js +33 -7
  41. package/dist/xstate-graph.umd.min.js +1 -1
  42. package/dist/xstate-graph.umd.min.js.map +1 -1
  43. package/dist/xstate-validation.cjs.js +1 -1
  44. package/dist/xstate-validation.development.cjs.js +1 -1
  45. package/dist/xstate-validation.development.esm.js +1 -1
  46. package/dist/xstate-validation.esm.js +1 -1
  47. package/dist/xstate.cjs.js +161 -49
  48. package/dist/xstate.cjs.mjs +1 -0
  49. package/dist/xstate.development.cjs.js +161 -49
  50. package/dist/xstate.development.cjs.mjs +1 -0
  51. package/dist/xstate.development.esm.js +163 -52
  52. package/dist/xstate.esm.js +163 -52
  53. package/dist/xstate.umd.min.js +1 -1
  54. package/dist/xstate.umd.min.js.map +1 -1
  55. package/docs/agents.md +132 -0
  56. package/docs/api-stability.json +3672 -0
  57. package/docs/context.md +4 -0
  58. package/docs/create-actor.md +3 -2
  59. package/docs/durable-execution.md +10 -2
  60. package/docs/emitted-events.md +20 -1
  61. package/docs/index.md +4 -2
  62. package/docs/model-based-testing.md +3 -1
  63. package/docs/persist-and-restore-actors.md +1 -1
  64. package/docs/persistence.md +2 -2
  65. package/docs/scxml.md +32 -0
  66. package/docs/serialization.md +3 -0
  67. package/docs/systems.md +33 -1
  68. package/docs/transition-functions.md +3 -1
  69. package/docs/transitions.md +4 -2
  70. package/docs/xstate-v5-to-v6.md +16 -1
  71. package/package.json +1 -1
package/README.md CHANGED
@@ -8,6 +8,11 @@ Before writing code with an installed package, read `docs/index.md` beside this
8
8
  README, usually `node_modules/xstate/docs/index.md`. These bundled docs match the
9
9
  installed package version.
10
10
 
11
+ For coding agents, start with `docs/agents.md`. Run `npx xstate docs` to locate
12
+ the installed documentation and read its machine-readable stability manifest.
13
+ The data-only CLI also supports `inspect`, `validate`, `simulate`, `test`,
14
+ `replay` and `diff`; see the agent guide for their contracts and limits.
15
+
11
16
  Online, start with the [quick start](https://github.com/statelyai/xstate/blob/next/docs/quick-start.md)
12
17
  or the [v5 to v6 migration guide](https://github.com/statelyai/xstate/blob/next/docs/xstate-v5-to-v6.md).
13
18
 
@@ -87,6 +92,29 @@ XState is a state management and orchestration solution for JavaScript and TypeS
87
92
 
88
93
  It uses event-driven programming, state machines, statecharts, and the actor model to handle complex logic in predictable, robust, and visual ways. XState provides a powerful and flexible way to manage application and workflow state by allowing developers to model logic as actors and state machines.
89
94
 
95
+ <!-- system-wide emission observation from packages/core/src/setup.ts and packages/core/src/system.ts -->
96
+
97
+ Observe emitted events from root and child actors with `createSystem({ onEmitted })`:
98
+
99
+ ```ts
100
+ import { createMachine, createSystem } from 'xstate';
101
+
102
+ const machine = createMachine({
103
+ entry: (_, enq) => enq.emit({ type: 'started' })
104
+ });
105
+
106
+ const system = createSystem({
107
+ onEmitted: ({ actorRef, event }) => {
108
+ console.log(actorRef.id, event);
109
+ }
110
+ });
111
+
112
+ const root = system.createActor(machine).start();
113
+ ```
114
+
115
+ Use `system.onEmitted(listener)` for a removable subscription. `actor.on(...)`
116
+ observes only that actor. See [actor systems](https://github.com/statelyai/xstate/blob/next/docs/systems.md#observe-emitted-events-across-a-system).
117
+
90
118
  <!-- experimental whole-system transitions from packages/core/src/systemTransition.ts -->
91
119
 
92
120
  For pure actor-system simulation, `initialSystemTransition`, `systemTransition`
package/bin/agent.js ADDED
@@ -0,0 +1,347 @@
1
+ // Data-only agent operations. No application module loading, code evaluation or effect
2
+ // execution is needed to inspect and explore a JSON machine definition.
3
+ const { readFileSync } = require('node:fs');
4
+ const { join } = require('node:path');
5
+ const { createHash } = require('node:crypto');
6
+ const core = require('../dist/xstate.cjs.js');
7
+ const graph = require('../dist/xstate-graph.cjs.js');
8
+ const { version } = require('../package.json');
9
+
10
+ function fail(code, message, details) {
11
+ const error = new Error(message);
12
+ error.code = code;
13
+ error.details = details;
14
+ throw error;
15
+ }
16
+
17
+ function readJSON(file) {
18
+ if (!file) fail('MISSING_ARGUMENT', 'Expected a JSON file path.');
19
+ try {
20
+ return JSON.parse(readFileSync(file, 'utf8'));
21
+ } catch (error) {
22
+ fail('INVALID_JSON', `Cannot read JSON from ${file}: ${error.message}`);
23
+ }
24
+ }
25
+
26
+ function expressions(value, path = '') {
27
+ if (!value || typeof value !== 'object') return [];
28
+ if (Object.hasOwn(value, '@code')) return [path];
29
+ return Object.entries(value).flatMap(([key, entry]) =>
30
+ expressions(
31
+ entry,
32
+ `${path}/${key.replaceAll('~', '~0').replaceAll('/', '~1')}`
33
+ )
34
+ );
35
+ }
36
+
37
+ function loadMachine(file) {
38
+ const definition = readJSON(file);
39
+ if (
40
+ !definition ||
41
+ typeof definition !== 'object' ||
42
+ Array.isArray(definition)
43
+ ) {
44
+ fail('INVALID_MACHINE', 'Expected a machine definition object.');
45
+ }
46
+ const paths = expressions(definition);
47
+ if (paths.length) {
48
+ fail(
49
+ 'EVALUATOR_REQUIRED',
50
+ 'The data-only CLI does not evaluate code expressions. Use the library with explicit runtime sources.',
51
+ { paths }
52
+ );
53
+ }
54
+ try {
55
+ return {
56
+ definition,
57
+ machine: core.machineFromJSON(definition),
58
+ hash: createHash('sha256')
59
+ .update(JSON.stringify(definition))
60
+ .digest('hex')
61
+ };
62
+ } catch (error) {
63
+ fail('INVALID_MACHINE', error.message);
64
+ }
65
+ }
66
+
67
+ function view(snapshot) {
68
+ return {
69
+ value: snapshot.value,
70
+ context: snapshot.context,
71
+ stateInputs: snapshot.getInputs(),
72
+ status: snapshot.status,
73
+ output: snapshot.output,
74
+ error:
75
+ snapshot.error instanceof Error
76
+ ? { name: snapshot.error.name, message: snapshot.error.message }
77
+ : snapshot.error
78
+ };
79
+ }
80
+
81
+ function effectView(effect) {
82
+ return {
83
+ type: effect.type,
84
+ kind: effect.kind,
85
+ params: effect.params,
86
+ event: effect.event
87
+ };
88
+ }
89
+
90
+ function eventList(value) {
91
+ const events = Array.isArray(value) ? value : value?.events;
92
+ if (
93
+ !Array.isArray(events) ||
94
+ events.some((event) => !event || typeof event.type !== 'string')
95
+ ) {
96
+ fail(
97
+ 'INVALID_EVENTS',
98
+ 'Expected an array of event objects with string type fields.'
99
+ );
100
+ }
101
+ return events;
102
+ }
103
+
104
+ function replay(machine, events, input) {
105
+ let [snapshot, effects] = core.initialTransition(machine, input);
106
+ const trace = [
107
+ { event: null, snapshot: view(snapshot), effects: effects.map(effectView) }
108
+ ];
109
+ const snapshots = [snapshot];
110
+ for (const event of events) {
111
+ [snapshot, effects] = core.transition(machine, snapshot, event);
112
+ snapshots.push(snapshot);
113
+ trace.push({
114
+ event,
115
+ snapshot: view(snapshot),
116
+ effects: effects.map(effectView)
117
+ });
118
+ }
119
+ return { trace, snapshots };
120
+ }
121
+
122
+ function assertions(machine, options) {
123
+ const states = new Set([
124
+ machine.root.id,
125
+ ...graph.getDescendantStateNodes(machine.root).map((node) => node.id)
126
+ ]);
127
+ for (const kind of ['never', 'reachable']) {
128
+ if (options[kind] === undefined) continue;
129
+ if (
130
+ !Array.isArray(options[kind]) ||
131
+ options[kind].some(
132
+ (id) =>
133
+ typeof id !== 'string' ||
134
+ !id.startsWith('#') ||
135
+ !states.has(id.slice(1))
136
+ )
137
+ ) {
138
+ fail(
139
+ 'INVALID_ASSERTION',
140
+ `${kind} must list existing state IDs prefixed with #.`
141
+ );
142
+ }
143
+ }
144
+ }
145
+
146
+ function violation(snapshots, never = []) {
147
+ for (const snapshot of snapshots) {
148
+ for (const state of never) {
149
+ if (snapshot.matches(state)) return { state, snapshot: view(snapshot) };
150
+ }
151
+ }
152
+ }
153
+
154
+ function difference(before, after, path = '') {
155
+ if (JSON.stringify(before) === JSON.stringify(after)) return [];
156
+ if (
157
+ before &&
158
+ after &&
159
+ typeof before === 'object' &&
160
+ typeof after === 'object' &&
161
+ !Array.isArray(before) &&
162
+ !Array.isArray(after)
163
+ ) {
164
+ return [...new Set([...Object.keys(before), ...Object.keys(after)])]
165
+ .sort()
166
+ .flatMap((key) => {
167
+ const pointer = `${path}/${key.replaceAll('~', '~0').replaceAll('/', '~1')}`;
168
+ if (!Object.hasOwn(before, key))
169
+ return [{ op: 'add', path: pointer, after: after[key] }];
170
+ if (!Object.hasOwn(after, key))
171
+ return [{ op: 'remove', path: pointer, before: before[key] }];
172
+ return difference(before[key], after[key], pointer);
173
+ });
174
+ }
175
+ return [{ op: 'replace', path, before, after }];
176
+ }
177
+
178
+ function run(command, rawArgs) {
179
+ const args = rawArgs.filter((arg) => arg !== '--json');
180
+ if (command === 'docs') {
181
+ return {
182
+ index: join(__dirname, '../docs/index.md'),
183
+ agentGuide: join(__dirname, '../docs/agents.md'),
184
+ stability: readJSON(join(__dirname, '../docs/api-stability.json'))
185
+ };
186
+ }
187
+ if (command === 'diff') {
188
+ const changes = difference(readJSON(args[0]), readJSON(args[1]));
189
+ return {
190
+ changed: changes.length > 0,
191
+ changes,
192
+ compatibility: 'notChecked'
193
+ };
194
+ }
195
+ const { machine, definition, hash } = loadMachine(args[0]);
196
+ if (command === 'validate')
197
+ return {
198
+ valid: true,
199
+ scope: 'definition',
200
+ runtimePayloadValidation: false
201
+ };
202
+ if (command === 'inspect') {
203
+ return {
204
+ definition,
205
+ graph: graph.toDirectedGraph(machine),
206
+ serializationDiagnostics:
207
+ core.getMachineSerializationDiagnostics(machine),
208
+ runtimePayloadValidation: false
209
+ };
210
+ }
211
+ if (command === 'simulate') {
212
+ const request = readJSON(args[1]);
213
+ return {
214
+ effectsExecuted: false,
215
+ ...omitSnapshots(replay(machine, eventList(request), request.input))
216
+ };
217
+ }
218
+ if (command === 'replay') {
219
+ const fixture = readJSON(args[1]);
220
+ if (
221
+ fixture.schemaVersion !== 1 ||
222
+ fixture.machineHash !== hash ||
223
+ fixture.xstateVersion !== version
224
+ ) {
225
+ fail(
226
+ 'FIXTURE_MISMATCH',
227
+ 'The fixture must match this definition, XState version and fixture format.'
228
+ );
229
+ }
230
+ assertions(machine, fixture);
231
+ const result = replay(machine, eventList(fixture), fixture.input);
232
+ const failure = violation(result.snapshots, fixture.never);
233
+ if (failure)
234
+ fail('PROPERTY_VIOLATION', 'A forbidden stable state was reached.', {
235
+ ...failure,
236
+ trace: result.trace,
237
+ fixture
238
+ });
239
+ return { effectsExecuted: false, trace: result.trace };
240
+ }
241
+ if (command === 'test') {
242
+ const options = readJSON(args[1]);
243
+ assertions(machine, options);
244
+ if (!(options.never?.length || options.reachable?.length)) {
245
+ fail(
246
+ 'MISSING_ASSERTION',
247
+ 'Supply at least one never or reachable state assertion.'
248
+ );
249
+ }
250
+ if (
251
+ options.limit !== undefined &&
252
+ (!Number.isSafeInteger(options.limit) || options.limit <= 0)
253
+ ) {
254
+ fail('INVALID_LIMIT', 'limit must be a positive safe integer.');
255
+ }
256
+ const paths = graph.getShortestPaths(machine, {
257
+ events: options.events,
258
+ input: options.input,
259
+ limit: options.limit ?? 10000
260
+ });
261
+ const visited = paths.map((path) => path.state);
262
+ const errored = visited.find((snapshot) => snapshot.status === 'error');
263
+ if (errored)
264
+ fail(
265
+ 'MODEL_EXECUTION_FAILED',
266
+ 'Model exploration entered an error snapshot.',
267
+ { snapshot: view(errored) }
268
+ );
269
+ for (const path of paths) {
270
+ const failure = violation(
271
+ path.steps.map((step) => step.state),
272
+ options.never
273
+ );
274
+ if (failure) {
275
+ const fixture = {
276
+ schemaVersion: 1,
277
+ xstateVersion: version,
278
+ machineHash: hash,
279
+ events: path.steps.slice(1).map((step) => step.event),
280
+ input: options.input,
281
+ never: options.never
282
+ };
283
+ fail('PROPERTY_VIOLATION', 'A forbidden stable state was reached.', {
284
+ ...failure,
285
+ fixture
286
+ });
287
+ }
288
+ }
289
+ const missing = (options.reachable ?? []).filter(
290
+ (state) => !visited.some((snapshot) => snapshot.matches(state))
291
+ );
292
+ if (missing.length)
293
+ fail(
294
+ 'REACHABILITY_NOT_WITNESSED',
295
+ 'No supplied event representative reached these states.',
296
+ { states: missing }
297
+ );
298
+ return {
299
+ passed: true,
300
+ scope: 'stableSnapshots',
301
+ effectsExecuted: false,
302
+ proof: false,
303
+ representativeEvents: options.events ?? null,
304
+ eventMode: Array.isArray(options.events)
305
+ ? 'replaceDefaults'
306
+ : 'augmentDefaults',
307
+ limit: options.limit ?? 10000,
308
+ paths: paths.length,
309
+ states: visited.map(view)
310
+ };
311
+ }
312
+ fail('UNKNOWN_COMMAND', `Unknown agent command: ${command}`);
313
+ }
314
+
315
+ function omitSnapshots({ trace }) {
316
+ return { trace };
317
+ }
318
+
319
+ module.exports = (command, args) => {
320
+ try {
321
+ const result = run(command, args);
322
+ console.log(
323
+ JSON.stringify({
324
+ schemaVersion: 1,
325
+ xstateVersion: version,
326
+ command,
327
+ ok: true,
328
+ result
329
+ })
330
+ );
331
+ } catch (error) {
332
+ console.log(
333
+ JSON.stringify({
334
+ schemaVersion: 1,
335
+ xstateVersion: version,
336
+ command,
337
+ ok: false,
338
+ error: {
339
+ code: error.code ?? 'OPERATION_FAILED',
340
+ message: error.message,
341
+ details: error.details
342
+ }
343
+ })
344
+ );
345
+ process.exitCode = 1;
346
+ }
347
+ };
package/bin/xstate.js CHANGED
@@ -6,6 +6,15 @@ const { spawnSync } = require('child_process');
6
6
  const [command, ...rest] = process.argv.slice(2);
7
7
 
8
8
  switch (command) {
9
+ case 'docs':
10
+ case 'inspect':
11
+ case 'validate':
12
+ case 'simulate':
13
+ case 'test':
14
+ case 'replay':
15
+ case 'diff':
16
+ require('./agent.js')(command, rest);
17
+ break;
9
18
  case 'migrate': {
10
19
  // Delegates to @xstate/codemod (ts-morph based v5 → v6 codemods).
11
20
  const result = spawnSync(
@@ -24,6 +33,13 @@ switch (command) {
24
33
  'Usage: xstate <command>',
25
34
  '',
26
35
  'Commands:',
36
+ ' docs Installed docs and export stability (JSON)',
37
+ ' inspect <machine.json> Definition and graph completeness (JSON)',
38
+ ' validate <machine.json> Validate a data-only definition (JSON)',
39
+ ' simulate <machine> <events> Calculate transitions without effects (JSON)',
40
+ ' test <machine> <options> Check sampled stable-state properties (JSON)',
41
+ ' replay <machine> <fixture> Replay a matching counterexample (JSON)',
42
+ ' diff <before> <after> Structural definition changes (JSON)',
27
43
  ' migrate [globs...] [--dry] Migrate source files to the current XState version',
28
44
  ' (delegates to @xstate/codemod)'
29
45
  ].join('\n')