xstate 5.32.2 → 6.0.0-alpha.10

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 (121) hide show
  1. package/README.md +25 -24
  2. package/dist/StateMachine-0aa98f54.development.esm.js +746 -0
  3. package/dist/StateMachine-aef700df.cjs.js +732 -0
  4. package/dist/StateMachine-d4931336.esm.js +729 -0
  5. package/dist/StateMachine-da872dde.development.cjs.js +749 -0
  6. package/dist/declarations/src/State.d.ts +11 -13
  7. package/dist/declarations/src/StateMachine.d.ts +40 -26
  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/createActor.d.ts +33 -34
  18. package/dist/declarations/src/createMachine.d.ts +38 -7
  19. package/dist/declarations/src/createMachineFromConfig.d.ts +177 -0
  20. package/dist/declarations/src/graph/graph.d.ts +6 -4
  21. package/dist/declarations/src/graph/shortestPaths.d.ts +2 -2
  22. package/dist/declarations/src/graph/simplePaths.d.ts +2 -2
  23. package/dist/declarations/src/index.d.ts +13 -9
  24. package/dist/declarations/src/inspection.d.ts +84 -30
  25. package/dist/declarations/src/mapState.d.ts +4 -2
  26. package/dist/declarations/src/schema.types.d.ts +90 -0
  27. package/dist/declarations/src/serialize.d.ts +50 -0
  28. package/dist/declarations/src/setup.d.ts +396 -136
  29. package/dist/declarations/src/spawn.d.ts +7 -32
  30. package/dist/declarations/src/stateUtils.d.ts +22 -19
  31. package/dist/declarations/src/system.d.ts +8 -7
  32. package/dist/declarations/src/transition.d.ts +8 -5
  33. package/dist/declarations/src/transitionActions.d.ts +4 -0
  34. package/dist/declarations/src/types.d.ts +494 -429
  35. package/dist/declarations/src/types.v6.d.ts +423 -0
  36. package/dist/declarations/src/utils.d.ts +5 -3
  37. package/dist/index-219cb621.development.cjs.js +4476 -0
  38. package/dist/index-2f2fbd9b.development.esm.js +4421 -0
  39. package/dist/index-559ceab3.cjs.js +4437 -0
  40. package/dist/index-8828376f.esm.js +4382 -0
  41. package/dist/xstate-actors.cjs.js +12 -838
  42. package/dist/xstate-actors.cjs.mjs +10 -5
  43. package/dist/xstate-actors.development.cjs.js +12 -838
  44. package/dist/xstate-actors.development.cjs.mjs +10 -5
  45. package/dist/xstate-actors.development.esm.js +1 -836
  46. package/dist/xstate-actors.esm.js +1 -836
  47. package/dist/xstate-actors.umd.min.js +1 -1
  48. package/dist/xstate-actors.umd.min.js.map +1 -1
  49. package/dist/xstate-graph.cjs.js +18 -29
  50. package/dist/xstate-graph.development.cjs.js +18 -29
  51. package/dist/xstate-graph.development.esm.js +15 -26
  52. package/dist/xstate-graph.esm.js +15 -26
  53. package/dist/xstate-graph.umd.min.js +1 -1
  54. package/dist/xstate-graph.umd.min.js.map +1 -1
  55. package/dist/xstate.cjs.js +1477 -122
  56. package/dist/xstate.cjs.mjs +19 -22
  57. package/dist/xstate.development.cjs.js +1477 -122
  58. package/dist/xstate.development.cjs.mjs +19 -22
  59. package/dist/xstate.development.esm.js +1442 -86
  60. package/dist/xstate.esm.js +1442 -86
  61. package/dist/xstate.umd.min.js +1 -1
  62. package/dist/xstate.umd.min.js.map +1 -1
  63. package/package.json +3 -47
  64. package/dist/StateMachine-23c204bb.development.esm.js +0 -566
  65. package/dist/StateMachine-67a0f463.development.cjs.js +0 -569
  66. package/dist/StateMachine-94d215e3.cjs.js +0 -563
  67. package/dist/StateMachine-d1d80308.esm.js +0 -560
  68. package/dist/assign-43b1b558.development.esm.js +0 -133
  69. package/dist/assign-54b312bc.esm.js +0 -127
  70. package/dist/assign-b5bc78f7.cjs.js +0 -129
  71. package/dist/assign-f8f66278.development.cjs.js +0 -135
  72. package/dist/declarations/src/actions/assign.d.ts +0 -45
  73. package/dist/declarations/src/actions/cancel.d.ts +0 -39
  74. package/dist/declarations/src/actions/emit.d.ts +0 -44
  75. package/dist/declarations/src/actions/enqueueActions.d.ts +0 -57
  76. package/dist/declarations/src/actions/log.d.ts +0 -16
  77. package/dist/declarations/src/actions/raise.d.ts +0 -21
  78. package/dist/declarations/src/actions/send.d.ts +0 -42
  79. package/dist/declarations/src/actions/spawnChild.d.ts +0 -34
  80. package/dist/declarations/src/actions/stopChild.d.ts +0 -19
  81. package/dist/declarations/src/actors/transition.d.ts +0 -143
  82. package/dist/declarations/src/dev/index.d.ts +0 -14
  83. package/dist/declarations/src/guards.d.ts +0 -128
  84. package/dist/log-9b02c355.esm.js +0 -401
  85. package/dist/log-c94995c7.cjs.js +0 -409
  86. package/dist/log-d9f3f459.development.cjs.js +0 -437
  87. package/dist/log-db444cad.development.esm.js +0 -429
  88. package/dist/raise-235108c6.esm.js +0 -2762
  89. package/dist/raise-45f11c00.development.esm.js +0 -2824
  90. package/dist/raise-c445379d.cjs.js +0 -2817
  91. package/dist/raise-eca73993.development.cjs.js +0 -2879
  92. package/dist/xstate-actions.cjs.d.mts +0 -2
  93. package/dist/xstate-actions.cjs.d.ts +0 -2
  94. package/dist/xstate-actions.cjs.js +0 -23
  95. package/dist/xstate-actions.cjs.mjs +0 -14
  96. package/dist/xstate-actions.development.cjs.js +0 -23
  97. package/dist/xstate-actions.development.cjs.mjs +0 -14
  98. package/dist/xstate-actions.development.esm.js +0 -4
  99. package/dist/xstate-actions.esm.js +0 -4
  100. package/dist/xstate-actions.umd.min.js +0 -2
  101. package/dist/xstate-actions.umd.min.js.map +0 -1
  102. package/dist/xstate-dev.cjs.d.mts +0 -2
  103. package/dist/xstate-dev.cjs.d.ts +0 -2
  104. package/dist/xstate-dev.cjs.js +0 -48
  105. package/dist/xstate-dev.cjs.mjs +0 -5
  106. package/dist/xstate-dev.development.cjs.js +0 -51
  107. package/dist/xstate-dev.development.cjs.mjs +0 -5
  108. package/dist/xstate-dev.development.esm.js +0 -45
  109. package/dist/xstate-dev.esm.js +0 -42
  110. package/dist/xstate-dev.umd.min.js +0 -2
  111. package/dist/xstate-dev.umd.min.js.map +0 -1
  112. package/dist/xstate-guards.cjs.d.mts +0 -2
  113. package/dist/xstate-guards.cjs.d.ts +0 -2
  114. package/dist/xstate-guards.cjs.js +0 -14
  115. package/dist/xstate-guards.cjs.mjs +0 -7
  116. package/dist/xstate-guards.development.cjs.js +0 -14
  117. package/dist/xstate-guards.development.cjs.mjs +0 -7
  118. package/dist/xstate-guards.development.esm.js +0 -2
  119. package/dist/xstate-guards.esm.js +0 -2
  120. package/dist/xstate-guards.umd.min.js +0 -2
  121. 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-45f11c00.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-45f11c00.development.esm.js';
4
- import { S as StateMachine } from './StateMachine-23c204bb.development.esm.js';
5
- export { S as StateMachine, a as StateNode } from './StateMachine-23c204bb.development.esm.js';
6
- import { a as assign } from './assign-43b1b558.development.esm.js';
7
- export { a as assign } from './assign-43b1b558.development.esm.js';
8
- import { s as sendTo, l as log, e as enqueueActions, a as emit } from './log-db444cad.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-db444cad.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-2f2fbd9b.development.esm.js';
2
+ export { A as Actor, T as TimeoutError, h as __unsafe_getAllOwnEventDescriptors, k as checkStateIn, c as createActor, w as createAsyncLogic, r as createCallbackLogic, q as createEmptyActor, u as createEventObservableLogic, x as createListenerLogic, v as createLogic, s as createObservableLogic, z as createSubscriptionLogic, f as getStateNodes, o as isBuiltInExecutableAction, e as isMachineSnapshot, y as listenerLogic, j as matchesState, l as pathToStateValue, B as subscriptionLogic, n as toObserver } from './index-2f2fbd9b.development.esm.js';
3
+ import { S as StateMachine } from './StateMachine-0aa98f54.development.esm.js';
4
+ export { S as StateMachine, a as StateNode } from './StateMachine-0aa98f54.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,1063 @@ 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 === 'actorSources' || 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', 'actorSources', '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
+ actorSources: implementations.actorSources ?? {},
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 getMissingGuardMessage(type, guards) {
267
+ return `Guard '${type}' is not implemented in machine '${json.id ?? '(machine)'}'. Available guards: ${Object.keys(guards).map(key => `'${key}'`).join(', ') || '(none)'}.`;
268
+ }
269
+ function resolveGuardConfig(guard) {
270
+ if (!guard) {
271
+ return undefined;
272
+ }
273
+ return args => {
274
+ const guardImpl = args.guards?.[guard.type];
275
+ if (!guardImpl) {
276
+ throw new Error(getMissingGuardMessage(guard.type, args.guards ?? {}));
277
+ }
278
+ return guardImpl(args, guard.params);
279
+ };
280
+ }
281
+ function resolveRouteConfig(route) {
282
+ if (!route || typeof route === 'function') {
283
+ return route;
284
+ }
285
+ const {
286
+ guard,
287
+ ...routeConfig
288
+ } = route;
289
+ if (!guard) {
290
+ return routeConfig;
291
+ }
292
+ const resolvedGuard = resolveGuardConfig({
293
+ type: guard
294
+ });
295
+ return args => resolvedGuard(args) ? routeConfig : undefined;
296
+ }
297
+ function iterNode(node, nodeKey) {
298
+ const originalEntryActions = node.entry;
299
+ const stateId = node.id || nodeKey;
300
+
301
+ // Wrap entry to first execute any pending transition actions, then normal entry.
302
+ // This ensures SCXML execution order: exit → transition_actions → entry_actions
303
+ // because pending transition actions are set by .to (before exit) but executed
304
+ // in entry (after exit), thus seeing post-exit context.
305
+ const entryFn = (x, enq) => {
306
+ // Drain any platform errors queued during transition selection (e.g.,
307
+ // failing <transition cond="..."> evaluations). These must be raised
308
+ // internally so they're processed before subsequent external events.
309
+ while (pendingPlatformErrors.length) {
310
+ const err = pendingPlatformErrors.shift();
311
+ enq.raise({
312
+ type: 'error.execution',
313
+ _scxmlEventType: 'platform',
314
+ _scxmlEventData: err
315
+ });
316
+ }
317
+ let context;
318
+
319
+ // If targetless transitions were interleaved with targeted transitions
320
+ // (parallel state), re-execute ALL transition actions from the original
321
+ // pre-transition context. This overrides any .to-produced context with
322
+ // the correct SCXML document-order sequential execution.
323
+ // Only trigger when BOTH targeted (pending in map) and targetless fired
324
+ // in the same microstep — prevents stale data from previous events.
325
+ if (contextBeforeTargetless && allTransitionActions.length > 0 && Object.keys(pendingTransitionActionsMap).length > 0) {
326
+ let ctx = contextBeforeTargetless;
327
+ for (const actions of allTransitionActions) {
328
+ const mergedX = {
329
+ ...x,
330
+ context: ctx
331
+ };
332
+ const result = executeActions(actions, mergedX, enq);
333
+ if (result.context) {
334
+ ctx = result.context;
335
+ }
336
+ }
337
+ allTransitionActions.length = 0;
338
+ contextBeforeTargetless = null;
339
+ // Clear per-target map since we re-processed everything
340
+ for (const key of Object.keys(pendingTransitionActionsMap)) {
341
+ delete pendingTransitionActionsMap[key];
342
+ }
343
+ context = ctx;
344
+ } else {
345
+ // Normal path: consume pending transition actions for THIS state.
346
+ // In parallel states, each target gets its own pending actions.
347
+ const transActions = stateId ? pendingTransitionActionsMap[stateId] : undefined;
348
+ if (transActions) {
349
+ delete pendingTransitionActionsMap[stateId];
350
+ const result = executeActions(transActions, x, enq);
351
+ if (result.context) {
352
+ context = result.context;
353
+ }
354
+ }
355
+ // Clear stale targetless data from previous microsteps
356
+ contextBeforeTargetless = null;
357
+ allTransitionActions.length = 0;
358
+ }
359
+
360
+ // Execute normal entry actions
361
+ if (originalEntryActions?.length) {
362
+ const mergedX = context ? {
363
+ ...x,
364
+ context
365
+ } : x;
366
+ const result = executeActions(originalEntryActions, mergedX, enq);
367
+ if (result.context) {
368
+ context = result.context;
369
+ }
370
+ }
371
+ return {
372
+ context
373
+ };
374
+ };
375
+ const nodeConfig = {
376
+ id: node.id,
377
+ initial: node.initial,
378
+ type: node.type,
379
+ history: node.history,
380
+ target: node.target,
381
+ description: node.description,
382
+ tags: node.tags,
383
+ input: node.input,
384
+ timeout: node.timeout,
385
+ states: node.states ? Object.entries(node.states).reduce((acc, [key, value]) => {
386
+ acc[key] = iterNode(value, key);
387
+ return acc;
388
+ }, {}) : undefined,
389
+ on: node.on ? Object.entries(node.on).reduce((acc, [key, value]) => {
390
+ acc[key] = getTransitionConfig(value);
391
+ return acc;
392
+ }, {}) : undefined,
393
+ always: node.always ? getTransitionConfig(node.always) : undefined,
394
+ choice: node.choice ? getTransitionConfig(node.choice) : undefined,
395
+ route: resolveRouteConfig(node.route),
396
+ after: node.after ? Object.entries(node.after).reduce((acc, [key, value]) => {
397
+ acc[key] = getTransitionConfig(value);
398
+ return acc;
399
+ }, {}) : undefined,
400
+ onTimeout: node.onTimeout ? getTransitionConfig(node.onTimeout) : undefined,
401
+ entry: entryFn,
402
+ exit: node.exit ? iterActions(node.exit) : undefined,
403
+ invoke: node.invoke ? iterInvokeConfigs(node.invoke) : undefined,
404
+ meta: node.meta,
405
+ output: node._scxmlDonedata ? makeDonedataOutput(node._scxmlDonedata) : node.output
406
+ };
407
+ return nodeConfig;
408
+ }
409
+ function makeDonedataOutput(donedata) {
410
+ return ({
411
+ context,
412
+ event,
413
+ self
414
+ }) => {
415
+ try {
416
+ if (donedata.params && donedata.params.length) {
417
+ const out = {};
418
+ for (const param of donedata.params) {
419
+ out[param.name] = evaluateExpr(context, param.expr, event, self);
420
+ }
421
+ return out;
422
+ }
423
+ if (donedata.contentExpr !== undefined) {
424
+ return evaluateExpr(context, donedata.contentExpr, event, self);
425
+ }
426
+ if (donedata.contentText !== undefined) {
427
+ // Try parsing as a JS expression (number/object/array literals);
428
+ // fall back to the raw string.
429
+ try {
430
+ return evaluateExpr(context, donedata.contentText, event, self);
431
+ } catch {
432
+ return donedata.contentText;
433
+ }
434
+ }
435
+ } catch (err) {
436
+ // Per SCXML, donedata expression errors raise error.execution and
437
+ // result in undefined event.data. Queue for the next entry to drain.
438
+ const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
439
+ pendingPlatformErrors.push({
440
+ tagname: 'donedata',
441
+ message,
442
+ line: NaN,
443
+ column: NaN,
444
+ reason: message
445
+ });
446
+ }
447
+ return undefined;
448
+ };
449
+ }
450
+ function iterInvokeConfigs(invokes) {
451
+ const invokeArray = Array.isArray(invokes) ? invokes : [invokes];
452
+ return invokeArray.map(inv => {
453
+ const extInv = inv;
454
+ // Create child machine from nested SCXML JSON
455
+ let src;
456
+ if (extInv._nestedMachineJSON) {
457
+ src = createMachineFromConfig(extInv._nestedMachineJSON, resolvedImplementations);
458
+ } else if (isUnserializableMarker(inv.src)) {
459
+ src = inv.src.id ? resolvedImplementations.actorSources[inv.src.id] : undefined;
460
+ if (!src) {
461
+ throw new Error(`Missing actorSources.${inv.src.id ?? ''}`);
462
+ }
463
+ } else {
464
+ src = inv.src;
465
+ }
466
+ return {
467
+ src,
468
+ id: inv.id,
469
+ registryKey: inv.registryKey,
470
+ input: inv.input,
471
+ onDone: inv.onDone ? getTransitionConfig(inv.onDone) : undefined,
472
+ onError: inv.onError ? getTransitionConfig(inv.onError) : undefined,
473
+ onSnapshot: inv.onSnapshot ? getTransitionConfig(inv.onSnapshot) : undefined,
474
+ timeout: inv.timeout,
475
+ onTimeout: inv.onTimeout ? getTransitionConfig(inv.onTimeout) : undefined
476
+ };
477
+ });
478
+ }
479
+
480
+ /**
481
+ * Raise an SCXML error.execution platform event onto the internal queue. Per
482
+ * spec, error.execution is type='platform' and _event.data contains error
483
+ * info. The surrounding block of executable content stops.
484
+ */
485
+ function raiseErrorExecution(state, tagname, err) {
486
+ const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
487
+ state.enq.raise({
488
+ type: 'error.execution',
489
+ _scxmlEventType: 'platform',
490
+ _scxmlEventData: {
491
+ tagname,
492
+ message,
493
+ line: NaN,
494
+ column: NaN,
495
+ reason: message
496
+ }
497
+ });
498
+ state.errored = true;
499
+ }
500
+ /** Execute an array of SCXML action JSON descriptors with context and enqueue. */
501
+ function executeActions(actions, x, enq, parentState) {
502
+ const state = parentState ?? {
503
+ enq,
504
+ errored: false
505
+ };
506
+ let context;
507
+ for (const action of actions) {
508
+ if (state.errored) break;
509
+ if (isBuiltInActionJSON(action)) {
510
+ switch (action.type) {
511
+ case '@xstate.raise':
512
+ {
513
+ // Tag as external if it has a delay (from <send>, not <raise>).
514
+ // Attach _scxmlSendId so _event.sendid can be read in expressions.
515
+ const isExternal = action.delay !== undefined;
516
+ const event = isExternal || action.id !== undefined ? {
517
+ ...action.event,
518
+ ...(isExternal ? {
519
+ _scxmlExternal: true
520
+ } : {}),
521
+ ...(action.id !== undefined ? {
522
+ _scxmlSendId: action.id
523
+ } : {})
524
+ } : action.event;
525
+ enq.raise(event, {
526
+ id: action.id,
527
+ delay: action.delay
528
+ });
529
+ break;
530
+ }
531
+ case '@xstate.cancel':
532
+ enq.cancel(action.id);
533
+ break;
534
+ case '@xstate.log':
535
+ enq.log(...action.args);
536
+ break;
537
+ case '@xstate.emit':
538
+ enq.emit(action.event);
539
+ break;
540
+ case '@xstate.assign':
541
+ context ??= {};
542
+ Object.assign(context, action.context);
543
+ break;
544
+ default:
545
+ throw new Error(`Unknown built-in action: ${action.type}`);
546
+ }
547
+ } else if (action.type === 'scxml.assign') {
548
+ const scxmlAction = action;
549
+ const mergedContext = {
550
+ ...x.context,
551
+ ...context
552
+ };
553
+ let value;
554
+ try {
555
+ value = evaluateExpr(mergedContext, scxmlAction.expr, x.event, x.self);
556
+ } catch (err) {
557
+ raiseErrorExecution(state, 'assign', err);
558
+ continue;
559
+ }
560
+ // Per SCXML, assigning to a location that doesn't exist on the
561
+ // datamodel is an error. Allow setting an own property via context;
562
+ // detect deep paths (a.b.c) which we don't support.
563
+ if (scxmlAction.location.includes('.')) {
564
+ raiseErrorExecution(state, 'assign', new Error(`Invalid assign location: ${scxmlAction.location}`));
565
+ continue;
566
+ }
567
+ context ??= {};
568
+ context[scxmlAction.location] = value;
569
+ } else if (action.type === 'scxml.raise') {
570
+ const scxmlAction = action;
571
+ const mergedContext = {
572
+ ...x.context,
573
+ ...context
574
+ };
575
+ let eventType;
576
+ let eventData;
577
+ let target;
578
+ let delay;
579
+ try {
580
+ eventType = scxmlAction.eventexpr ? evaluateExpr(mergedContext, scxmlAction.eventexpr, x.event, x.self) : scxmlAction.event || 'unknown';
581
+ eventData = {
582
+ type: eventType
583
+ };
584
+ if (scxmlAction.params) {
585
+ for (const param of scxmlAction.params) {
586
+ eventData[param.name] = evaluateExpr(mergedContext, param.expr, x.event, x.self);
587
+ }
588
+ }
589
+ target = scxmlAction.targetexpr ? evaluateExpr(mergedContext, scxmlAction.targetexpr, x.event, x.self) : scxmlAction.target;
590
+ const isInternalTarget = target === '#_internal';
591
+ delay = scxmlAction.delayexpr ? delayToMs(evaluateExpr(mergedContext, scxmlAction.delayexpr, x.event, x.self)) : isInternalTarget ? undefined : scxmlAction.delay;
592
+ } catch (err) {
593
+ raiseErrorExecution(state, 'send', err);
594
+ continue;
595
+ }
596
+ const isInternalTarget = target === '#_internal';
597
+ const isParentTarget = target === '#_parent';
598
+
599
+ // Validate target. SCXML targets must be '#_internal', '#_parent',
600
+ // or '#_<id>'. A bare string without the '#_' prefix is invalid and
601
+ // raises error.execution.
602
+ if (typeof target === 'string' && target.length > 0 && !target.startsWith('#_')) {
603
+ raiseErrorExecution(state, 'send', new Error(`Invalid send target: ${target}`));
604
+ continue;
605
+ }
606
+ // Attach send id so _event.sendid can be read in expressions.
607
+ if (scxmlAction.id !== undefined) {
608
+ eventData._scxmlSendId = scxmlAction.id;
609
+ }
610
+ // Resolve target at runtime: parent, child, or self
611
+ if (isParentTarget && x.parent) {
612
+ // Send to parent via sendTo; pass delay if present
613
+ enq.sendTo(x.parent, eventData, delay !== undefined ? {
614
+ delay
615
+ } : undefined);
616
+ } else if (typeof target === 'string' && target.startsWith('#_') && !isParentTarget && !isInternalTarget) {
617
+ // #_<invokeId> → try to send to child actor, fall back to self-raise
618
+ const childId = target.slice(2); // strip '#_'
619
+ const childRef = x.children?.[childId];
620
+ if (childRef) {
621
+ enq.sendTo(childRef, eventData);
622
+ } else {
623
+ // Not a known child (e.g. #_scxml_sessionid) → self-raise
624
+ if (delay !== undefined) {
625
+ eventData._scxmlExternal = true;
626
+ }
627
+ enq.raise(eventData, {
628
+ id: scxmlAction.id,
629
+ delay
630
+ });
631
+ }
632
+ } else {
633
+ // Self-send or no special target: raise as external event
634
+ if (delay !== undefined) {
635
+ eventData._scxmlExternal = true;
636
+ }
637
+ enq.raise(eventData, {
638
+ id: scxmlAction.id,
639
+ delay
640
+ });
641
+ }
642
+ } else if (action.type === 'scxml.cancel') {
643
+ const scxmlAction = action;
644
+ const mergedContext = {
645
+ ...x.context,
646
+ ...context
647
+ };
648
+ try {
649
+ const sendId = evaluateExpr(mergedContext, scxmlAction.sendidexpr, x.event, x.self);
650
+ if (sendId) {
651
+ enq.cancel(sendId);
652
+ }
653
+ } catch {
654
+ // ignore evaluation errors
655
+ }
656
+ } else if (action.type === 'scxml.script') {
657
+ const scxmlAction = action;
658
+ const mergedContext = {
659
+ ...x.context,
660
+ ...context
661
+ };
662
+ try {
663
+ const updatedContext = executeScript(mergedContext, scxmlAction.code);
664
+ context ??= {};
665
+ Object.assign(context, updatedContext);
666
+ } catch (err) {
667
+ raiseErrorExecution(state, 'script', err);
668
+ }
669
+ } else if (action.type === 'scxml.foreach') {
670
+ const scxmlAction = action;
671
+ const mergedContext = {
672
+ ...x.context,
673
+ ...context
674
+ };
675
+ // SCXML: `item` (and `index` if present) must be valid datamodel
676
+ // locations — i.e. legal identifiers. Reject things like 'continue'
677
+ // (string literal) or '7' (number).
678
+ const isLegalIdentifier = s => /^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(s);
679
+ if (!isLegalIdentifier(scxmlAction.item) || scxmlAction.index && !isLegalIdentifier(scxmlAction.index)) {
680
+ raiseErrorExecution(state, 'foreach', new Error('foreach item/index is not a legal location'));
681
+ continue;
682
+ }
683
+ let arr;
684
+ try {
685
+ arr = evaluateExpr(mergedContext, scxmlAction.array, x.event, x.self);
686
+ } catch (err) {
687
+ raiseErrorExecution(state, 'foreach', err);
688
+ continue;
689
+ }
690
+ if (!Array.isArray(arr)) {
691
+ raiseErrorExecution(state, 'foreach', new Error('foreach array is not iterable'));
692
+ continue;
693
+ }
694
+ context ??= {};
695
+ for (let i = 0; i < arr.length; i++) {
696
+ context[scxmlAction.item] = arr[i];
697
+ if (scxmlAction.index) {
698
+ context[scxmlAction.index] = i;
699
+ }
700
+ const iterX = {
701
+ ...x,
702
+ context: {
703
+ ...x.context,
704
+ ...context
705
+ }
706
+ };
707
+ const iterResult = executeActions(scxmlAction.actions, iterX, enq, state);
708
+ if (iterResult.context) {
709
+ Object.assign(context, iterResult.context);
710
+ }
711
+ if (state.errored) break;
712
+ }
713
+ } else if (action.type === 'scxml.block') {
714
+ const scxmlAction = action;
715
+ const blockX = {
716
+ ...x,
717
+ context: {
718
+ ...x.context,
719
+ ...context
720
+ }
721
+ };
722
+ // Block has its own ExecState — errors don't propagate to parent.
723
+ const blockResult = executeActions(scxmlAction.actions, blockX, enq);
724
+ if (blockResult.context) {
725
+ context ??= {};
726
+ for (const key of Object.keys(blockResult.context)) {
727
+ if (blockResult.context[key] !== x.context[key]) {
728
+ context[key] = blockResult.context[key];
729
+ }
730
+ }
731
+ }
732
+ } else if (action.type === 'scxml.if') {
733
+ const scxmlAction = action;
734
+ const mergedContext = {
735
+ ...x.context,
736
+ ...context
737
+ };
738
+ for (const branch of scxmlAction.branches) {
739
+ let condResult;
740
+ if (branch.cond) {
741
+ try {
742
+ condResult = !!evaluateExpr(mergedContext, branch.cond, x.event, x.self);
743
+ } catch (err) {
744
+ raiseErrorExecution(state, 'if', err);
745
+ condResult = false;
746
+ }
747
+ } else {
748
+ condResult = true;
749
+ }
750
+ if (condResult) {
751
+ if (branch.actions.length) {
752
+ const branchX = {
753
+ ...x,
754
+ context: mergedContext
755
+ };
756
+ const branchResult = executeActions(branch.actions, branchX, enq, state);
757
+ if (branchResult.context) {
758
+ context ??= {};
759
+ for (const key of Object.keys(branchResult.context)) {
760
+ if (branchResult.context[key] !== x.context[key]) {
761
+ context[key] = branchResult.context[key];
762
+ }
763
+ }
764
+ }
765
+ }
766
+ break;
767
+ }
768
+ }
769
+ } else {
770
+ enq(x.actions[action.type], action.params);
771
+ }
772
+ }
773
+ return {
774
+ context: context ? {
775
+ ...x.context,
776
+ ...context
777
+ } : undefined,
778
+ errored: state.errored
779
+ };
780
+ }
781
+ function iterActions(actions) {
782
+ return (x, enq) => executeActions(actions, x, enq);
783
+ }
784
+ function getTransitionConfig(transition) {
785
+ const transitions = Array.isArray(transition) ? transition : [transition];
786
+
787
+ // Return an array of transition configs. Each SCXML transition becomes
788
+ // a separate XState transition with its own guard and optional .to.
789
+ // This ensures guards are evaluated by XState's evaluateCandidate (once,
790
+ // with pre-exit context) and NOT re-evaluated in computeEntrySet.
791
+ return transitions.map(t => {
792
+ const target = Array.isArray(t.target) ? t.target[0] : t.target;
793
+ const targetConfig = t.target;
794
+ const guard = resolveGuardConfig(t.guard);
795
+
796
+ // No guard and no actions: simple static config
797
+ if (!guard && !t.actions?.length) {
798
+ return {
799
+ target: targetConfig,
800
+ description: t.description,
801
+ reenter: t.reenter,
802
+ meta: t.meta,
803
+ input: t.input
804
+ };
805
+ }
806
+
807
+ // Guard but no actions: static config with guard
808
+ if (guard && !t.actions?.length) {
809
+ return {
810
+ target: targetConfig,
811
+ guard,
812
+ description: t.description,
813
+ reenter: t.reenter,
814
+ meta: t.meta,
815
+ input: t.input
816
+ };
817
+ }
818
+
819
+ // Targetless transitions: execute actions directly in .to AND track for
820
+ // parallel re-execution. For non-parallel, .to result is used directly.
821
+ // For parallel (entries exist), first entry re-executes all in document order.
822
+ if (!target) {
823
+ return {
824
+ guard,
825
+ description: t.description,
826
+ reenter: t.reenter,
827
+ meta: t.meta,
828
+ input: t.input,
829
+ to: (x, enq) => {
830
+ if (t.actions?.length) {
831
+ // Track for parallel re-execution (dedup by reference)
832
+ if (!allTransitionActions.includes(t.actions)) {
833
+ allTransitionActions.push(t.actions);
834
+ }
835
+ // Save pre-transition context for parallel override
836
+ contextBeforeTargetless ??= x.context;
837
+ // Execute immediately (fallback for non-parallel case)
838
+ const result = executeActions(t.actions, x, enq);
839
+ if (result.context) {
840
+ return {
841
+ context: result.context
842
+ };
843
+ }
844
+ }
845
+ return {};
846
+ }
847
+ };
848
+ }
849
+
850
+ // Has target + actions: use guard (for XState evaluation) + .to (for pending actions).
851
+ // The .to function does NOT re-check the guard — XState's evaluateCandidate
852
+ // already validated it. The .to just stores actions for entry to execute.
853
+ // Map by target state ID so parallel transitions each get their own actions.
854
+ return {
855
+ guard,
856
+ description: t.description,
857
+ reenter: t.reenter,
858
+ meta: t.meta,
859
+ input: t.input,
860
+ to: (_x, _enq) => {
861
+ if (t.actions?.length) {
862
+ const targetId = target.replace(/^#/, '');
863
+ pendingTransitionActionsMap[targetId] = t.actions;
864
+ // Track for parallel re-execution (dedup by reference)
865
+ if (!allTransitionActions.includes(t.actions)) {
866
+ allTransitionActions.push(t.actions);
867
+ }
868
+ }
869
+ return {
870
+ target: targetConfig,
871
+ reenter: t.reenter
872
+ };
873
+ }
874
+ };
875
+ });
876
+ }
877
+ const rootNodeConfig = iterNode(json);
878
+ const contextConfig = json.context ? {
879
+ context: json.context
880
+ } : {};
881
+ _scxmlMachineName = json.id || '';
882
+ _scxmlSessionId = 'session_' + Math.random().toString(36).slice(2);
883
+ const machine = createMachine({
884
+ ...rootNodeConfig,
885
+ ...contextConfig,
886
+ version: json.version
887
+ });
888
+
889
+ // Register SCXML guard implementations
890
+ const providedGuards = {
891
+ 'scxml.cond': ({
892
+ context,
893
+ event,
894
+ self
895
+ }, params) => {
896
+ const expr = params?.expr;
897
+ if (!expr) return true;
898
+ try {
899
+ return !!evaluateExpr(context, expr, event, self);
900
+ } catch (err) {
901
+ // Per SCXML spec, a cond that fails to evaluate is treated as false
902
+ // AND raises error.execution. We can't enqueue from a guard, so
903
+ // queue it for the next entry to drain.
904
+ const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
905
+ pendingPlatformErrors.push({
906
+ tagname: 'cond',
907
+ message,
908
+ line: NaN,
909
+ column: NaN,
910
+ reason: message
911
+ });
912
+ return false;
913
+ }
914
+ },
915
+ 'xstate.stateIn': (args, params) => {
916
+ const stateId = params?.stateId;
917
+ if (!stateId) return false;
918
+ const normalizedId = stateId.replace(/^#/, '');
919
+ const snapshot = args._snapshot;
920
+ if (snapshot?._nodes) {
921
+ return snapshot._nodes.some(node => node.id === normalizedId);
922
+ }
923
+ return false;
924
+ },
925
+ 'xstate.not': (args, params) => {
926
+ const inner = params?.guard;
927
+ const innerImpl = inner && args.guards?.[inner.type];
928
+ if (!innerImpl) {
929
+ throw new Error(`Guard '${inner?.type}' referenced by 'xstate.not' is not implemented.`);
930
+ }
931
+ return !innerImpl(args, inner.params);
932
+ }
933
+ };
934
+ const provided = machine.provide({
935
+ actions: resolvedImplementations.actions,
936
+ actorSources: resolvedImplementations.actorSources,
937
+ guards: {
938
+ ...providedGuards,
939
+ ...resolvedImplementations.guards
940
+ },
941
+ delays: resolvedImplementations.delays
942
+ });
943
+
944
+ // Keep the original JSON so `serializeMachine(machine)`
945
+ // round-trip losslessly.
946
+ provided._json = json;
947
+ return provided;
948
+ }
949
+ function isBuiltInActionJSON(action) {
950
+ return action.type.startsWith('@xstate.');
951
+ }
952
+
953
+ /**
954
+ * Machine-definition serialization.
955
+ *
956
+ * `machineConfigToJSON` converts a machine config into a JSON-safe structure
957
+ * (the `MachineJSON` shape accepted by `createMachineFromConfig`). The boundary
958
+ * between serializable structure and runtime implementations is explicit:
959
+ * functions are represented as code expressions, while actor logic, runtime
960
+ * schemas, and other non-code runtime values are replaced with an `{
961
+ * "$unserializable": <kind> }` marker rather than silently dropped.
962
+ *
963
+ * A machine definition is fully portable iff its JSON contains no
964
+ * `$unserializable` markers; reviving a definition that has markers requires
965
+ * re-providing those implementations (e.g. via `machine.provide(...)` or the
966
+ * `actions`/`guards`/`actorSources` maps on `createMachineFromConfig`
967
+ * revival).
968
+ */
969
+
970
+ /**
971
+ * Returns the JSON-serializable definition of a machine.
972
+ *
973
+ * Inline functions are represented as `{ "@type": "code", lang: "ts", expr }`.
974
+ * Actor logic and runtime schemas appear as `{ "$unserializable": ... }`
975
+ * markers. A machine created via `createMachineFromConfig` returns its original
976
+ * JSON config (lossless round-trip):
977
+ *
978
+ * ```ts
979
+ * import { serializeMachine, createMachineFromConfig } from 'xstate';
980
+ *
981
+ * const json = serializeMachine(machine);
982
+ * const revived = createMachineFromConfig(
983
+ * JSON.parse(JSON.stringify(json))
984
+ * );
985
+ * ```
986
+ */
987
+ function serializeMachine(machine) {
988
+ return machine._json ?? machineConfigToJSON(machine.config);
989
+ }
990
+ function marker(kind, id) {
991
+ const m = {
992
+ $unserializable: kind
993
+ };
994
+ if (id) {
995
+ m.id = id;
996
+ }
997
+ return m;
998
+ }
999
+
1000
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
1001
+ function codeExpression(fn) {
1002
+ return {
1003
+ '@type': 'code',
1004
+ lang: 'ts',
1005
+ expr: fn.toString()
1006
+ };
1007
+ }
1008
+ function isActorLogic(value) {
1009
+ return !!value && typeof value === 'object' && typeof value.transition === 'function' && typeof value.getInitialSnapshot === 'function';
1010
+ }
1011
+ function isRuntimeSchema(value) {
1012
+ return !!value && typeof value === 'object' && '~standard' in value;
1013
+ }
1014
+
1015
+ /** JSON-safe deep copy of a plain value; functions become code expressions. */
1016
+ function valueToJSON(value) {
1017
+ if (typeof value === 'function') {
1018
+ return codeExpression(value);
1019
+ }
1020
+ if (value === null || typeof value !== 'object') {
1021
+ return typeof value === 'bigint' || typeof value === 'symbol' ? marker('value') : value;
1022
+ }
1023
+ if (isActorLogic(value)) {
1024
+ return marker('actor', value.id);
1025
+ }
1026
+ if (isRuntimeSchema(value)) {
1027
+ return marker('schema');
1028
+ }
1029
+ if (Array.isArray(value)) {
1030
+ return value.map(valueToJSON);
1031
+ }
1032
+ if (value.constructor !== Object && value.constructor !== undefined) {
1033
+ // Class instances (actor logic, schemas, dates, ...) are not portable.
1034
+ return marker('value');
1035
+ }
1036
+ const result = {};
1037
+ for (const key of Object.keys(value)) {
1038
+ const v = value[key];
1039
+ if (v !== undefined) {
1040
+ result[key] = valueToJSON(v);
1041
+ }
1042
+ }
1043
+ return result;
1044
+ }
1045
+ function invokeToJSON(invoke) {
1046
+ if (Array.isArray(invoke)) {
1047
+ return invoke.map(invokeToJSON);
1048
+ }
1049
+ const def = invoke;
1050
+ const result = {};
1051
+ for (const key of Object.keys(def)) {
1052
+ const value = def[key];
1053
+ if (value === undefined) {
1054
+ continue;
1055
+ }
1056
+ result[key] = key === 'src' && typeof value !== 'string' ?
1057
+ // Actor logic keeps its dedicated marker kind so revival knows the
1058
+ // contract is an actor, not a plain function.
1059
+ marker('actor', value.id) : valueToJSON(value);
1060
+ }
1061
+ return result;
1062
+ }
1063
+ function implementationsToJSON(map, kind) {
1064
+ if (!map) {
1065
+ return undefined;
1066
+ }
1067
+ // Keys are preserved — they are the contract a revived machine must
1068
+ // fulfill via provide() — while values become markers (or stay, for
1069
+ // JSON-safe values like numeric delays).
1070
+ const result = {};
1071
+ for (const key of Object.keys(map)) {
1072
+ const value = map[key];
1073
+ if (kind === 'schema') {
1074
+ result[key] = marker('schema', key);
1075
+ } else if (typeof value === 'function') {
1076
+ result[key] = codeExpression(value);
1077
+ } else if (kind === 'actor' && value && typeof value === 'object') {
1078
+ result[key] = marker('actor', key);
1079
+ } else {
1080
+ result[key] = valueToJSON(value);
1081
+ }
1082
+ }
1083
+ return result;
1084
+ }
1085
+ function stateNodeConfigToJSON(config) {
1086
+ const result = {};
1087
+ for (const key of Object.keys(config)) {
1088
+ if (key === 'states' || key === 'invoke') {
1089
+ continue;
1090
+ }
1091
+ const value = config[key];
1092
+ if (value !== undefined) {
1093
+ result[key] = valueToJSON(value);
1094
+ }
1095
+ }
1096
+ if (config.invoke !== undefined) {
1097
+ result.invoke = invokeToJSON(config.invoke);
1098
+ }
1099
+ if (config.states) {
1100
+ const states = {};
1101
+ for (const key of Object.keys(config.states)) {
1102
+ states[key] = stateNodeConfigToJSON(config.states[key]);
1103
+ }
1104
+ result.states = states;
1105
+ }
1106
+ return result;
1107
+ }
1108
+
1109
+ /**
1110
+ * Converts a machine config (as passed to `createMachine`) to its JSON-safe
1111
+ * definition. See module docs for the `$unserializable` marker contract.
1112
+ */
1113
+ function machineConfigToJSON(config) {
1114
+ const result = stateNodeConfigToJSON(config);
1115
+ if (config.internalEvents !== undefined) {
1116
+ result.internalEvents = valueToJSON(config.internalEvents);
1117
+ }
1118
+ if (config.schemas) {
1119
+ const schemas = {};
1120
+ for (const key of Object.keys(config.schemas)) {
1121
+ const value = config.schemas[key];
1122
+ if (value && typeof value === 'object' && !('~standard' in value)) {
1123
+ // Map-form schemas (events/emitted): preserve event-type keys.
1124
+ schemas[key] = implementationsToJSON(value, 'schema');
1125
+ } else {
1126
+ schemas[key] = marker('schema', key);
1127
+ }
1128
+ }
1129
+ result.schemas = schemas;
1130
+ }
1131
+ for (const key of ['actions', 'guards', 'actorSources', 'delays']) {
1132
+ if (config[key]) {
1133
+ result[key] = implementationsToJSON(config[key], key === 'actorSources' ? 'actor' : 'function');
1134
+ }
1135
+ }
1136
+ return result;
88
1137
  }
89
1138
 
90
1139
  /**
@@ -133,11 +1182,245 @@ function mapState(snapshot, mapper) {
133
1182
  return results;
134
1183
  }
135
1184
 
1185
+ /** The Standard Schema interface. */
1186
+
1187
+ /** A type-only Standard Schema produced by {@link types}. */
1188
+
1189
+ /**
1190
+ * Declares schema types for inference only — no runtime validation.
1191
+ *
1192
+ * Use in a machine's `schemas` when you want TypeScript types without pulling
1193
+ * in a schema library like Zod. The returned value is a valid Standard Schema
1194
+ * whose validation is the identity function, so it carries types but never
1195
+ * rejects input.
1196
+ *
1197
+ * @example
1198
+ *
1199
+ * ```ts
1200
+ * import { createMachine, types } from 'xstate';
1201
+ *
1202
+ * const machine = createMachine({
1203
+ * schemas: {
1204
+ * context: types<{ count: number }>(),
1205
+ * events: {
1206
+ * inc: types<{ by: number }>(),
1207
+ * reset: types<{}>()
1208
+ * }
1209
+ * },
1210
+ * context: { count: 0 }
1211
+ * // ...
1212
+ * });
1213
+ * ```
1214
+ */
1215
+ function types() {
1216
+ return {
1217
+ '~standard': {
1218
+ version: 1,
1219
+ vendor: 'xstate.types',
1220
+ validate: value => ({
1221
+ value: value
1222
+ })
1223
+ }
1224
+ };
1225
+ }
1226
+
1227
+ /** Returns true if the value is a type-only schema created by {@link types}. */
1228
+ function isTypeSchema(value) {
1229
+ return !!value && typeof value === 'object' && '~standard' in value && value['~standard'].vendor === 'xstate.types';
1230
+ }
1231
+
1232
+ // eslint-disable-next-line @typescript-eslint/no-namespace
1233
+
1234
+ /** State schema with optional schemas.input and nested states */
1235
+
1236
+ /** Extracts input type from a state schema */
1237
+
1238
+ /**
1239
+ * Converts SetupStateSchema to StateSchema with input types included. This
1240
+ * allows getInputs() to be strongly typed.
1241
+ */
1242
+
1243
+ /** Converts the root setup states config to a StateSchema. */
1244
+
1245
+ /** Machine config with typed state input */
1246
+
1247
+ /** States config type that provides typed input for known states */
1248
+
1249
+ /** State node config that recursively applies typed input for nested states */
1250
+
1251
+ /** Initial transition with typed input based on target state */
1252
+
1253
+ /** Return type of setup() */
1254
+
1255
+ /**
1256
+ * Sets up a state machine with state input schemas and other configuration.
1257
+ *
1258
+ * @example
1259
+ *
1260
+ * ```ts
1261
+ * import { setup } from 'xstate';
1262
+ * import z from 'zod';
1263
+ *
1264
+ * const s = setup({
1265
+ * states: {
1266
+ * loading: {
1267
+ * schemas: {
1268
+ * input: z.object({
1269
+ * userId: z.string()
1270
+ * })
1271
+ * }
1272
+ * }
1273
+ * }
1274
+ * });
1275
+ *
1276
+ * const machine = s.createMachine({
1277
+ * initial: {
1278
+ * target: 'loading',
1279
+ * input: { userId: '123' }
1280
+ * },
1281
+ * states: {
1282
+ * loading: {
1283
+ * entry: ({ input }) => {
1284
+ * console.log(input.userId);
1285
+ * }
1286
+ * }
1287
+ * }
1288
+ * });
1289
+ * ```
1290
+ */
1291
+
1292
+ function setup(config = {}) {
1293
+ const {
1294
+ states = {},
1295
+ schemas,
1296
+ actions,
1297
+ actorSources,
1298
+ guards,
1299
+ delays
1300
+ } = config;
1301
+ return {
1302
+ extend(extension) {
1303
+ return setup(mergeSetupConfigs(config, extension));
1304
+ },
1305
+ createMachine(machineConfig) {
1306
+ const configSchemas = machineConfig.schemas;
1307
+ const mergedSchemas = mergeSchemas(configSchemas, schemas);
1308
+ const mergedActions = mergeMaps(actions, machineConfig.actions);
1309
+ const mergedActorSources = mergeMaps(actorSources, machineConfig.actorSources);
1310
+ const mergedGuards = mergeMaps(guards, machineConfig.guards);
1311
+ const mergedDelays = mergeMaps(delays, machineConfig.delays);
1312
+ return new StateMachine({
1313
+ ...machineConfig,
1314
+ ...(mergedSchemas ? {
1315
+ schemas: mergedSchemas
1316
+ } : undefined),
1317
+ ...(mergedActions ? {
1318
+ actions: mergedActions
1319
+ } : undefined),
1320
+ ...(mergedActorSources ? {
1321
+ actorSources: mergedActorSources
1322
+ } : undefined),
1323
+ ...(mergedGuards ? {
1324
+ guards: mergedGuards
1325
+ } : undefined),
1326
+ ...(mergedDelays ? {
1327
+ delays: mergedDelays
1328
+ } : undefined)
1329
+ });
1330
+ },
1331
+ createStateConfig(stateConfig) {
1332
+ return stateConfig;
1333
+ },
1334
+ states
1335
+ };
1336
+ }
1337
+ function createSystem(_config = {}) {
1338
+ const runtimeRef = {};
1339
+ const pendingObservers = [];
1340
+ const flushObservers = () => {
1341
+ const runtime = runtimeRef.current;
1342
+ if (!runtime) {
1343
+ return;
1344
+ }
1345
+ for (const entry of pendingObservers) {
1346
+ if (entry.active && !entry.subscription) {
1347
+ entry.subscription = runtime.inspect(entry.observer);
1348
+ }
1349
+ }
1350
+ };
1351
+ return {
1352
+ createActor(logic, options) {
1353
+ const actor = createActor(logic, {
1354
+ ...options,
1355
+ _systemRef: runtimeRef
1356
+ });
1357
+ flushObservers();
1358
+ return actor;
1359
+ },
1360
+ get(key) {
1361
+ return runtimeRef.current?.get(key);
1362
+ },
1363
+ getAll() {
1364
+ return runtimeRef.current?.getAll() ?? {};
1365
+ },
1366
+ inspect(observer) {
1367
+ const runtime = runtimeRef.current;
1368
+ if (runtime) {
1369
+ return runtime.inspect(observer);
1370
+ }
1371
+ const entry = {
1372
+ observer: observer,
1373
+ active: true
1374
+ };
1375
+ pendingObservers.push(entry);
1376
+ return {
1377
+ unsubscribe() {
1378
+ entry.active = false;
1379
+ entry.subscription?.unsubscribe();
1380
+ }
1381
+ };
1382
+ },
1383
+ setup(config) {
1384
+ return config ? setup(config) : setup();
1385
+ }
1386
+ };
1387
+ }
1388
+ function mergeMaps(left, right) {
1389
+ return left || right ? {
1390
+ ...left,
1391
+ ...right
1392
+ } : undefined;
1393
+ }
1394
+ function mergeSchemas(left, right) {
1395
+ if (!left && !right) {
1396
+ return undefined;
1397
+ }
1398
+ return {
1399
+ ...left,
1400
+ ...right,
1401
+ events: mergeMaps(left?.events, right?.events),
1402
+ emitted: mergeMaps(left?.emitted, right?.emitted),
1403
+ children: mergeMaps(left?.children, right?.children)
1404
+ };
1405
+ }
1406
+ function mergeSetupConfigs(base, extension) {
1407
+ return {
1408
+ ...base,
1409
+ ...extension,
1410
+ schemas: mergeSchemas(base.schemas, extension.schemas),
1411
+ states: mergeMaps(base.states, extension.states),
1412
+ actions: mergeMaps(base.actions, extension.actions),
1413
+ actorSources: mergeMaps(base.actorSources, extension.actorSources),
1414
+ guards: mergeMaps(base.guards, extension.guards),
1415
+ delays: mergeMaps(base.delays, extension.delays)
1416
+ };
1417
+ }
1418
+
136
1419
  /** @internal */
137
1420
  function createInertActorScope(actorLogic) {
138
1421
  const self = createActor(actorLogic);
139
1422
  const inertActorScope = {
140
- self,
1423
+ self: self,
141
1424
  defer: () => {},
142
1425
  id: '',
143
1426
  logger: () => {},
@@ -153,7 +1436,7 @@ function createInertActorScope(actorLogic) {
153
1436
  /** @deprecated Use `initialTransition(…)` instead. */
154
1437
  function getInitialSnapshot(actorLogic, ...[input]) {
155
1438
  const actorScope = createInertActorScope(actorLogic);
156
- return actorLogic.getInitialSnapshot(actorScope, input);
1439
+ return actorLogic.initialTransition(input, actorScope)[0];
157
1440
  }
158
1441
 
159
1442
  /**
@@ -192,64 +1475,8 @@ function getInitialSnapshot(actorLogic, ...[input]) {
192
1475
  function getNextSnapshot(actorLogic, snapshot, event) {
193
1476
  const inertActorScope = createInertActorScope(actorLogic);
194
1477
  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
- };
1478
+ const transitionResult = actorLogic.transition(snapshot, event, inertActorScope);
1479
+ return Array.isArray(transitionResult) ? transitionResult[0] : transitionResult;
253
1480
  }
254
1481
 
255
1482
  // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
@@ -314,6 +1541,11 @@ class SimulatedClock {
314
1541
  }
315
1542
  }
316
1543
  this._flushing = false;
1544
+ // Check if new timeouts were added during the last iteration
1545
+ if (this._flushingInvalidated) {
1546
+ this._flushingInvalidated = false;
1547
+ this.flushTimeouts();
1548
+ }
317
1549
  }
318
1550
  increment(ms) {
319
1551
  this._now += ms;
@@ -355,6 +1587,138 @@ function toPromise(actor) {
355
1587
  });
356
1588
  }
357
1589
 
1590
+ // this is needed to make JSDoc `@link` work properly
1591
+
1592
+ /**
1593
+ * @remarks
1594
+ * `T | unknown` reduces to `unknown` and that can be problematic when it comes
1595
+ * to contextual typing. It especially is a problem when the union has a
1596
+ * function member, like here:
1597
+ *
1598
+ * ```ts
1599
+ * declare function test(
1600
+ * cbOrVal: ((arg: number) => unknown) | unknown
1601
+ * ): void;
1602
+ * test((arg) => {}); // oops, implicit any
1603
+ * ```
1604
+ *
1605
+ * This type can be used to avoid this problem. This union represents the same
1606
+ * value space as `unknown`.
1607
+ */
1608
+
1609
+ // https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
1610
+
1611
+ // @TODO: we can't use native `NoInfer` as we need those:
1612
+ // https://github.com/microsoft/TypeScript/pull/61092
1613
+ // https://github.com/microsoft/TypeScript/pull/61077
1614
+ // but even with those fixes native NoInfer still doesn't work - further issues have to be reproduced and fixed
1615
+
1616
+ /** @deprecated Use the built-in `NoInfer` type instead */
1617
+
1618
+ /** The full definition of an event, with a string `type`. */
1619
+
1620
+ /**
1621
+ * The string or object representing the state value relative to the parent
1622
+ * state node.
1623
+ *
1624
+ * @remarks
1625
+ * - For a child atomic state node, this is a string, e.g., `"pending"`.
1626
+ * - For complex state nodes, this is an object, e.g., `{ success:
1627
+ * "someChildState" }`.
1628
+ */
1629
+
1630
+ // Accept any StateNode instance regardless of generic parameters
1631
+ // Using a union type to handle variance issues with machine.resolveState
1632
+
1633
+ let SpecialTargets = /*#__PURE__*/function (SpecialTargets) {
1634
+ SpecialTargets["Internal"] = "#_internal";
1635
+ return SpecialTargets;
1636
+ }({});
1637
+
1638
+ /**
1639
+ * A pending effect (delayed event/transition that has not fired yet) captured
1640
+ * in a persisted snapshot — a serialized built-in action descriptor plus its
1641
+ * runtime progress, with enough information to restore it under any strategy.
1642
+ */
1643
+
1644
+ /**
1645
+ * How pending effects (timers) from a persisted snapshot are restored on
1646
+ * `actor.start()`:
1647
+ *
1648
+ * - `'resume'` (default) — the clock was "frozen" while persisted: a 5-minute
1649
+ * timer persisted with 1 minute elapsed fires after 4 more minutes.
1650
+ * - `'restart'` — timers start over with their full delay.
1651
+ * - `'absolute'` — timers honor the original wall-clock expiry (`startedAt +
1652
+ * delay`); already-expired timers fire immediately.
1653
+ * - A custom function receiving the pending effect and returning the remaining
1654
+ * delay in ms.
1655
+ */
1656
+
1657
+ /** @deprecated Use `AnyActor` instead. */
1658
+
1659
+ // Based on RxJS types
1660
+
1661
+ /**
1662
+ * A consumer-facing actor handle.
1663
+ *
1664
+ * `ActorRef` describes the contract a consumer needs: which events can be sent
1665
+ * to the actor, which snapshot it publishes for observation, and which emitted
1666
+ * events can be listened to. It intentionally does not expose lifecycle control
1667
+ * or runtime internals. A concrete `Actor` satisfies this interface, so APIs
1668
+ * should accept `ActorRef` whenever they only need to send events, read
1669
+ * snapshots, or listen to emitted events.
1670
+ */
1671
+
1672
+ /**
1673
+ * Runtime-only actor capabilities.
1674
+ *
1675
+ * These members are needed by the interpreter, scheduler, inspection, and child
1676
+ * management code. Public consumer APIs should prefer `ActorRef` unless they
1677
+ * genuinely need lifecycle control such as starting, stopping, or accessing
1678
+ * system-owned runtime state.
1679
+ */
1680
+
1681
+ /**
1682
+ * A concrete actor instance type.
1683
+ *
1684
+ * `ActorInstance` combines the consumer `ActorRef` contract with runtime
1685
+ * lifecycle capabilities. Values returned by `createActor(...)` and
1686
+ * `spawn(...)` naturally satisfy narrower `ActorRef` contracts.
1687
+ */
1688
+
1689
+ /**
1690
+ * The actor's own full self handle.
1691
+ *
1692
+ * Internals and action/guard implementations receive this shape because `self`
1693
+ * can participate in runtime-owned behavior while still being usable wherever
1694
+ * an `ActorRef` is expected.
1695
+ */
1696
+
1697
+ // TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
1698
+
1699
+ /** The concrete actor instance type produced from actor logic. */
1700
+
1701
+ // TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
1702
+
1703
+ /**
1704
+ * Represents logic which can be used by an actor.
1705
+ *
1706
+ * @template TSnapshot - The type of the snapshot.
1707
+ * @template TEvent - The type of the event object.
1708
+ * @template TInput - The type of the input.
1709
+ * @template TSystem - The type of the actor system.
1710
+ */
1711
+
1712
+ // Actors that don't have literal string IDs — these are the only ones
1713
+ // that should appear in the index signature fallback, since actors with
1714
+ // literal IDs are already covered by ToConcreteChildren.
1715
+
1716
+ /** Maps state IDs to their input types based on the StateSchema. */
1717
+
1718
+ /** @deprecated */
1719
+
1720
+ /** Mappers for subscribeTo - maps lifecycle events to machine events */
1721
+
358
1722
  /**
359
1723
  * Given actor `logic`, a `snapshot`, and an `event`, returns a tuple of the
360
1724
  * `nextSnapshot` and `actions` to execute.
@@ -362,13 +1726,9 @@ function toPromise(actor) {
362
1726
  * This is a pure function that does not execute `actions`.
363
1727
  */
364
1728
  function transition(logic, snapshot, event) {
365
- const executableActions = [];
366
1729
  const actorScope = createInertActorScope(logic);
367
- actorScope.actionExecutor = action => {
368
- executableActions.push(action);
369
- };
370
- const nextSnapshot = logic.transition(snapshot, event, actorScope);
371
- return [nextSnapshot, executableActions];
1730
+ const [nextSnapshot, effects] = logic.transition(snapshot, event, actorScope);
1731
+ return [nextSnapshot, effects];
372
1732
  }
373
1733
 
374
1734
  /**
@@ -379,12 +1739,8 @@ function transition(logic, snapshot, event) {
379
1739
  * This is a pure function that does not execute `actions`.
380
1740
  */
381
1741
  function initialTransition(logic, ...[input]) {
382
- const executableActions = [];
383
1742
  const actorScope = createInertActorScope(logic);
384
- actorScope.actionExecutor = action => {
385
- executableActions.push(action);
386
- };
387
- const nextSnapshot = logic.getInitialSnapshot(actorScope, input);
1743
+ const [nextSnapshot, executableActions] = logic.initialTransition(input, actorScope);
388
1744
  return [nextSnapshot, executableActions];
389
1745
  }
390
1746
 
@@ -413,7 +1769,7 @@ function getInitialMicrosteps(machine, ...[input]) {
413
1769
  const actorScope = createInertActorScope(machine);
414
1770
  const initEvent = createInitEvent(input);
415
1771
  const internalQueue = [];
416
- const preInitialSnapshot = machine._getPreInitialState(actorScope, initEvent, internalQueue);
1772
+ const preInitialSnapshot = machine._getPreInitialState(actorScope, initEvent);
417
1773
  const first = initialMicrostep(machine.root, preInitialSnapshot, actorScope, initEvent, internalQueue);
418
1774
  const {
419
1775
  microsteps
@@ -464,7 +1820,7 @@ function getNextTransitions(state) {
464
1820
  // Get all transitions for each event type
465
1821
  // Include ALL transitions, even if the same event type appears in multiple state nodes
466
1822
  // This is important for guarded transitions - all are "potential" regardless of guard evaluation
467
- for (const [, transitions] of s.transitions) {
1823
+ for (const [, transitions] of s.transitions.entries()) {
468
1824
  potentialTransitions.push(...transitions);
469
1825
  }
470
1826
 
@@ -581,4 +1937,4 @@ function waitFor(actorRef, predicate, options) {
581
1937
  });
582
1938
  }
583
1939
 
584
- export { SimulatedClock, assertEvent, createMachine, getInitialMicrosteps, getInitialSnapshot, getMicrosteps, getNextSnapshot, getNextTransitions, initialTransition, mapState, setup, toPromise, transition, waitFor };
1940
+ export { SimulatedClock, SpecialTargets, assertEvent, createMachine, createMachineFromConfig, createStateConfig, createSystem, getInitialMicrosteps, getInitialSnapshot, getMicrosteps, getNextSnapshot, getNextTransitions, initialTransition, isTypeSchema, machineConfigToJSON, mapState, serializeMachine, setup, toPromise, transition, types, waitFor };