xstate 5.32.1 → 6.0.0-alpha.2

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