xstate 5.32.0 → 6.0.0-alpha.1

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 (119) hide show
  1. package/README.md +7 -8
  2. package/dist/StateMachine-47e60715.esm.js +720 -0
  3. package/dist/StateMachine-48dcad8f.cjs.js +723 -0
  4. package/dist/StateMachine-6e063ac1.development.cjs.js +740 -0
  5. package/dist/StateMachine-6ec50e21.development.esm.js +737 -0
  6. package/dist/declarations/src/State.d.ts +11 -13
  7. package/dist/declarations/src/StateMachine.d.ts +27 -21
  8. package/dist/declarations/src/StateNode.d.ts +18 -54
  9. package/dist/declarations/src/actions.d.ts +14 -9
  10. package/dist/declarations/src/actors/callback.d.ts +42 -16
  11. package/dist/declarations/src/actors/index.d.ts +9 -6
  12. package/dist/declarations/src/actors/listener.d.ts +18 -0
  13. package/dist/declarations/src/actors/logic.d.ts +105 -0
  14. package/dist/declarations/src/actors/observable.d.ts +72 -27
  15. package/dist/declarations/src/actors/promise.d.ts +133 -48
  16. package/dist/declarations/src/actors/subscription.d.ts +22 -0
  17. package/dist/declarations/src/atom.d.ts +81 -0
  18. package/dist/declarations/src/createActor.d.ts +31 -33
  19. package/dist/declarations/src/createMachine.d.ts +35 -7
  20. package/dist/declarations/src/createMachineFromConfig.d.ts +176 -0
  21. package/dist/declarations/src/graph/graph.d.ts +4 -2
  22. package/dist/declarations/src/index.d.ts +12 -8
  23. package/dist/declarations/src/inspection.d.ts +84 -30
  24. package/dist/declarations/src/mapState.d.ts +4 -2
  25. package/dist/declarations/src/schema.types.d.ts +90 -0
  26. package/dist/declarations/src/serialize.d.ts +44 -0
  27. package/dist/declarations/src/setup.d.ts +251 -123
  28. package/dist/declarations/src/spawn.d.ts +6 -31
  29. package/dist/declarations/src/stateUtils.d.ts +21 -19
  30. package/dist/declarations/src/system.d.ts +8 -7
  31. package/dist/declarations/src/transition.d.ts +5 -5
  32. package/dist/declarations/src/types.d.ts +407 -406
  33. package/dist/declarations/src/types.v6.d.ts +392 -0
  34. package/dist/declarations/src/utils.d.ts +4 -3
  35. package/dist/index-39259ea4.development.esm.js +4948 -0
  36. package/dist/index-688701ed.esm.js +4909 -0
  37. package/dist/index-c35824b4.development.cjs.js +5007 -0
  38. package/dist/index-f0d7107d.cjs.js +4968 -0
  39. package/dist/xstate-actors.cjs.js +12 -838
  40. package/dist/xstate-actors.cjs.mjs +10 -5
  41. package/dist/xstate-actors.development.cjs.js +12 -838
  42. package/dist/xstate-actors.development.cjs.mjs +10 -5
  43. package/dist/xstate-actors.development.esm.js +1 -836
  44. package/dist/xstate-actors.esm.js +1 -836
  45. package/dist/xstate-actors.umd.min.js +1 -1
  46. package/dist/xstate-actors.umd.min.js.map +1 -1
  47. package/dist/xstate-graph.cjs.js +18 -29
  48. package/dist/xstate-graph.development.cjs.js +18 -29
  49. package/dist/xstate-graph.development.esm.js +15 -26
  50. package/dist/xstate-graph.esm.js +15 -26
  51. package/dist/xstate-graph.umd.min.js +1 -1
  52. package/dist/xstate-graph.umd.min.js.map +1 -1
  53. package/dist/xstate.cjs.js +1316 -108
  54. package/dist/xstate.cjs.mjs +22 -22
  55. package/dist/xstate.development.cjs.js +1316 -108
  56. package/dist/xstate.development.cjs.mjs +22 -22
  57. package/dist/xstate.development.esm.js +1278 -72
  58. package/dist/xstate.esm.js +1278 -72
  59. package/dist/xstate.umd.min.js +1 -1
  60. package/dist/xstate.umd.min.js.map +1 -1
  61. package/package.json +3 -47
  62. package/dist/StateMachine-45e79c35.esm.js +0 -560
  63. package/dist/StateMachine-be2e51a3.development.cjs.js +0 -569
  64. package/dist/StateMachine-e0178d48.development.esm.js +0 -566
  65. package/dist/StateMachine-e6732977.cjs.js +0 -563
  66. package/dist/assign-2aa58daa.esm.js +0 -127
  67. package/dist/assign-927227d6.development.cjs.js +0 -135
  68. package/dist/assign-e8f2bd75.cjs.js +0 -129
  69. package/dist/assign-f338cee3.development.esm.js +0 -133
  70. package/dist/declarations/src/actions/assign.d.ts +0 -45
  71. package/dist/declarations/src/actions/cancel.d.ts +0 -39
  72. package/dist/declarations/src/actions/emit.d.ts +0 -44
  73. package/dist/declarations/src/actions/enqueueActions.d.ts +0 -57
  74. package/dist/declarations/src/actions/log.d.ts +0 -16
  75. package/dist/declarations/src/actions/raise.d.ts +0 -21
  76. package/dist/declarations/src/actions/send.d.ts +0 -42
  77. package/dist/declarations/src/actions/spawnChild.d.ts +0 -34
  78. package/dist/declarations/src/actions/stopChild.d.ts +0 -19
  79. package/dist/declarations/src/actors/transition.d.ts +0 -143
  80. package/dist/declarations/src/dev/index.d.ts +0 -14
  81. package/dist/declarations/src/guards.d.ts +0 -128
  82. package/dist/log-029f180b.development.cjs.js +0 -433
  83. package/dist/log-5a37e0ee.development.esm.js +0 -425
  84. package/dist/log-605ef461.esm.js +0 -397
  85. package/dist/log-fedf0966.cjs.js +0 -405
  86. package/dist/raise-74097812.development.esm.js +0 -2823
  87. package/dist/raise-7c948725.cjs.js +0 -2816
  88. package/dist/raise-964cd4e9.development.cjs.js +0 -2878
  89. package/dist/raise-e974d1c9.esm.js +0 -2761
  90. package/dist/xstate-actions.cjs.d.mts +0 -2
  91. package/dist/xstate-actions.cjs.d.ts +0 -2
  92. package/dist/xstate-actions.cjs.js +0 -23
  93. package/dist/xstate-actions.cjs.mjs +0 -14
  94. package/dist/xstate-actions.development.cjs.js +0 -23
  95. package/dist/xstate-actions.development.cjs.mjs +0 -14
  96. package/dist/xstate-actions.development.esm.js +0 -4
  97. package/dist/xstate-actions.esm.js +0 -4
  98. package/dist/xstate-actions.umd.min.js +0 -2
  99. package/dist/xstate-actions.umd.min.js.map +0 -1
  100. package/dist/xstate-dev.cjs.d.mts +0 -2
  101. package/dist/xstate-dev.cjs.d.ts +0 -2
  102. package/dist/xstate-dev.cjs.js +0 -48
  103. package/dist/xstate-dev.cjs.mjs +0 -5
  104. package/dist/xstate-dev.development.cjs.js +0 -51
  105. package/dist/xstate-dev.development.cjs.mjs +0 -5
  106. package/dist/xstate-dev.development.esm.js +0 -45
  107. package/dist/xstate-dev.esm.js +0 -42
  108. package/dist/xstate-dev.umd.min.js +0 -2
  109. package/dist/xstate-dev.umd.min.js.map +0 -1
  110. package/dist/xstate-guards.cjs.d.mts +0 -2
  111. package/dist/xstate-guards.cjs.d.ts +0 -2
  112. package/dist/xstate-guards.cjs.js +0 -14
  113. package/dist/xstate-guards.cjs.mjs +0 -7
  114. package/dist/xstate-guards.development.cjs.js +0 -14
  115. package/dist/xstate-guards.development.cjs.mjs +0 -7
  116. package/dist/xstate-guards.development.esm.js +0 -2
  117. package/dist/xstate-guards.esm.js +0 -2
  118. package/dist/xstate-guards.umd.min.js +0 -2
  119. package/dist/xstate-guards.umd.min.js.map +0 -1
@@ -2,12 +2,8 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var dist_xstateActors = require('./xstate-actors.development.cjs.js');
6
- var dist_xstateGuards = require('./raise-964cd4e9.development.cjs.js');
7
- var StateMachine = require('./StateMachine-be2e51a3.development.cjs.js');
8
- var assign = require('./assign-927227d6.development.cjs.js');
9
- var log = require('./log-029f180b.development.cjs.js');
10
- require('./xstate-dev.development.cjs.js');
5
+ var dist_xstateActors = require('./index-c35824b4.development.cjs.js');
6
+ var StateMachine = require('./StateMachine-6e063ac1.development.cjs.js');
11
7
 
12
8
  /**
13
9
  * Asserts that the given event object is of the specified type or types. Throws
@@ -33,8 +29,8 @@ require('./xstate-dev.development.cjs.js');
33
29
  * ```
34
30
  */
35
31
  function assertEvent(event, type) {
36
- const types = dist_xstateGuards.toArray(type);
37
- const matches = types.some(descriptor => dist_xstateGuards.matchesEventDescriptor(event.type, descriptor));
32
+ const types = dist_xstateActors.toArray(type);
33
+ const matches = types.some(descriptor => dist_xstateActors.matchesEventDescriptor(event.type, descriptor));
38
34
  if (!matches) {
39
35
  const typesText = types.length === 1 ? `type matching "${types[0]}"` : `one of types matching "${types.join('", "')}"`;
40
36
  throw new Error(`Expected event ${JSON.stringify(event)} to have ${typesText}`);
@@ -83,8 +79,1004 @@ function assertEvent(event, type) {
83
79
  * @param options DEPRECATED: use `setup({ ... })` or `machine.provide({ ... })`
84
80
  * to provide machine implementations instead.
85
81
  */
86
- function createMachine(config, implementations) {
87
- return new StateMachine.StateMachine(config, implementations);
82
+ // Overload 1: With schemas.context — context type inferred from schema
83
+
84
+ // Overload 2: Without schemas.context — context type inferred from config.context
85
+
86
+ // Implementation
87
+ function createMachine(config) {
88
+ return new StateMachine.StateMachine(config);
89
+ }
90
+ function createStateConfig(config) {
91
+ return config;
92
+ }
93
+
94
+ function delayToMs(delay) {
95
+ const parsedDelay = dist_xstateActors.parseDelayToMilliseconds(delay);
96
+ if (parsedDelay !== undefined) return parsedDelay;
97
+ return typeof delay === 'string' ? parseFloat(delay) || 0 : delay;
98
+ }
99
+
100
+ /**
101
+ * Isolated executable-content block. Errors in nested actions stop this block
102
+ * but do not propagate to the surrounding action list. Used to model SCXML's
103
+ * separate <onentry>/<onexit> blocks: each is its own block per spec.
104
+ */
105
+
106
+ function isUnserializableMarker(value) {
107
+ return !!value && typeof value === 'object' && '$unserializable' in value && typeof value.$unserializable === 'string';
108
+ }
109
+ function assertNoUnresolvedMarkers(value, path = '$') {
110
+ if (isUnserializableMarker(value)) {
111
+ if (value.$unserializable === 'actor' && path.endsWith('.src')) {
112
+ return;
113
+ }
114
+ throw new Error(`Unresolved ${value.$unserializable} at ${path}`);
115
+ }
116
+ if (!value || typeof value !== 'object') {
117
+ return;
118
+ }
119
+ if (Array.isArray(value)) {
120
+ value.forEach((item, index) => assertNoUnresolvedMarkers(item, `${path}[${index}]`));
121
+ return;
122
+ }
123
+ for (const key of Object.keys(value)) {
124
+ const child = value[key];
125
+ const isImplementationMarker = path === '$' && (key === 'actions' || key === 'guards' || key === 'actors' || key === 'delays' || key === 'schemas');
126
+ if (isImplementationMarker) {
127
+ continue;
128
+ }
129
+ assertNoUnresolvedMarkers(child, `${path}.${key}`);
130
+ }
131
+ }
132
+ function assertRootImplementationMarkers(json, implementations) {
133
+ for (const kind of ['actions', 'guards', 'actors', 'delays']) {
134
+ const map = json[kind];
135
+ if (!map) {
136
+ continue;
137
+ }
138
+ for (const key of Object.keys(map)) {
139
+ const value = map[key];
140
+ if (!isUnserializableMarker(value)) {
141
+ continue;
142
+ }
143
+ if (!implementations[kind]?.[key]) {
144
+ throw new Error(`Missing ${kind}.${key}`);
145
+ }
146
+ }
147
+ }
148
+ }
149
+ function extractSerializableDelays(delays) {
150
+ const result = {};
151
+ if (!delays) {
152
+ return result;
153
+ }
154
+ for (const key of Object.keys(delays)) {
155
+ const value = delays[key];
156
+ if (typeof value === 'number') {
157
+ result[key] = value;
158
+ }
159
+ }
160
+ return result;
161
+ }
162
+ function mergeImplementations(json, implementations) {
163
+ return {
164
+ actions: implementations.actions ?? {},
165
+ guards: implementations.guards ?? {},
166
+ actors: implementations.actors ?? {},
167
+ delays: {
168
+ ...extractSerializableDelays(json.delays),
169
+ ...(implementations.delays ?? {})
170
+ }
171
+ };
172
+ }
173
+ let _scxmlSessionId = 'session_' + Math.random().toString(36).slice(2);
174
+ let _scxmlMachineName = '';
175
+
176
+ /** Evaluates an SCXML expression with context variables available via `with`. */
177
+ function evaluateExpr(context, expr, event, self) {
178
+ const fnBody = `
179
+ with (context) {
180
+ return (${expr});
181
+ }
182
+ `.trim();
183
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
184
+ const fn = new Function('context', '_event', '_sessionid', '_name', '_ioprocessors', 'In', fnBody);
185
+ const In = stateId => {
186
+ if (!self) return false;
187
+ try {
188
+ const snap = self.getSnapshot?.();
189
+ if (snap?._nodes) {
190
+ const sanitized = stateId.replace(/\./g, '$');
191
+ return snap._nodes.some(node => node.id === sanitized || node.id.endsWith('.' + sanitized));
192
+ }
193
+ } catch {
194
+ // ignore
195
+ }
196
+ return false;
197
+ };
198
+ const SCXML_ORIGIN = '#_scxml_session';
199
+ const SCXML_ORIGIN_TYPE = 'http://www.w3.org/TR/scxml/#SCXMLEventProcessor';
200
+ const isExternal = event && event._scxmlExternal;
201
+ const isInitEvent = event && event.type === '@xstate.init';
202
+ // Per SCXML spec, _event has these fields. Fields that don't apply to a
203
+ // particular event are present with value `undefined` (so `'name' in _event`
204
+ // is true but `typeof _event.name === 'undefined'` for unset fields).
205
+ // For xstate.done.* events, _event.data is the `output` payload (matches
206
+ // SCXML's done event semantics where data is the donedata).
207
+ const isDoneEvent = event && typeof event.type === 'string' && event.type.startsWith('xstate.done.');
208
+ const explicitType = event && event._scxmlEventType;
209
+ const scxmlEvent = event && !isInitEvent ? {
210
+ name: event.type,
211
+ type: explicitType || (isExternal ? 'external' : 'internal'),
212
+ sendid: event._scxmlSendId,
213
+ origin: isExternal ? SCXML_ORIGIN : undefined,
214
+ origintype: isExternal ? SCXML_ORIGIN_TYPE : undefined,
215
+ invokeid: event._scxmlInvokeId,
216
+ data: isDoneEvent ? event.output : event._scxmlEventData !== undefined ? event._scxmlEventData : event
217
+ } : undefined;
218
+ const result = fn(context, scxmlEvent, _scxmlSessionId, _scxmlMachineName, {
219
+ scxml: {
220
+ location: `#_scxml_${_scxmlSessionId}`
221
+ }
222
+ }, In);
223
+ return result;
224
+ }
225
+
226
+ /** Executes an SCXML script block and returns updated context values. */
227
+ function executeScript(context, code) {
228
+ // Create a proxy to track which properties are modified
229
+ const updates = {};
230
+ const contextKeys = Object.keys(context);
231
+
232
+ // Build variable declarations and reassignment capture
233
+ const varDeclarations = contextKeys.map(k => `let ${k} = context.${k};`).join('\n');
234
+ const captureUpdates = contextKeys.map(k => `updates.${k} = ${k};`).join('\n');
235
+ const fnBody = `
236
+ ${varDeclarations}
237
+ ${code}
238
+ ${captureUpdates}
239
+ return updates;
240
+ `;
241
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
242
+ const fn = new Function('context', 'updates', fnBody);
243
+ return fn(context, updates);
244
+ }
245
+ function createMachineFromConfig(json, implementations = {}) {
246
+ assertRootImplementationMarkers(json, implementations);
247
+ assertNoUnresolvedMarkers(json);
248
+ const resolvedImplementations = mergeImplementations(json, implementations);
249
+
250
+ // Pending transition actions: set by .to functions, consumed by entry functions.
251
+ // This bridges SCXML's exit→transition→entry action ordering with XState's
252
+ // .to function receiving pre-exit context.
253
+ // Map keyed by target state ID so parallel transitions don't overwrite each other.
254
+ const pendingTransitionActionsMap = {};
255
+
256
+ // Ordered queue of ALL transition actions (targeted + targetless) for parallel
257
+ // context sharing. In SCXML, all transition actions execute sequentially in
258
+ // document order with a shared evolving data model.
259
+ const allTransitionActions = [];
260
+ // Pre-transition context saved when a targetless .to executes. Used by entry
261
+ // functions to re-execute all transition actions from scratch when parallel
262
+ // targetless transitions coexist with targeted transitions.
263
+ let contextBeforeTargetless = null;
264
+ // Platform errors collected during transition selection (e.g., failing
265
+ // <transition cond>). Drained by the next state's entry into the internal
266
+ // event queue so they're processed before any external events.
267
+ const pendingPlatformErrors = [];
268
+ function iterNode(node, nodeKey) {
269
+ const originalEntryActions = node.entry;
270
+ const stateId = node.id || nodeKey;
271
+
272
+ // Wrap entry to first execute any pending transition actions, then normal entry.
273
+ // This ensures SCXML execution order: exit → transition_actions → entry_actions
274
+ // because pending transition actions are set by .to (before exit) but executed
275
+ // in entry (after exit), thus seeing post-exit context.
276
+ const entryFn = (x, enq) => {
277
+ // Drain any platform errors queued during transition selection (e.g.,
278
+ // failing <transition cond="..."> evaluations). These must be raised
279
+ // internally so they're processed before subsequent external events.
280
+ while (pendingPlatformErrors.length) {
281
+ const err = pendingPlatformErrors.shift();
282
+ enq.raise({
283
+ type: 'error.execution',
284
+ _scxmlEventType: 'platform',
285
+ _scxmlEventData: err
286
+ });
287
+ }
288
+ let context;
289
+
290
+ // If targetless transitions were interleaved with targeted transitions
291
+ // (parallel state), re-execute ALL transition actions from the original
292
+ // pre-transition context. This overrides any .to-produced context with
293
+ // the correct SCXML document-order sequential execution.
294
+ // Only trigger when BOTH targeted (pending in map) and targetless fired
295
+ // in the same microstep — prevents stale data from previous events.
296
+ if (contextBeforeTargetless && allTransitionActions.length > 0 && Object.keys(pendingTransitionActionsMap).length > 0) {
297
+ let ctx = contextBeforeTargetless;
298
+ for (const actions of allTransitionActions) {
299
+ const mergedX = {
300
+ ...x,
301
+ context: ctx
302
+ };
303
+ const result = executeActions(actions, mergedX, enq);
304
+ if (result.context) {
305
+ ctx = result.context;
306
+ }
307
+ }
308
+ allTransitionActions.length = 0;
309
+ contextBeforeTargetless = null;
310
+ // Clear per-target map since we re-processed everything
311
+ for (const key of Object.keys(pendingTransitionActionsMap)) {
312
+ delete pendingTransitionActionsMap[key];
313
+ }
314
+ context = ctx;
315
+ } else {
316
+ // Normal path: consume pending transition actions for THIS state.
317
+ // In parallel states, each target gets its own pending actions.
318
+ const transActions = stateId ? pendingTransitionActionsMap[stateId] : undefined;
319
+ if (transActions) {
320
+ delete pendingTransitionActionsMap[stateId];
321
+ const result = executeActions(transActions, x, enq);
322
+ if (result.context) {
323
+ context = result.context;
324
+ }
325
+ }
326
+ // Clear stale targetless data from previous microsteps
327
+ contextBeforeTargetless = null;
328
+ allTransitionActions.length = 0;
329
+ }
330
+
331
+ // Execute normal entry actions
332
+ if (originalEntryActions?.length) {
333
+ const mergedX = context ? {
334
+ ...x,
335
+ context
336
+ } : x;
337
+ const result = executeActions(originalEntryActions, mergedX, enq);
338
+ if (result.context) {
339
+ context = result.context;
340
+ }
341
+ }
342
+ return {
343
+ context
344
+ };
345
+ };
346
+ const nodeConfig = {
347
+ id: node.id,
348
+ initial: node.initial,
349
+ type: node.type,
350
+ history: node.history,
351
+ target: node.target,
352
+ description: node.description,
353
+ tags: node.tags,
354
+ input: node.input,
355
+ timeout: node.timeout,
356
+ states: node.states ? Object.entries(node.states).reduce((acc, [key, value]) => {
357
+ acc[key] = iterNode(value, key);
358
+ return acc;
359
+ }, {}) : undefined,
360
+ on: node.on ? Object.entries(node.on).reduce((acc, [key, value]) => {
361
+ acc[key] = getTransitionConfig(value);
362
+ return acc;
363
+ }, {}) : undefined,
364
+ always: node.always ? getTransitionConfig(node.always) : undefined,
365
+ choice: node.choice ? getTransitionConfig(node.choice) : undefined,
366
+ route: node.route,
367
+ after: node.after ? Object.entries(node.after).reduce((acc, [key, value]) => {
368
+ acc[key] = getTransitionConfig(value);
369
+ return acc;
370
+ }, {}) : undefined,
371
+ onTimeout: node.onTimeout ? getTransitionConfig(node.onTimeout) : undefined,
372
+ entry: entryFn,
373
+ exit: node.exit ? iterActions(node.exit) : undefined,
374
+ invoke: node.invoke ? iterInvokeConfigs(node.invoke) : undefined,
375
+ meta: node.meta,
376
+ output: node._scxmlDonedata ? makeDonedataOutput(node._scxmlDonedata) : node.output
377
+ };
378
+ return nodeConfig;
379
+ }
380
+ function makeDonedataOutput(donedata) {
381
+ return ({
382
+ context,
383
+ event,
384
+ self
385
+ }) => {
386
+ try {
387
+ if (donedata.params && donedata.params.length) {
388
+ const out = {};
389
+ for (const param of donedata.params) {
390
+ out[param.name] = evaluateExpr(context, param.expr, event, self);
391
+ }
392
+ return out;
393
+ }
394
+ if (donedata.contentExpr !== undefined) {
395
+ return evaluateExpr(context, donedata.contentExpr, event, self);
396
+ }
397
+ if (donedata.contentText !== undefined) {
398
+ // Try parsing as a JS expression (number/object/array literals);
399
+ // fall back to the raw string.
400
+ try {
401
+ return evaluateExpr(context, donedata.contentText, event, self);
402
+ } catch {
403
+ return donedata.contentText;
404
+ }
405
+ }
406
+ } catch (err) {
407
+ // Per SCXML, donedata expression errors raise error.execution and
408
+ // result in undefined event.data. Queue for the next entry to drain.
409
+ const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
410
+ pendingPlatformErrors.push({
411
+ tagname: 'donedata',
412
+ message,
413
+ line: NaN,
414
+ column: NaN,
415
+ reason: message
416
+ });
417
+ }
418
+ return undefined;
419
+ };
420
+ }
421
+ function iterInvokeConfigs(invokes) {
422
+ const invokeArray = Array.isArray(invokes) ? invokes : [invokes];
423
+ return invokeArray.map(inv => {
424
+ const extInv = inv;
425
+ // Create child machine from nested SCXML JSON
426
+ let src;
427
+ if (extInv._nestedMachineJSON) {
428
+ src = createMachineFromConfig(extInv._nestedMachineJSON, resolvedImplementations);
429
+ } else if (isUnserializableMarker(inv.src)) {
430
+ src = inv.src.id ? resolvedImplementations.actors[inv.src.id] : undefined;
431
+ if (!src) {
432
+ throw new Error(`Missing actors.${inv.src.id ?? ''}`);
433
+ }
434
+ } else {
435
+ src = inv.src;
436
+ }
437
+ return {
438
+ src,
439
+ id: inv.id,
440
+ systemId: inv.systemId,
441
+ input: inv.input,
442
+ onDone: inv.onDone ? getTransitionConfig(inv.onDone) : undefined,
443
+ onError: inv.onError ? getTransitionConfig(inv.onError) : undefined,
444
+ onSnapshot: inv.onSnapshot ? getTransitionConfig(inv.onSnapshot) : undefined,
445
+ timeout: inv.timeout,
446
+ onTimeout: inv.onTimeout ? getTransitionConfig(inv.onTimeout) : undefined
447
+ };
448
+ });
449
+ }
450
+
451
+ /**
452
+ * Raise an SCXML error.execution platform event onto the internal queue. Per
453
+ * spec, error.execution is type='platform' and _event.data contains error
454
+ * info. The surrounding block of executable content stops.
455
+ */
456
+ function raiseErrorExecution(state, tagname, err) {
457
+ const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
458
+ state.enq.raise({
459
+ type: 'error.execution',
460
+ _scxmlEventType: 'platform',
461
+ _scxmlEventData: {
462
+ tagname,
463
+ message,
464
+ line: NaN,
465
+ column: NaN,
466
+ reason: message
467
+ }
468
+ });
469
+ state.errored = true;
470
+ }
471
+ /** Execute an array of SCXML action JSON descriptors with context and enqueue. */
472
+ function executeActions(actions, x, enq, parentState) {
473
+ const state = parentState ?? {
474
+ enq,
475
+ errored: false
476
+ };
477
+ let context;
478
+ for (const action of actions) {
479
+ if (state.errored) break;
480
+ if (isBuiltInActionJSON(action)) {
481
+ switch (action.type) {
482
+ case '@xstate.raise':
483
+ {
484
+ // Tag as external if it has a delay (from <send>, not <raise>).
485
+ // Attach _scxmlSendId so _event.sendid can be read in expressions.
486
+ const isExternal = action.delay !== undefined;
487
+ const event = isExternal || action.id !== undefined ? {
488
+ ...action.event,
489
+ ...(isExternal ? {
490
+ _scxmlExternal: true
491
+ } : {}),
492
+ ...(action.id !== undefined ? {
493
+ _scxmlSendId: action.id
494
+ } : {})
495
+ } : action.event;
496
+ enq.raise(event, {
497
+ id: action.id,
498
+ delay: action.delay
499
+ });
500
+ break;
501
+ }
502
+ case '@xstate.cancel':
503
+ enq.cancel(action.id);
504
+ break;
505
+ case '@xstate.log':
506
+ enq.log(...action.args);
507
+ break;
508
+ case '@xstate.emit':
509
+ enq.emit(action.event);
510
+ break;
511
+ case '@xstate.assign':
512
+ context ??= {};
513
+ Object.assign(context, action.context);
514
+ break;
515
+ default:
516
+ throw new Error(`Unknown built-in action: ${action.type}`);
517
+ }
518
+ } else if (action.type === 'scxml.assign') {
519
+ const scxmlAction = action;
520
+ const mergedContext = {
521
+ ...x.context,
522
+ ...context
523
+ };
524
+ let value;
525
+ try {
526
+ value = evaluateExpr(mergedContext, scxmlAction.expr, x.event, x.self);
527
+ } catch (err) {
528
+ raiseErrorExecution(state, 'assign', err);
529
+ continue;
530
+ }
531
+ // Per SCXML, assigning to a location that doesn't exist on the
532
+ // datamodel is an error. Allow setting an own property via context;
533
+ // detect deep paths (a.b.c) which we don't support.
534
+ if (scxmlAction.location.includes('.')) {
535
+ raiseErrorExecution(state, 'assign', new Error(`Invalid assign location: ${scxmlAction.location}`));
536
+ continue;
537
+ }
538
+ context ??= {};
539
+ context[scxmlAction.location] = value;
540
+ } else if (action.type === 'scxml.raise') {
541
+ const scxmlAction = action;
542
+ const mergedContext = {
543
+ ...x.context,
544
+ ...context
545
+ };
546
+ let eventType;
547
+ let eventData;
548
+ let target;
549
+ let delay;
550
+ try {
551
+ eventType = scxmlAction.eventexpr ? evaluateExpr(mergedContext, scxmlAction.eventexpr, x.event, x.self) : scxmlAction.event || 'unknown';
552
+ eventData = {
553
+ type: eventType
554
+ };
555
+ if (scxmlAction.params) {
556
+ for (const param of scxmlAction.params) {
557
+ eventData[param.name] = evaluateExpr(mergedContext, param.expr, x.event, x.self);
558
+ }
559
+ }
560
+ target = scxmlAction.targetexpr ? evaluateExpr(mergedContext, scxmlAction.targetexpr, x.event, x.self) : scxmlAction.target;
561
+ const isInternalTarget = target === '#_internal';
562
+ delay = scxmlAction.delayexpr ? delayToMs(evaluateExpr(mergedContext, scxmlAction.delayexpr, x.event, x.self)) : isInternalTarget ? undefined : scxmlAction.delay;
563
+ } catch (err) {
564
+ raiseErrorExecution(state, 'send', err);
565
+ continue;
566
+ }
567
+ const isInternalTarget = target === '#_internal';
568
+ const isParentTarget = target === '#_parent';
569
+
570
+ // Validate target. SCXML targets must be '#_internal', '#_parent',
571
+ // or '#_<id>'. A bare string without the '#_' prefix is invalid and
572
+ // raises error.execution.
573
+ if (typeof target === 'string' && target.length > 0 && !target.startsWith('#_')) {
574
+ raiseErrorExecution(state, 'send', new Error(`Invalid send target: ${target}`));
575
+ continue;
576
+ }
577
+ // Attach send id so _event.sendid can be read in expressions.
578
+ if (scxmlAction.id !== undefined) {
579
+ eventData._scxmlSendId = scxmlAction.id;
580
+ }
581
+ // Resolve target at runtime: parent, child, or self
582
+ if (isParentTarget && x.parent) {
583
+ // Send to parent via sendTo; pass delay if present
584
+ enq.sendTo(x.parent, eventData, delay !== undefined ? {
585
+ delay
586
+ } : undefined);
587
+ } else if (typeof target === 'string' && target.startsWith('#_') && !isParentTarget && !isInternalTarget) {
588
+ // #_<invokeId> → try to send to child actor, fall back to self-raise
589
+ const childId = target.slice(2); // strip '#_'
590
+ const childRef = x.children?.[childId];
591
+ if (childRef) {
592
+ enq.sendTo(childRef, eventData);
593
+ } else {
594
+ // Not a known child (e.g. #_scxml_sessionid) → self-raise
595
+ if (delay !== undefined) {
596
+ eventData._scxmlExternal = true;
597
+ }
598
+ enq.raise(eventData, {
599
+ id: scxmlAction.id,
600
+ delay
601
+ });
602
+ }
603
+ } else {
604
+ // Self-send or no special target: raise as external event
605
+ if (delay !== undefined) {
606
+ eventData._scxmlExternal = true;
607
+ }
608
+ enq.raise(eventData, {
609
+ id: scxmlAction.id,
610
+ delay
611
+ });
612
+ }
613
+ } else if (action.type === 'scxml.cancel') {
614
+ const scxmlAction = action;
615
+ const mergedContext = {
616
+ ...x.context,
617
+ ...context
618
+ };
619
+ try {
620
+ const sendId = evaluateExpr(mergedContext, scxmlAction.sendidexpr, x.event, x.self);
621
+ if (sendId) {
622
+ enq.cancel(sendId);
623
+ }
624
+ } catch {
625
+ // ignore evaluation errors
626
+ }
627
+ } else if (action.type === 'scxml.script') {
628
+ const scxmlAction = action;
629
+ const mergedContext = {
630
+ ...x.context,
631
+ ...context
632
+ };
633
+ try {
634
+ const updatedContext = executeScript(mergedContext, scxmlAction.code);
635
+ context ??= {};
636
+ Object.assign(context, updatedContext);
637
+ } catch (err) {
638
+ raiseErrorExecution(state, 'script', err);
639
+ }
640
+ } else if (action.type === 'scxml.foreach') {
641
+ const scxmlAction = action;
642
+ const mergedContext = {
643
+ ...x.context,
644
+ ...context
645
+ };
646
+ // SCXML: `item` (and `index` if present) must be valid datamodel
647
+ // locations — i.e. legal identifiers. Reject things like 'continue'
648
+ // (string literal) or '7' (number).
649
+ const isLegalIdentifier = s => /^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(s);
650
+ if (!isLegalIdentifier(scxmlAction.item) || scxmlAction.index && !isLegalIdentifier(scxmlAction.index)) {
651
+ raiseErrorExecution(state, 'foreach', new Error('foreach item/index is not a legal location'));
652
+ continue;
653
+ }
654
+ let arr;
655
+ try {
656
+ arr = evaluateExpr(mergedContext, scxmlAction.array, x.event, x.self);
657
+ } catch (err) {
658
+ raiseErrorExecution(state, 'foreach', err);
659
+ continue;
660
+ }
661
+ if (!Array.isArray(arr)) {
662
+ raiseErrorExecution(state, 'foreach', new Error('foreach array is not iterable'));
663
+ continue;
664
+ }
665
+ context ??= {};
666
+ for (let i = 0; i < arr.length; i++) {
667
+ context[scxmlAction.item] = arr[i];
668
+ if (scxmlAction.index) {
669
+ context[scxmlAction.index] = i;
670
+ }
671
+ const iterX = {
672
+ ...x,
673
+ context: {
674
+ ...x.context,
675
+ ...context
676
+ }
677
+ };
678
+ const iterResult = executeActions(scxmlAction.actions, iterX, enq, state);
679
+ if (iterResult.context) {
680
+ Object.assign(context, iterResult.context);
681
+ }
682
+ if (state.errored) break;
683
+ }
684
+ } else if (action.type === 'scxml.block') {
685
+ const scxmlAction = action;
686
+ const blockX = {
687
+ ...x,
688
+ context: {
689
+ ...x.context,
690
+ ...context
691
+ }
692
+ };
693
+ // Block has its own ExecState — errors don't propagate to parent.
694
+ const blockResult = executeActions(scxmlAction.actions, blockX, enq);
695
+ if (blockResult.context) {
696
+ context ??= {};
697
+ for (const key of Object.keys(blockResult.context)) {
698
+ if (blockResult.context[key] !== x.context[key]) {
699
+ context[key] = blockResult.context[key];
700
+ }
701
+ }
702
+ }
703
+ } else if (action.type === 'scxml.if') {
704
+ const scxmlAction = action;
705
+ const mergedContext = {
706
+ ...x.context,
707
+ ...context
708
+ };
709
+ for (const branch of scxmlAction.branches) {
710
+ let condResult;
711
+ if (branch.cond) {
712
+ try {
713
+ condResult = !!evaluateExpr(mergedContext, branch.cond, x.event, x.self);
714
+ } catch (err) {
715
+ raiseErrorExecution(state, 'if', err);
716
+ condResult = false;
717
+ }
718
+ } else {
719
+ condResult = true;
720
+ }
721
+ if (condResult) {
722
+ if (branch.actions.length) {
723
+ const branchX = {
724
+ ...x,
725
+ context: mergedContext
726
+ };
727
+ const branchResult = executeActions(branch.actions, branchX, enq, state);
728
+ if (branchResult.context) {
729
+ context ??= {};
730
+ for (const key of Object.keys(branchResult.context)) {
731
+ if (branchResult.context[key] !== x.context[key]) {
732
+ context[key] = branchResult.context[key];
733
+ }
734
+ }
735
+ }
736
+ }
737
+ break;
738
+ }
739
+ }
740
+ } else {
741
+ enq(x.actions[action.type], action.params);
742
+ }
743
+ }
744
+ return {
745
+ context: context ? {
746
+ ...x.context,
747
+ ...context
748
+ } : undefined,
749
+ errored: state.errored
750
+ };
751
+ }
752
+ function iterActions(actions) {
753
+ return (x, enq) => executeActions(actions, x, enq);
754
+ }
755
+ function getTransitionConfig(transition) {
756
+ const transitions = Array.isArray(transition) ? transition : [transition];
757
+
758
+ // Return an array of transition configs. Each SCXML transition becomes
759
+ // a separate XState transition with its own guard and optional .to.
760
+ // This ensures guards are evaluated by XState's evaluateCandidate (once,
761
+ // with pre-exit context) and NOT re-evaluated in computeEntrySet.
762
+ return transitions.map(t => {
763
+ const target = Array.isArray(t.target) ? t.target[0] : t.target;
764
+ const targetConfig = t.target;
765
+
766
+ // No guard and no actions: simple static config
767
+ if (!t.guard && !t.actions?.length) {
768
+ return {
769
+ target: targetConfig,
770
+ description: t.description,
771
+ reenter: t.reenter,
772
+ meta: t.meta,
773
+ input: t.input
774
+ };
775
+ }
776
+
777
+ // Guard but no actions: static config with guard
778
+ if (t.guard && !t.actions?.length) {
779
+ return {
780
+ target: targetConfig,
781
+ guard: t.guard,
782
+ description: t.description,
783
+ reenter: t.reenter,
784
+ meta: t.meta,
785
+ input: t.input
786
+ };
787
+ }
788
+
789
+ // Targetless transitions: execute actions directly in .to AND track for
790
+ // parallel re-execution. For non-parallel, .to result is used directly.
791
+ // For parallel (entries exist), first entry re-executes all in document order.
792
+ if (!target) {
793
+ return {
794
+ guard: t.guard,
795
+ description: t.description,
796
+ reenter: t.reenter,
797
+ meta: t.meta,
798
+ input: t.input,
799
+ to: (x, enq) => {
800
+ if (t.actions?.length) {
801
+ // Track for parallel re-execution (dedup by reference)
802
+ if (!allTransitionActions.includes(t.actions)) {
803
+ allTransitionActions.push(t.actions);
804
+ }
805
+ // Save pre-transition context for parallel override
806
+ contextBeforeTargetless ??= x.context;
807
+ // Execute immediately (fallback for non-parallel case)
808
+ const result = executeActions(t.actions, x, enq);
809
+ if (result.context) {
810
+ return {
811
+ context: result.context
812
+ };
813
+ }
814
+ }
815
+ return {};
816
+ }
817
+ };
818
+ }
819
+
820
+ // Has target + actions: use guard (for XState evaluation) + .to (for pending actions).
821
+ // The .to function does NOT re-check the guard — XState's evaluateCandidate
822
+ // already validated it. The .to just stores actions for entry to execute.
823
+ // Map by target state ID so parallel transitions each get their own actions.
824
+ return {
825
+ guard: t.guard,
826
+ description: t.description,
827
+ reenter: t.reenter,
828
+ meta: t.meta,
829
+ input: t.input,
830
+ to: (_x, _enq) => {
831
+ if (t.actions?.length) {
832
+ const targetId = target.replace(/^#/, '');
833
+ pendingTransitionActionsMap[targetId] = t.actions;
834
+ // Track for parallel re-execution (dedup by reference)
835
+ if (!allTransitionActions.includes(t.actions)) {
836
+ allTransitionActions.push(t.actions);
837
+ }
838
+ }
839
+ return {
840
+ target: targetConfig,
841
+ reenter: t.reenter
842
+ };
843
+ }
844
+ };
845
+ });
846
+ }
847
+ const rootNodeConfig = iterNode(json);
848
+ const contextConfig = json.context ? {
849
+ context: json.context
850
+ } : {};
851
+ _scxmlMachineName = json.id || '';
852
+ _scxmlSessionId = 'session_' + Math.random().toString(36).slice(2);
853
+ const machine = createMachine({
854
+ ...rootNodeConfig,
855
+ ...contextConfig,
856
+ version: json.version
857
+ });
858
+
859
+ // Register SCXML guard implementations
860
+ const providedGuards = {
861
+ 'scxml.cond': ({
862
+ context,
863
+ event,
864
+ self
865
+ }, params) => {
866
+ const expr = params?.expr;
867
+ if (!expr) return true;
868
+ try {
869
+ return !!evaluateExpr(context, expr, event, self);
870
+ } catch (err) {
871
+ // Per SCXML spec, a cond that fails to evaluate is treated as false
872
+ // AND raises error.execution. We can't enqueue from a guard, so
873
+ // queue it for the next entry to drain.
874
+ const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
875
+ pendingPlatformErrors.push({
876
+ tagname: 'cond',
877
+ message,
878
+ line: NaN,
879
+ column: NaN,
880
+ reason: message
881
+ });
882
+ return false;
883
+ }
884
+ },
885
+ 'xstate.stateIn': (args, params) => {
886
+ const stateId = params?.stateId;
887
+ if (!stateId) return false;
888
+ const normalizedId = stateId.replace(/^#/, '');
889
+ const snapshot = args._snapshot;
890
+ if (snapshot?._nodes) {
891
+ return snapshot._nodes.some(node => node.id === normalizedId);
892
+ }
893
+ return false;
894
+ },
895
+ 'xstate.not': (args, params) => {
896
+ const inner = params?.guard;
897
+ const innerImpl = inner && providedGuards[inner.type];
898
+ if (!innerImpl) {
899
+ throw new Error(`Guard '${inner?.type}' referenced by 'xstate.not' is not implemented.`);
900
+ }
901
+ return !innerImpl(args, inner.params);
902
+ }
903
+ };
904
+ const provided = machine.provide({
905
+ actions: resolvedImplementations.actions,
906
+ actors: resolvedImplementations.actors,
907
+ guards: {
908
+ ...providedGuards,
909
+ ...resolvedImplementations.guards
910
+ },
911
+ delays: resolvedImplementations.delays
912
+ });
913
+
914
+ // Keep the original JSON so `serializeMachine(machine)`
915
+ // round-trip losslessly.
916
+ provided._json = json;
917
+ return provided;
918
+ }
919
+ function isBuiltInActionJSON(action) {
920
+ return action.type.startsWith('@xstate.');
921
+ }
922
+
923
+ /**
924
+ * Machine-definition serialization.
925
+ *
926
+ * `machineConfigToJSON` converts a machine config into a JSON-safe structure
927
+ * (the `MachineJSON` shape accepted by `createMachineFromConfig`). The boundary
928
+ * between serializable structure and runtime implementations is explicit: any
929
+ * value that cannot be represented as data (inline functions, actor logic,
930
+ * runtime schemas) is replaced with an `{ "$unserializable": <kind> }` marker
931
+ * rather than silently dropped.
932
+ *
933
+ * A machine definition is fully portable iff its JSON contains no
934
+ * `$unserializable` markers; reviving a definition that has markers requires
935
+ * re-providing those implementations (e.g. via `machine.provide(...)` or the
936
+ * `actions`/`guards`/`actors` maps on `createMachineFromConfig` revival).
937
+ */
938
+
939
+ /**
940
+ * Returns the JSON-serializable definition of a machine.
941
+ *
942
+ * Inline functions, actor logic, and runtime schemas cannot be represented as
943
+ * data; they appear as `{ "$unserializable": ... }` markers. A machine created
944
+ * via `createMachineFromConfig` returns its original JSON config (lossless
945
+ * round-trip):
946
+ *
947
+ * ```ts
948
+ * import { serializeMachine, createMachineFromConfig } from 'xstate';
949
+ *
950
+ * const json = serializeMachine(machine);
951
+ * const revived = createMachineFromConfig(
952
+ * JSON.parse(JSON.stringify(json))
953
+ * );
954
+ * ```
955
+ */
956
+ function serializeMachine(machine) {
957
+ return machine._json ?? machineConfigToJSON(machine.config);
958
+ }
959
+
960
+ /**
961
+ * State-node config keys that are part of the serializable definition. Keys
962
+ * outside this list (unknown/internal properties) are dropped.
963
+ */
964
+ const STATE_NODE_KEYS = ['id', 'key', 'type', 'history', 'description', 'target', 'version', 'initial', 'context', 'on', 'after', 'always', 'choice', 'route', 'onTimeout', 'timeout', 'entry', 'exit', 'meta', 'output', 'tags', 'input', '_scxmlDonedata'];
965
+ function marker(kind, id) {
966
+ const m = {
967
+ $unserializable: kind
968
+ };
969
+ if (id) {
970
+ m.id = id;
971
+ }
972
+ return m;
973
+ }
974
+
975
+ /** JSON-safe deep copy of a plain value; functions become markers. */
976
+ function valueToJSON(value) {
977
+ if (typeof value === 'function') {
978
+ return marker('function', value.name || undefined);
979
+ }
980
+ if (value === null || typeof value !== 'object') {
981
+ return typeof value === 'bigint' || typeof value === 'symbol' ? marker('value') : value;
982
+ }
983
+ if (Array.isArray(value)) {
984
+ return value.map(valueToJSON);
985
+ }
986
+ if (value.constructor !== Object && value.constructor !== undefined) {
987
+ // Class instances (actor logic, schemas, dates, ...) are not portable.
988
+ return marker('value');
989
+ }
990
+ const result = {};
991
+ for (const key of Object.keys(value)) {
992
+ const v = value[key];
993
+ if (v !== undefined) {
994
+ result[key] = valueToJSON(v);
995
+ }
996
+ }
997
+ return result;
998
+ }
999
+ function invokeToJSON(invoke) {
1000
+ if (Array.isArray(invoke)) {
1001
+ return invoke.map(invokeToJSON);
1002
+ }
1003
+ const def = invoke;
1004
+ const result = {};
1005
+ for (const key of Object.keys(def)) {
1006
+ const value = def[key];
1007
+ if (value === undefined) {
1008
+ continue;
1009
+ }
1010
+ result[key] = key === 'src' && typeof value !== 'string' ?
1011
+ // Actor logic keeps its dedicated marker kind so revival knows the
1012
+ // contract is an actor, not a plain function.
1013
+ marker('actor', value.id) : valueToJSON(value);
1014
+ }
1015
+ return result;
1016
+ }
1017
+ function implementationsToJSON(map, kind) {
1018
+ if (!map) {
1019
+ return undefined;
1020
+ }
1021
+ // Keys are preserved — they are the contract a revived machine must
1022
+ // fulfill via provide() — while values become markers (or stay, for
1023
+ // JSON-safe values like numeric delays).
1024
+ const result = {};
1025
+ for (const key of Object.keys(map)) {
1026
+ const value = map[key];
1027
+ result[key] = typeof value === 'function' ? marker('function', key) : value && typeof value === 'object' ? marker(kind, key) : value;
1028
+ }
1029
+ return result;
1030
+ }
1031
+ function stateNodeConfigToJSON(config) {
1032
+ const result = {};
1033
+ for (const key of STATE_NODE_KEYS) {
1034
+ const value = config[key];
1035
+ if (value !== undefined) {
1036
+ result[key] = valueToJSON(value);
1037
+ }
1038
+ }
1039
+ if (config.invoke !== undefined) {
1040
+ result.invoke = invokeToJSON(config.invoke);
1041
+ }
1042
+ if (config.states) {
1043
+ const states = {};
1044
+ for (const key of Object.keys(config.states)) {
1045
+ states[key] = stateNodeConfigToJSON(config.states[key]);
1046
+ }
1047
+ result.states = states;
1048
+ }
1049
+ return result;
1050
+ }
1051
+
1052
+ /**
1053
+ * Converts a machine config (as passed to `createMachine`) to its JSON-safe
1054
+ * definition. See module docs for the `$unserializable` marker contract.
1055
+ */
1056
+ function machineConfigToJSON(config) {
1057
+ const result = stateNodeConfigToJSON(config);
1058
+ if (config.internalEvents !== undefined) {
1059
+ result.internalEvents = valueToJSON(config.internalEvents);
1060
+ }
1061
+ if (config.schemas) {
1062
+ const schemas = {};
1063
+ for (const key of Object.keys(config.schemas)) {
1064
+ const value = config.schemas[key];
1065
+ if (value && typeof value === 'object' && !('~standard' in value)) {
1066
+ // Map-form schemas (events/emitted): preserve event-type keys.
1067
+ schemas[key] = implementationsToJSON(value, 'schema');
1068
+ } else {
1069
+ schemas[key] = marker('schema', key);
1070
+ }
1071
+ }
1072
+ result.schemas = schemas;
1073
+ }
1074
+ for (const key of ['actions', 'guards', 'actors', 'delays']) {
1075
+ if (config[key]) {
1076
+ result[key] = implementationsToJSON(config[key], key === 'actors' ? 'actor' : 'function');
1077
+ }
1078
+ }
1079
+ return result;
88
1080
  }
89
1081
 
90
1082
  /**
@@ -116,7 +1108,7 @@ function mapState(snapshot, mapper) {
116
1108
  return mapper;
117
1109
  };
118
1110
  const visited = new Set();
119
- for (const atomicNode of snapshot._nodes.filter(dist_xstateGuards.isAtomicStateNode)) {
1111
+ for (const atomicNode of snapshot._nodes.filter(dist_xstateActors.isAtomicStateNode)) {
120
1112
  let current = atomicNode;
121
1113
  while (current && !visited.has(current)) {
122
1114
  visited.add(current);
@@ -133,11 +1125,141 @@ function mapState(snapshot, mapper) {
133
1125
  return results;
134
1126
  }
135
1127
 
1128
+ /** The Standard Schema interface. */
1129
+
1130
+ /** A type-only Standard Schema produced by {@link types}. */
1131
+
1132
+ /**
1133
+ * Declares schema types for inference only — no runtime validation.
1134
+ *
1135
+ * Use in a machine's `schemas` when you want TypeScript types without pulling
1136
+ * in a schema library like Zod. The returned value is a valid Standard Schema
1137
+ * whose validation is the identity function, so it carries types but never
1138
+ * rejects input.
1139
+ *
1140
+ * @example
1141
+ *
1142
+ * ```ts
1143
+ * import { createMachine, types } from 'xstate';
1144
+ *
1145
+ * const machine = createMachine({
1146
+ * schemas: {
1147
+ * context: types<{ count: number }>(),
1148
+ * events: {
1149
+ * inc: types<{ by: number }>(),
1150
+ * reset: types<{}>()
1151
+ * }
1152
+ * },
1153
+ * context: { count: 0 }
1154
+ * // ...
1155
+ * });
1156
+ * ```
1157
+ */
1158
+ function types() {
1159
+ return {
1160
+ '~standard': {
1161
+ version: 1,
1162
+ vendor: 'xstate.types',
1163
+ validate: value => ({
1164
+ value: value
1165
+ })
1166
+ }
1167
+ };
1168
+ }
1169
+
1170
+ /** Returns true if the value is a type-only schema created by {@link types}. */
1171
+ function isTypeSchema(value) {
1172
+ return !!value && typeof value === 'object' && '~standard' in value && value['~standard'].vendor === 'xstate.types';
1173
+ }
1174
+
1175
+ // eslint-disable-next-line @typescript-eslint/no-namespace
1176
+
1177
+ /** State schema with optional schemas.input and nested states */
1178
+
1179
+ /** Extracts input type from a state schema */
1180
+
1181
+ /**
1182
+ * Converts SetupStateSchema to StateSchema with input types included. This
1183
+ * allows getInputs() to be strongly typed.
1184
+ */
1185
+
1186
+ /** Converts the root setup states config to a StateSchema. */
1187
+
1188
+ /** Machine config with typed state input */
1189
+
1190
+ /** States config type that provides typed input for known states */
1191
+
1192
+ /** State node config that recursively applies typed input for nested states */
1193
+
1194
+ /** Initial transition with typed input based on target state */
1195
+
1196
+ /** Return type of setup() */
1197
+
1198
+ /**
1199
+ * Sets up a state machine with state input schemas and other configuration.
1200
+ *
1201
+ * @example
1202
+ *
1203
+ * ```ts
1204
+ * import { setup } from 'xstate';
1205
+ * import z from 'zod';
1206
+ *
1207
+ * const s = setup({
1208
+ * states: {
1209
+ * loading: {
1210
+ * schemas: {
1211
+ * input: z.object({
1212
+ * userId: z.string()
1213
+ * })
1214
+ * }
1215
+ * }
1216
+ * }
1217
+ * });
1218
+ *
1219
+ * const machine = s.createMachine({
1220
+ * initial: {
1221
+ * target: 'loading',
1222
+ * input: { userId: '123' }
1223
+ * },
1224
+ * states: {
1225
+ * loading: {
1226
+ * entry: ({ input }) => {
1227
+ * console.log(input.userId);
1228
+ * }
1229
+ * }
1230
+ * }
1231
+ * });
1232
+ * ```
1233
+ */
1234
+ function setup(config = {}) {
1235
+ const {
1236
+ states = {},
1237
+ schemas
1238
+ } = config;
1239
+ return {
1240
+ createMachine(machineConfig) {
1241
+ const configSchemas = machineConfig.schemas;
1242
+ const mergedSchemas = schemas || configSchemas ? {
1243
+ ...configSchemas,
1244
+ ...schemas
1245
+ } : undefined;
1246
+ return new StateMachine.StateMachine(mergedSchemas ? {
1247
+ ...machineConfig,
1248
+ schemas: mergedSchemas
1249
+ } : machineConfig);
1250
+ },
1251
+ createStateConfig(stateConfig) {
1252
+ return stateConfig;
1253
+ },
1254
+ states
1255
+ };
1256
+ }
1257
+
136
1258
  /** @internal */
137
1259
  function createInertActorScope(actorLogic) {
138
- const self = dist_xstateGuards.createActor(actorLogic);
1260
+ const self = dist_xstateActors.createActor(actorLogic);
139
1261
  const inertActorScope = {
140
- self,
1262
+ self: self,
141
1263
  defer: () => {},
142
1264
  id: '',
143
1265
  logger: () => {},
@@ -192,60 +1314,8 @@ function getInitialSnapshot(actorLogic, ...[input]) {
192
1314
  function getNextSnapshot(actorLogic, snapshot, event) {
193
1315
  const inertActorScope = createInertActorScope(actorLogic);
194
1316
  inertActorScope.self._snapshot = snapshot;
195
- return actorLogic.transition(snapshot, event, inertActorScope);
196
- }
197
-
198
- // at the moment we allow extra actors - ones that are not specified by `children`
199
- // this could be reconsidered in the future
200
-
201
- // used to keep only StateSchema relevant keys
202
- // this helps with type serialization as it makes the inferred type much shorter when dealing with huge configs
203
-
204
- function setup({
205
- schemas,
206
- actors,
207
- actions,
208
- guards,
209
- delays
210
- }) {
211
- return {
212
- assign: assign.assign,
213
- sendTo: log.sendTo,
214
- raise: dist_xstateGuards.raise,
215
- log: log.log,
216
- cancel: dist_xstateGuards.cancel,
217
- stopChild: dist_xstateGuards.stopChild,
218
- enqueueActions: log.enqueueActions,
219
- emit: log.emit,
220
- spawnChild: dist_xstateGuards.spawnChild,
221
- createStateConfig: config => config,
222
- createAction: fn => fn,
223
- createMachine: config => createMachine({
224
- ...config,
225
- schemas
226
- }, {
227
- actors,
228
- actions,
229
- guards,
230
- delays
231
- }),
232
- extend: extended => setup({
233
- schemas,
234
- actors,
235
- actions: {
236
- ...actions,
237
- ...extended.actions
238
- },
239
- guards: {
240
- ...guards,
241
- ...extended.guards
242
- },
243
- delays: {
244
- ...delays,
245
- ...extended.delays
246
- }
247
- })
248
- };
1317
+ const transitionResult = actorLogic.transition(snapshot, event, inertActorScope);
1318
+ return Array.isArray(transitionResult) ? transitionResult[0] : transitionResult;
249
1319
  }
250
1320
 
251
1321
  // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
@@ -310,6 +1380,11 @@ class SimulatedClock {
310
1380
  }
311
1381
  }
312
1382
  this._flushing = false;
1383
+ // Check if new timeouts were added during the last iteration
1384
+ if (this._flushingInvalidated) {
1385
+ this._flushingInvalidated = false;
1386
+ this.flushTimeouts();
1387
+ }
313
1388
  }
314
1389
  increment(ms) {
315
1390
  this._now += ms;
@@ -351,6 +1426,138 @@ function toPromise(actor) {
351
1426
  });
352
1427
  }
353
1428
 
1429
+ // this is needed to make JSDoc `@link` work properly
1430
+
1431
+ /**
1432
+ * @remarks
1433
+ * `T | unknown` reduces to `unknown` and that can be problematic when it comes
1434
+ * to contextual typing. It especially is a problem when the union has a
1435
+ * function member, like here:
1436
+ *
1437
+ * ```ts
1438
+ * declare function test(
1439
+ * cbOrVal: ((arg: number) => unknown) | unknown
1440
+ * ): void;
1441
+ * test((arg) => {}); // oops, implicit any
1442
+ * ```
1443
+ *
1444
+ * This type can be used to avoid this problem. This union represents the same
1445
+ * value space as `unknown`.
1446
+ */
1447
+
1448
+ // https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
1449
+
1450
+ // @TODO: we can't use native `NoInfer` as we need those:
1451
+ // https://github.com/microsoft/TypeScript/pull/61092
1452
+ // https://github.com/microsoft/TypeScript/pull/61077
1453
+ // but even with those fixes native NoInfer still doesn't work - further issues have to be reproduced and fixed
1454
+
1455
+ /** @deprecated Use the built-in `NoInfer` type instead */
1456
+
1457
+ /** The full definition of an event, with a string `type`. */
1458
+
1459
+ /**
1460
+ * The string or object representing the state value relative to the parent
1461
+ * state node.
1462
+ *
1463
+ * @remarks
1464
+ * - For a child atomic state node, this is a string, e.g., `"pending"`.
1465
+ * - For complex state nodes, this is an object, e.g., `{ success:
1466
+ * "someChildState" }`.
1467
+ */
1468
+
1469
+ // Accept any StateNode instance regardless of generic parameters
1470
+ // Using a union type to handle variance issues with machine.resolveState
1471
+
1472
+ let SpecialTargets = /*#__PURE__*/function (SpecialTargets) {
1473
+ SpecialTargets["Internal"] = "#_internal";
1474
+ return SpecialTargets;
1475
+ }({});
1476
+
1477
+ /**
1478
+ * A pending effect (delayed event/transition that has not fired yet) captured
1479
+ * in a persisted snapshot — a serialized built-in action descriptor plus its
1480
+ * runtime progress, with enough information to restore it under any strategy.
1481
+ */
1482
+
1483
+ /**
1484
+ * How pending effects (timers) from a persisted snapshot are restored on
1485
+ * `actor.start()`:
1486
+ *
1487
+ * - `'resume'` (default) — the clock was "frozen" while persisted: a 5-minute
1488
+ * timer persisted with 1 minute elapsed fires after 4 more minutes.
1489
+ * - `'restart'` — timers start over with their full delay.
1490
+ * - `'absolute'` — timers honor the original wall-clock expiry (`startedAt +
1491
+ * delay`); already-expired timers fire immediately.
1492
+ * - A custom function receiving the pending effect and returning the remaining
1493
+ * delay in ms.
1494
+ */
1495
+
1496
+ /** @deprecated Use `AnyActor` instead. */
1497
+
1498
+ // Based on RxJS types
1499
+
1500
+ /**
1501
+ * A consumer-facing actor handle.
1502
+ *
1503
+ * `ActorRef` describes the contract a consumer needs: which events can be sent
1504
+ * to the actor, which snapshot it publishes for observation, and which emitted
1505
+ * events can be listened to. It intentionally does not expose lifecycle control
1506
+ * or runtime internals. A concrete `Actor` satisfies this interface, so APIs
1507
+ * should accept `ActorRef` whenever they only need to send events, read
1508
+ * snapshots, or listen to emitted events.
1509
+ */
1510
+
1511
+ /**
1512
+ * Runtime-only actor capabilities.
1513
+ *
1514
+ * These members are needed by the interpreter, scheduler, inspection, and child
1515
+ * management code. Public consumer APIs should prefer `ActorRef` unless they
1516
+ * genuinely need lifecycle control such as starting, stopping, or accessing
1517
+ * system-owned runtime state.
1518
+ */
1519
+
1520
+ /**
1521
+ * A concrete actor instance type.
1522
+ *
1523
+ * `ActorInstance` combines the consumer `ActorRef` contract with runtime
1524
+ * lifecycle capabilities. Values returned by `createActor(...)` and
1525
+ * `spawn(...)` naturally satisfy narrower `ActorRef` contracts.
1526
+ */
1527
+
1528
+ /**
1529
+ * The actor's own full self handle.
1530
+ *
1531
+ * Internals and action/guard implementations receive this shape because `self`
1532
+ * can participate in runtime-owned behavior while still being usable wherever
1533
+ * an `ActorRef` is expected.
1534
+ */
1535
+
1536
+ // TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
1537
+
1538
+ /** The concrete actor instance type produced from actor logic. */
1539
+
1540
+ // TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
1541
+
1542
+ /**
1543
+ * Represents logic which can be used by an actor.
1544
+ *
1545
+ * @template TSnapshot - The type of the snapshot.
1546
+ * @template TEvent - The type of the event object.
1547
+ * @template TInput - The type of the input.
1548
+ * @template TSystem - The type of the actor system.
1549
+ */
1550
+
1551
+ // Actors that don't have literal string IDs — these are the only ones
1552
+ // that should appear in the index signature fallback, since actors with
1553
+ // literal IDs are already covered by ToConcreteChildren.
1554
+
1555
+ /** Maps state IDs to their input types based on the StateSchema. */
1556
+
1557
+ /** @deprecated */
1558
+
1559
+ /** Mappers for subscribeTo - maps lifecycle events to machine events */
1560
+
354
1561
  /**
355
1562
  * Given actor `logic`, a `snapshot`, and an `event`, returns a tuple of the
356
1563
  * `nextSnapshot` and `actions` to execute.
@@ -363,8 +1570,9 @@ function transition(logic, snapshot, event) {
363
1570
  actorScope.actionExecutor = action => {
364
1571
  executableActions.push(action);
365
1572
  };
366
- const nextSnapshot = logic.transition(snapshot, event, actorScope);
367
- return [nextSnapshot, executableActions];
1573
+ const transitionResult = logic.transition(snapshot, event, actorScope);
1574
+ const [nextSnapshot, effects] = Array.isArray(transitionResult) ? transitionResult : [transitionResult, []];
1575
+ return [nextSnapshot, executableActions.concat(effects)];
368
1576
  }
369
1577
 
370
1578
  /**
@@ -394,7 +1602,7 @@ function getMicrosteps(machine, snapshot, event) {
394
1602
  const actorScope = createInertActorScope(machine);
395
1603
  const {
396
1604
  microsteps
397
- } = dist_xstateGuards.macrostep(snapshot, event, actorScope, []);
1605
+ } = dist_xstateActors.macrostep(snapshot, event, actorScope, []);
398
1606
  return microsteps;
399
1607
  }
400
1608
 
@@ -407,13 +1615,13 @@ function getMicrosteps(machine, snapshot, event) {
407
1615
  */
408
1616
  function getInitialMicrosteps(machine, ...[input]) {
409
1617
  const actorScope = createInertActorScope(machine);
410
- const initEvent = dist_xstateGuards.createInitEvent(input);
1618
+ const initEvent = dist_xstateActors.createInitEvent(input);
411
1619
  const internalQueue = [];
412
- const preInitialSnapshot = machine._getPreInitialState(actorScope, initEvent, internalQueue);
413
- const first = dist_xstateGuards.initialMicrostep(machine.root, preInitialSnapshot, actorScope, initEvent, internalQueue);
1620
+ const preInitialSnapshot = machine._getPreInitialState(actorScope, initEvent);
1621
+ const first = dist_xstateActors.initialMicrostep(machine.root, preInitialSnapshot, actorScope, initEvent, internalQueue);
414
1622
  const {
415
1623
  microsteps
416
- } = dist_xstateGuards.macrostep(first[0], initEvent, actorScope, internalQueue);
1624
+ } = dist_xstateActors.macrostep(first[0], initEvent, actorScope, internalQueue);
417
1625
  return [first, ...microsteps];
418
1626
  }
419
1627
 
@@ -443,7 +1651,7 @@ function getInitialMicrosteps(machine, ...[input]) {
443
1651
  */
444
1652
  function getNextTransitions(state) {
445
1653
  const potentialTransitions = [];
446
- const atomicStates = state._nodes.filter(dist_xstateGuards.isAtomicStateNode);
1654
+ const atomicStates = state._nodes.filter(dist_xstateActors.isAtomicStateNode);
447
1655
  const visited = new Set();
448
1656
 
449
1657
  // Collect all transitions from atomic states and their ancestors
@@ -451,7 +1659,7 @@ function getNextTransitions(state) {
451
1659
  for (const stateNode of atomicStates) {
452
1660
  // For each atomic state, process the state itself first, then its ancestors
453
1661
  // This ensures child state transitions come before parent state transitions
454
- for (const s of [stateNode].concat(dist_xstateGuards.getProperAncestors(stateNode, undefined))) {
1662
+ for (const s of [stateNode].concat(dist_xstateActors.getProperAncestors(stateNode, undefined))) {
455
1663
  if (visited.has(s.id)) {
456
1664
  continue;
457
1665
  }
@@ -460,7 +1668,7 @@ function getNextTransitions(state) {
460
1668
  // Get all transitions for each event type
461
1669
  // Include ALL transitions, even if the same event type appears in multiple state nodes
462
1670
  // This is important for guarded transitions - all are "potential" regardless of guard evaluation
463
- for (const [, transitions] of s.transitions) {
1671
+ for (const [, transitions] of s.transitions.entries()) {
464
1672
  potentialTransitions.push(...transitions);
465
1673
  }
466
1674
 
@@ -577,51 +1785,51 @@ function waitFor(actorRef, predicate, options) {
577
1785
  });
578
1786
  }
579
1787
 
1788
+ exports.Actor = dist_xstateActors.Actor;
1789
+ exports.TimeoutError = dist_xstateActors.TimeoutError;
1790
+ exports.__unsafe_getAllOwnEventDescriptors = dist_xstateActors.getAllOwnEventDescriptors;
1791
+ exports.checkStateIn = dist_xstateActors.checkStateIn;
1792
+ exports.createActor = dist_xstateActors.createActor;
1793
+ exports.createAsyncAtom = dist_xstateActors.createAsyncAtom;
1794
+ exports.createAsyncLogic = dist_xstateActors.createAsyncLogic;
1795
+ exports.createAtom = dist_xstateActors.createAtom;
1796
+ exports.createAtomConfig = dist_xstateActors.createAtomConfig;
1797
+ exports.createCallbackLogic = dist_xstateActors.createCallbackLogic;
580
1798
  exports.createEmptyActor = dist_xstateActors.createEmptyActor;
581
- exports.fromCallback = dist_xstateActors.fromCallback;
582
- exports.fromEventObservable = dist_xstateActors.fromEventObservable;
583
- exports.fromObservable = dist_xstateActors.fromObservable;
584
- exports.fromPromise = dist_xstateActors.fromPromise;
585
- exports.fromTransition = dist_xstateActors.fromTransition;
586
- exports.Actor = dist_xstateGuards.Actor;
587
- exports.__unsafe_getAllOwnEventDescriptors = dist_xstateGuards.getAllOwnEventDescriptors;
588
- exports.and = dist_xstateGuards.and;
589
- exports.cancel = dist_xstateGuards.cancel;
590
- exports.createActor = dist_xstateGuards.createActor;
591
- exports.getStateNodes = dist_xstateGuards.getStateNodes;
592
- exports.interpret = dist_xstateGuards.interpret;
593
- exports.isMachineSnapshot = dist_xstateGuards.isMachineSnapshot;
594
- exports.matchesState = dist_xstateGuards.matchesState;
595
- exports.not = dist_xstateGuards.not;
596
- exports.or = dist_xstateGuards.or;
597
- exports.pathToStateValue = dist_xstateGuards.pathToStateValue;
598
- exports.raise = dist_xstateGuards.raise;
599
- exports.spawnChild = dist_xstateGuards.spawnChild;
600
- exports.stateIn = dist_xstateGuards.stateIn;
601
- exports.stop = dist_xstateGuards.stop;
602
- exports.stopChild = dist_xstateGuards.stopChild;
603
- exports.toObserver = dist_xstateGuards.toObserver;
1799
+ exports.createEventObservableLogic = dist_xstateActors.createEventObservableLogic;
1800
+ exports.createListenerLogic = dist_xstateActors.createListenerLogic;
1801
+ exports.createLogic = dist_xstateActors.createLogic;
1802
+ exports.createObservableLogic = dist_xstateActors.createObservableLogic;
1803
+ exports.createReducerAtom = dist_xstateActors.createReducerAtom;
1804
+ exports.createSubscriptionLogic = dist_xstateActors.createSubscriptionLogic;
1805
+ exports.getStateNodes = dist_xstateActors.getStateNodes;
1806
+ exports.isAtom = dist_xstateActors.isAtom;
1807
+ exports.isMachineSnapshot = dist_xstateActors.isMachineSnapshot;
1808
+ exports.listenerLogic = dist_xstateActors.listenerLogic;
1809
+ exports.matchesState = dist_xstateActors.matchesState;
1810
+ exports.pathToStateValue = dist_xstateActors.pathToStateValue;
1811
+ exports.subscriptionLogic = dist_xstateActors.subscriptionLogic;
1812
+ exports.toObserver = dist_xstateActors.toObserver;
604
1813
  exports.StateMachine = StateMachine.StateMachine;
605
1814
  exports.StateNode = StateMachine.StateNode;
606
- exports.assign = assign.assign;
607
- exports.SpecialTargets = log.SpecialTargets;
608
- exports.emit = log.emit;
609
- exports.enqueueActions = log.enqueueActions;
610
- exports.forwardTo = log.forwardTo;
611
- exports.log = log.log;
612
- exports.sendParent = log.sendParent;
613
- exports.sendTo = log.sendTo;
614
1815
  exports.SimulatedClock = SimulatedClock;
1816
+ exports.SpecialTargets = SpecialTargets;
615
1817
  exports.assertEvent = assertEvent;
616
1818
  exports.createMachine = createMachine;
1819
+ exports.createMachineFromConfig = createMachineFromConfig;
1820
+ exports.createStateConfig = createStateConfig;
617
1821
  exports.getInitialMicrosteps = getInitialMicrosteps;
618
1822
  exports.getInitialSnapshot = getInitialSnapshot;
619
1823
  exports.getMicrosteps = getMicrosteps;
620
1824
  exports.getNextSnapshot = getNextSnapshot;
621
1825
  exports.getNextTransitions = getNextTransitions;
622
1826
  exports.initialTransition = initialTransition;
1827
+ exports.isTypeSchema = isTypeSchema;
1828
+ exports.machineConfigToJSON = machineConfigToJSON;
623
1829
  exports.mapState = mapState;
1830
+ exports.serializeMachine = serializeMachine;
624
1831
  exports.setup = setup;
625
1832
  exports.toPromise = toPromise;
626
1833
  exports.transition = transition;
1834
+ exports.types = types;
627
1835
  exports.waitFor = waitFor;