xstate 6.0.0-alpha.11 → 6.0.0-alpha.13

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 (40) hide show
  1. package/dist/{StateMachine-0aa98f54.development.esm.js → StateMachine-193c2d4d.development.esm.js} +43 -2
  2. package/dist/{StateMachine-aef700df.cjs.js → StateMachine-1b26c5de.cjs.js} +43 -2
  3. package/dist/{StateMachine-da872dde.development.cjs.js → StateMachine-3120a7ff.development.cjs.js} +43 -2
  4. package/dist/{StateMachine-d4931336.esm.js → StateMachine-614c0f36.esm.js} +43 -2
  5. package/dist/declarations/src/StateMachine.d.ts +1 -0
  6. package/dist/declarations/src/actors/promise.d.ts +4 -2
  7. package/dist/declarations/src/createActor.d.ts +2 -8
  8. package/dist/declarations/src/createMachineFromConfig.d.ts +56 -25
  9. package/dist/declarations/src/fsm.d.ts +74 -0
  10. package/dist/declarations/src/index.d.ts +2 -1
  11. package/dist/declarations/src/serialize.d.ts +8 -22
  12. package/dist/declarations/src/setup.d.ts +54 -23
  13. package/dist/declarations/src/stateUtils.d.ts +3 -1
  14. package/dist/declarations/src/transitionActions.d.ts +1 -1
  15. package/dist/declarations/src/types.d.ts +17 -7
  16. package/dist/declarations/src/types.v6.d.ts +25 -6
  17. package/dist/{index-219cb621.development.cjs.js → index-08d86676.development.cjs.js} +48 -27
  18. package/dist/{index-559ceab3.cjs.js → index-32631949.cjs.js} +48 -27
  19. package/dist/{index-8828376f.esm.js → index-603c1cda.esm.js} +45 -28
  20. package/dist/{index-2f2fbd9b.development.esm.js → index-7081e0c9.development.esm.js} +45 -28
  21. package/dist/xstate-actors.cjs.js +1 -1
  22. package/dist/xstate-actors.development.cjs.js +1 -1
  23. package/dist/xstate-actors.development.esm.js +1 -1
  24. package/dist/xstate-actors.esm.js +1 -1
  25. package/dist/xstate-actors.umd.min.js.map +1 -1
  26. package/dist/xstate-graph.cjs.js +2 -2
  27. package/dist/xstate-graph.development.cjs.js +2 -2
  28. package/dist/xstate-graph.development.esm.js +2 -2
  29. package/dist/xstate-graph.esm.js +2 -2
  30. package/dist/xstate-graph.umd.min.js +1 -1
  31. package/dist/xstate-graph.umd.min.js.map +1 -1
  32. package/dist/xstate.cjs.js +835 -144
  33. package/dist/xstate.cjs.mjs +1 -0
  34. package/dist/xstate.development.cjs.js +835 -144
  35. package/dist/xstate.development.cjs.mjs +1 -0
  36. package/dist/xstate.development.esm.js +838 -148
  37. package/dist/xstate.esm.js +838 -148
  38. package/dist/xstate.umd.min.js +1 -1
  39. package/dist/xstate.umd.min.js.map +1 -1
  40. package/package.json +1 -1
@@ -2,8 +2,8 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var dist_xstateActors = require('./index-559ceab3.cjs.js');
6
- var StateMachine = require('./StateMachine-aef700df.cjs.js');
5
+ var dist_xstateActors = require('./index-32631949.cjs.js');
6
+ var StateMachine = require('./StateMachine-1b26c5de.cjs.js');
7
7
 
8
8
  /**
9
9
  * Asserts that the given event object is of the specified type or types. Throws
@@ -91,6 +91,339 @@ function createStateConfig(config) {
91
91
  return config;
92
92
  }
93
93
 
94
+ const emptyImplementations = {
95
+ actions: {},
96
+ actorSources: {},
97
+ guards: {},
98
+ delays: {}
99
+ };
100
+ const emptyExecutableActions = [];
101
+ const emptyRawActions = [];
102
+ const builtInActionSet = new Set(Object.values(dist_xstateActors.builtInActions));
103
+ function toArray(value) {
104
+ return value === undefined ? [] : Array.isArray(value) ? value : [value];
105
+ }
106
+ function resolveContext(context, input) {
107
+ return typeof context === 'function' ? context({
108
+ input
109
+ }) : context ?? {};
110
+ }
111
+ function resolveTransitionContext(context, args) {
112
+ return typeof context === 'function' ? context(args) : context;
113
+ }
114
+ function resolveInput(input, context, event) {
115
+ return typeof input === 'function' ? input({
116
+ context,
117
+ event
118
+ }) : input;
119
+ }
120
+ function mergeContextPatch(context, patch) {
121
+ for (const key of Object.keys(patch)) {
122
+ if (!Object.prototype.hasOwnProperty.call(context, key) || !Object.is(context[key], patch[key])) {
123
+ return {
124
+ ...context,
125
+ ...patch
126
+ };
127
+ }
128
+ }
129
+ return context;
130
+ }
131
+ function createSnapshot(value, context, input, machine, stateInput) {
132
+ return {
133
+ status: 'active',
134
+ output: undefined,
135
+ error: undefined,
136
+ value,
137
+ context,
138
+ input,
139
+ children: {},
140
+ _stateInput: stateInput,
141
+ machine
142
+ };
143
+ }
144
+ function cloneSnapshot(snapshot, value, context, stateInput) {
145
+ return {
146
+ status: snapshot.status,
147
+ output: snapshot.output,
148
+ error: snapshot.error,
149
+ value,
150
+ context,
151
+ input: snapshot.input,
152
+ children: snapshot.children,
153
+ _stateInput: stateInput,
154
+ machine: snapshot.machine
155
+ };
156
+ }
157
+ function stopSnapshot(snapshot) {
158
+ return {
159
+ status: 'stopped',
160
+ output: undefined,
161
+ error: undefined,
162
+ value: snapshot.value,
163
+ context: snapshot.context,
164
+ input: undefined,
165
+ children: snapshot.children,
166
+ _stateInput: snapshot._stateInput,
167
+ machine: snapshot.machine
168
+ };
169
+ }
170
+ function assertNoStringTransitions(config) {
171
+ for (const [stateKey, stateConfig] of Object.entries(config.states)) {
172
+ for (const [eventType, transitionConfig] of Object.entries(stateConfig.on ?? {})) {
173
+ for (const transition of toArray(transitionConfig)) {
174
+ if (typeof transition === 'string') {
175
+ const target = transition;
176
+ throw new Error(`Invalid transition for "${stateKey}.${eventType}": use { target: "${target}" } instead of a string target.`);
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ function resolveSimpleEnqueuedActions(rawActions) {
183
+ const executableActions = [];
184
+ for (const action of rawActions) {
185
+ if (!action || typeof action !== 'object' || !('action' in action) || typeof action.action !== 'function' || builtInActionSet.has(action.action) || '_special' in action.action) {
186
+ return undefined;
187
+ }
188
+ executableActions.push({
189
+ type: action.action.name || '(anonymous)',
190
+ params: undefined,
191
+ args: action.args,
192
+ exec: action.args.length ? action.action.bind(null, ...action.args) : action.action
193
+ });
194
+ }
195
+ return executableActions;
196
+ }
197
+ function createFSM(config) {
198
+ const machine = {
199
+ id: config.id ?? '(fsm)',
200
+ implementations: emptyImplementations
201
+ };
202
+ assertNoStringTransitions(config);
203
+ const runActions = (snapshot, event, actorScope, rawActions) => {
204
+ if (!rawActions?.length) {
205
+ return [snapshot, emptyExecutableActions];
206
+ }
207
+ const simpleExecutableActions = resolveSimpleEnqueuedActions(rawActions);
208
+ if (simpleExecutableActions) {
209
+ return [snapshot, simpleExecutableActions];
210
+ }
211
+ return dist_xstateActors.resolveActionsWithContext(snapshot, event, actorScope, rawActions);
212
+ };
213
+ const runStateActions = (snapshot, event, actorScope, actionsConfig, stateInput, internalQueue) => {
214
+ if (!actionsConfig) {
215
+ return [snapshot, emptyExecutableActions];
216
+ }
217
+ const actions = [];
218
+ const enq = dist_xstateActors.createTransitionEnqueue(actorScope, actions, internalQueue, true);
219
+ let context;
220
+ const actionCount = Array.isArray(actionsConfig) ? actionsConfig.length : 1;
221
+ for (let i = 0; i < actionCount; i++) {
222
+ const action = Array.isArray(actionsConfig) ? actionsConfig[i] : actionsConfig;
223
+ const result = action({
224
+ context: context ?? snapshot.context,
225
+ event: event,
226
+ input: stateInput,
227
+ value: snapshot.value,
228
+ self: actorScope.self,
229
+ system: actorScope.system,
230
+ parent: actorScope.self._parent,
231
+ children: snapshot.children
232
+ }, enq);
233
+ if (result?.context !== undefined) {
234
+ const currentContext = context ?? snapshot.context;
235
+ const nextContext = mergeContextPatch(currentContext, result.context);
236
+ if (nextContext !== currentContext) {
237
+ context = nextContext;
238
+ }
239
+ }
240
+ }
241
+ const nextSnapshot = context !== undefined ? cloneSnapshot(snapshot, snapshot.value, context, snapshot._stateInput) : snapshot;
242
+ return runActions(nextSnapshot, event, actorScope, actions);
243
+ };
244
+ const selectTransition = (snapshot, event, actorScope, internalQueue) => {
245
+ const state = config.states[snapshot.value];
246
+ const transitionsConfig = state?.on?.[event.type];
247
+ if (!transitionsConfig) {
248
+ return undefined;
249
+ }
250
+ const transitionCount = Array.isArray(transitionsConfig) ? transitionsConfig.length : 1;
251
+ for (let i = 0; i < transitionCount; i++) {
252
+ const transition = Array.isArray(transitionsConfig) ? transitionsConfig[i] : transitionsConfig;
253
+ const args = {
254
+ context: snapshot.context,
255
+ event,
256
+ input: snapshot.input,
257
+ value: snapshot.value,
258
+ self: actorScope.self,
259
+ system: actorScope.system,
260
+ parent: actorScope.self._parent,
261
+ children: snapshot.children
262
+ };
263
+ if (typeof transition === 'function') {
264
+ const actions = [];
265
+ const enq = dist_xstateActors.createTransitionEnqueue(actorScope, actions, internalQueue, true);
266
+ const result = transition(args, enq);
267
+ if (!result) {
268
+ if (actions.length) {
269
+ return {
270
+ actions
271
+ };
272
+ }
273
+ continue;
274
+ }
275
+ return {
276
+ target: result.target,
277
+ context: result.context,
278
+ input: result.input,
279
+ actions
280
+ };
281
+ }
282
+ if ('guard' in transition && transition.guard && !transition.guard(args)) {
283
+ continue;
284
+ }
285
+ return {
286
+ target: transition.target,
287
+ context: resolveTransitionContext(transition.context, args),
288
+ input: transition.input,
289
+ actions: 'actions' in transition && transition.actions ? toArray(transition.actions) : emptyRawActions
290
+ };
291
+ }
292
+ return undefined;
293
+ };
294
+ const transition = (snapshot, event, actorScope) => {
295
+ if (snapshot.status !== 'active') {
296
+ return [snapshot, []];
297
+ }
298
+ if (event.type === dist_xstateActors.XSTATE_STOP) {
299
+ return [stopSnapshot(snapshot), emptyExecutableActions];
300
+ }
301
+ const stateConfig = config.states[snapshot.value];
302
+ const directTransition = stateConfig?.on?.[event.type];
303
+ if (!directTransition) {
304
+ return [snapshot, emptyExecutableActions];
305
+ }
306
+ if (!Array.isArray(directTransition) && typeof directTransition !== 'function' && !('guard' in directTransition) && !('actions' in directTransition && directTransition.actions) && typeof directTransition.input !== 'function') {
307
+ const target = directTransition.target ?? snapshot.value;
308
+ const stateChanged = target !== snapshot.value;
309
+ if (stateChanged && (stateConfig.exit || config.states[target]?.entry)) ; else {
310
+ const hasContext = directTransition.context !== undefined;
311
+ const hasInput = directTransition.input !== undefined;
312
+ const resolvedContext = resolveTransitionContext(directTransition.context, {
313
+ context: snapshot.context,
314
+ event,
315
+ input: snapshot.input,
316
+ value: snapshot.value,
317
+ self: actorScope.self,
318
+ system: actorScope.system,
319
+ parent: actorScope.self._parent,
320
+ children: snapshot.children
321
+ });
322
+ const context = hasContext && resolvedContext ? mergeContextPatch(snapshot.context, resolvedContext) : snapshot.context;
323
+ if (!stateChanged && context === snapshot.context && !hasInput && snapshot._stateInput === undefined) {
324
+ return [snapshot, emptyExecutableActions];
325
+ }
326
+ return [cloneSnapshot(snapshot, target, context, hasInput ? resolveInput(directTransition.input, context, event) : undefined), emptyExecutableActions];
327
+ }
328
+ }
329
+ if (typeof directTransition === 'function' && directTransition.length < 2 && !stateConfig?.exit) {
330
+ const result = directTransition({
331
+ context: snapshot.context,
332
+ event,
333
+ input: snapshot.input,
334
+ value: snapshot.value,
335
+ self: actorScope.self,
336
+ system: actorScope.system,
337
+ parent: actorScope.self._parent,
338
+ children: snapshot.children
339
+ }, undefined);
340
+ if (result) {
341
+ const target = result.target ?? snapshot.value;
342
+ if (!config.states[target]?.entry) {
343
+ const hasContext = result.context !== undefined;
344
+ const hasInput = result.input !== undefined;
345
+ const context = hasContext && result.context ? mergeContextPatch(snapshot.context, result.context) : snapshot.context;
346
+ if (target === snapshot.value && context === snapshot.context && !hasInput && snapshot._stateInput === undefined) {
347
+ return [snapshot, emptyExecutableActions];
348
+ }
349
+ return [cloneSnapshot(snapshot, target, context, hasInput ? resolveInput(result.input, context, event) : undefined), emptyExecutableActions];
350
+ }
351
+ }
352
+ }
353
+ let nextSnapshot = snapshot;
354
+ const executableActions = [];
355
+ const internalQueue = [event];
356
+ let iterations = 0;
357
+ while (internalQueue.length) {
358
+ if (++iterations > 1000) {
359
+ throw new Error('FSM microstep count exceeded 1000');
360
+ }
361
+ const nextEvent = internalQueue.shift();
362
+ const selected = selectTransition(nextSnapshot, nextEvent, actorScope, internalQueue);
363
+ if (!selected) {
364
+ continue;
365
+ }
366
+ const nextValue = selected.target ?? nextSnapshot.value;
367
+ const stateChanged = nextValue !== nextSnapshot.value;
368
+ if (stateChanged) {
369
+ const [exited, exitActions] = runStateActions(nextSnapshot, nextEvent, actorScope, config.states[nextSnapshot.value]?.exit, nextSnapshot._stateInput, internalQueue);
370
+ nextSnapshot = exited;
371
+ executableActions.push(...exitActions);
372
+ }
373
+ let context = nextSnapshot.context;
374
+ if (selected.context !== undefined) {
375
+ context = mergeContextPatch(context, selected.context);
376
+ }
377
+ const hasInput = selected.input !== undefined;
378
+ const stateInput = hasInput ? resolveInput(selected.input, context, nextEvent) : undefined;
379
+ if (stateChanged || context !== nextSnapshot.context || hasInput || nextSnapshot._stateInput !== undefined) {
380
+ nextSnapshot = cloneSnapshot(nextSnapshot, nextValue, context, stateInput);
381
+ }
382
+ const [afterTransition, transitionActions] = runActions(nextSnapshot, nextEvent, actorScope, selected.actions);
383
+ nextSnapshot = afterTransition;
384
+ executableActions.push(...transitionActions);
385
+ if (stateChanged) {
386
+ const [entered, entryActions] = runStateActions(nextSnapshot, nextEvent, actorScope, config.states[nextValue]?.entry, stateInput, internalQueue);
387
+ nextSnapshot = entered;
388
+ executableActions.push(...entryActions);
389
+ }
390
+ }
391
+ return [nextSnapshot, executableActions];
392
+ };
393
+ const logic = {
394
+ id: config.id,
395
+ config,
396
+ transition,
397
+ initialTransition: (input, actorScope) => {
398
+ const context = resolveContext(config.context, input);
399
+ const snapshot = createSnapshot(config.initial, context, input, machine);
400
+ const internalQueue = [];
401
+ let [nextSnapshot, actions] = runStateActions(snapshot, {
402
+ type: dist_xstateActors.XSTATE_INIT
403
+ }, actorScope, config.states[config.initial]?.entry, undefined, internalQueue);
404
+ if (!actions.length) {
405
+ actions = [];
406
+ }
407
+ while (internalQueue.length) {
408
+ const [raisedSnapshot, raisedActions] = transition(nextSnapshot, internalQueue.shift(), actorScope);
409
+ nextSnapshot = raisedSnapshot;
410
+ actions.push(...raisedActions);
411
+ }
412
+ return [nextSnapshot, actions];
413
+ },
414
+ getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
415
+ getPersistedSnapshot: ({
416
+ machine: _,
417
+ ...snapshot
418
+ }) => snapshot,
419
+ restoreSnapshot: snapshot => ({
420
+ ...snapshot,
421
+ machine: machine
422
+ })
423
+ };
424
+ return logic;
425
+ }
426
+
94
427
  function delayToMs(delay) {
95
428
  const parsedDelay = dist_xstateActors.parseDelayToMilliseconds(delay);
96
429
  if (parsedDelay !== undefined) return parsedDelay;
@@ -103,48 +436,23 @@ function delayToMs(delay) {
103
436
  * separate <onentry>/<onexit> blocks: each is its own block per spec.
104
437
  */
105
438
 
106
- function isUnserializableMarker(value) {
107
- return !!value && typeof value === 'object' && '$unserializable' in value && typeof value.$unserializable === 'string';
439
+ function isExpression(value) {
440
+ return !!value && typeof value === 'object' && typeof value['@expr'] === 'string';
108
441
  }
109
- function assertNoUnresolvedMarkers(value, path = '$') {
110
- if (isUnserializableMarker(value)) {
111
- if (value.$unserializable === 'actor' && path.endsWith('.src')) {
112
- return;
113
- }
114
- throw new Error(`Unresolved ${value.$unserializable} at ${path}`);
115
- }
116
- if (!value || typeof value !== 'object') {
117
- return;
118
- }
119
- if (Array.isArray(value)) {
120
- value.forEach((item, index) => assertNoUnresolvedMarkers(item, `${path}[${index}]`));
121
- return;
122
- }
123
- for (const key of Object.keys(value)) {
124
- const child = value[key];
125
- const isImplementationMarker = path === '$' && (key === 'actions' || key === 'guards' || key === 'actorSources' || key === 'delays' || key === 'schemas');
126
- if (isImplementationMarker) {
127
- continue;
128
- }
129
- assertNoUnresolvedMarkers(child, `${path}.${key}`);
130
- }
442
+ function isCode(value) {
443
+ return !!value && typeof value === 'object' && typeof value['@code'] === 'string';
131
444
  }
132
- function assertRootImplementationMarkers(json, implementations) {
133
- for (const kind of ['actions', 'guards', 'actorSources', 'delays']) {
134
- const map = json[kind];
135
- if (!map) {
136
- continue;
137
- }
138
- for (const key of Object.keys(map)) {
139
- const value = map[key];
140
- if (!isUnserializableMarker(value)) {
141
- continue;
142
- }
143
- if (!implementations[kind]?.[key]) {
144
- throw new Error(`Missing ${kind}.${key}`);
145
- }
146
- }
147
- }
445
+ function isResolvable(value) {
446
+ return isExpression(value) || isCode(value);
447
+ }
448
+ function isBuiltInActionType(type) {
449
+ return type.startsWith('@xstate.');
450
+ }
451
+ function isScxmlActionType(type) {
452
+ return type.startsWith('scxml.');
453
+ }
454
+ function toPath(parent, key) {
455
+ return typeof key === 'number' ? `${parent}[${key}]` : `${parent}.${key}`;
148
456
  }
149
457
  function extractSerializableDelays(delays) {
150
458
  const result = {};
@@ -155,6 +463,8 @@ function extractSerializableDelays(delays) {
155
463
  const value = delays[key];
156
464
  if (typeof value === 'number') {
157
465
  result[key] = value;
466
+ } else if (value && typeof value === 'object' && typeof value.duration === 'number') {
467
+ result[key] = value.duration;
158
468
  }
159
469
  }
160
470
  return result;
@@ -167,9 +477,248 @@ function mergeImplementations(json, implementations) {
167
477
  delays: {
168
478
  ...extractSerializableDelays(json.delays),
169
479
  ...(implementations.delays ?? {})
480
+ },
481
+ evaluators: implementations.evaluators ?? {}
482
+ };
483
+ }
484
+ function createExpressionResolver(expressionLanguage, implementations) {
485
+ function getEvaluator(value, path) {
486
+ const lang = value['@lang'] ?? expressionLanguage;
487
+ if (!lang) {
488
+ throw new Error(`Missing @exprLang for expression at ${path}`);
489
+ }
490
+ const evaluator = implementations.evaluators[lang];
491
+ if (!evaluator) {
492
+ throw new Error(`Missing evaluator for @lang '${lang}' at ${path}`);
493
+ }
494
+ return evaluator;
495
+ }
496
+ function evaluateResolvable(value, slot, scope, path) {
497
+ const kind = isExpression(value) ? 'expr' : 'code';
498
+ const source = isExpression(value) ? value['@expr'] : value['@code'];
499
+ return getEvaluator(value, path)({
500
+ source,
501
+ kind,
502
+ slot,
503
+ scope,
504
+ path
505
+ });
506
+ }
507
+ function resolveValue(value, slot, scope, path) {
508
+ if (isResolvable(value)) {
509
+ return evaluateResolvable(value, slot, scope, path);
510
+ }
511
+ if (Array.isArray(value)) {
512
+ return value.map((item, index) => resolveValue(item, slot, scope, toPath(path, index)));
513
+ }
514
+ if (!value || typeof value !== 'object') {
515
+ return value;
516
+ }
517
+ const result = {};
518
+ for (const key of Object.keys(value)) {
519
+ result[key] = resolveValue(value[key], slot, scope, toPath(path, key));
520
+ }
521
+ return result;
522
+ }
523
+ function makeScope(x, extra) {
524
+ return {
525
+ context: x.context,
526
+ event: x.event,
527
+ input: x.input,
528
+ self: x.self,
529
+ children: x.children,
530
+ params: x.params,
531
+ ...extra
532
+ };
533
+ }
534
+ function getDurationConfig(value, path) {
535
+ if (!isResolvable(value)) {
536
+ return value;
537
+ }
538
+ return args => delayToMs(resolveValue(value, 'delay', makeScope(args), path));
539
+ }
540
+ function assertResolvable(value, path) {
541
+ if (isResolvable(value)) {
542
+ getEvaluator(value, path);
543
+ return;
544
+ }
545
+ if (Array.isArray(value)) {
546
+ value.forEach((item, index) => assertResolvable(item, toPath(path, index)));
547
+ return;
548
+ }
549
+ if (!value || typeof value !== 'object') {
550
+ return;
551
+ }
552
+ for (const key of Object.keys(value)) {
553
+ assertResolvable(value[key], toPath(path, key));
170
554
  }
555
+ }
556
+ return {
557
+ evaluateResolvable,
558
+ resolveValue,
559
+ makeScope,
560
+ getDurationConfig,
561
+ assertResolvable
171
562
  };
172
563
  }
564
+ function toActionArray(actions) {
565
+ return actions === undefined ? [] : Array.isArray(actions) ? actions : [actions];
566
+ }
567
+ function validateChoiceConfig(choice, path) {
568
+ if (!Array.isArray(choice)) {
569
+ return;
570
+ }
571
+ choice.forEach((branch, index) => {
572
+ if (branch.when === undefined && index !== choice.length - 1) {
573
+ throw new Error(`Choice fallback branch at ${path}[${index}] must be last.`);
574
+ }
575
+ });
576
+ }
577
+ function assertMachineJSON(json, resolvedImplementations, expressionResolver) {
578
+ const {
579
+ assertResolvable
580
+ } = expressionResolver;
581
+ function assertCondition(condition, path) {
582
+ if (!condition) {
583
+ return;
584
+ }
585
+ if (isResolvable(condition)) {
586
+ assertResolvable(condition, path);
587
+ return;
588
+ }
589
+ assertResolvable(condition.params, `${path}.params`);
590
+ if (json.guards?.[condition.type]) {
591
+ assertCondition(json.guards[condition.type].when, `$.guards.${condition.type}.when`);
592
+ return;
593
+ }
594
+ if (!resolvedImplementations.guards[condition.type] && !['scxml.cond', 'xstate.stateIn', 'xstate.not'].includes(condition.type)) {
595
+ throw new Error(`Missing guard implementation "${condition.type}"`);
596
+ }
597
+ }
598
+ function assertAction(action, path, stack = []) {
599
+ if (isResolvable(action)) {
600
+ assertResolvable(action, path);
601
+ return;
602
+ }
603
+ if (!action || typeof action.type !== 'string') {
604
+ throw new Error(`Invalid action at ${path}`);
605
+ }
606
+ assertResolvable(action.params, `${path}.params`);
607
+ if (isBuiltInActionType(action.type) || isScxmlActionType(action.type)) {
608
+ assertResolvable(action, path);
609
+ return;
610
+ }
611
+ const definition = json.actions?.[action.type];
612
+ if (definition) {
613
+ if (stack.includes(action.type)) {
614
+ throw new Error(`Circular action reference: ${stack.concat(action.type).join(' -> ')}`);
615
+ }
616
+ const definitions = Array.isArray(definition) ? definition : [definition];
617
+ definitions.forEach((item, index) => assertAction(item, `${path}.actions.${action.type}${definitions.length > 1 ? `[${index}]` : ''}`, stack.concat(action.type)));
618
+ return;
619
+ }
620
+ if (!resolvedImplementations.actions[action.type]) {
621
+ throw new Error(`Missing action implementation "${action.type}"`);
622
+ }
623
+ }
624
+ function assertActions(actions, path) {
625
+ toActionArray(actions).forEach((action, index) => assertAction(action, `${path}[${index}]`));
626
+ }
627
+ function assertTransition(transition, path) {
628
+ const transitions = Array.isArray(transition) ? transition : transition ? [transition] : [];
629
+ transitions.forEach((t, index) => {
630
+ const transitionPath = Array.isArray(transition) ? `${path}[${index}]` : path;
631
+ if (isResolvable(t)) {
632
+ assertResolvable(t, transitionPath);
633
+ return;
634
+ }
635
+ assertCondition(t.guard, `${transitionPath}.guard`);
636
+ assertActions(t.actions, `${transitionPath}.actions`);
637
+ assertResolvable(t.context, `${transitionPath}.context`);
638
+ assertResolvable(t.input, `${transitionPath}.input`);
639
+ });
640
+ }
641
+ function assertStateNode(node, path) {
642
+ assertResolvable(node.context, `${path}.context`);
643
+ assertResolvable(node.input, `${path}.input`);
644
+ assertResolvable(node.output, `${path}.output`);
645
+ assertResolvable(node.timeout, `${path}.timeout`);
646
+ assertActions(node.entry, `${path}.entry`);
647
+ assertActions(node.exit, `${path}.exit`);
648
+ if (node.type === 'choice') {
649
+ if (!node.choice) {
650
+ throw new Error(`Choice state at ${path} must declare choice.`);
651
+ }
652
+ if (Array.isArray(node.choice)) {
653
+ validateChoiceConfig(node.choice, `${path}.choice`);
654
+ node.choice.forEach((branch, index) => {
655
+ assertCondition(branch.when, `${path}.choice[${index}].when`);
656
+ assertResolvable(branch.context, `${path}.choice[${index}].context`);
657
+ assertResolvable(branch.input, `${path}.choice[${index}].input`);
658
+ });
659
+ } else {
660
+ assertResolvable(node.choice, `${path}.choice`);
661
+ }
662
+ }
663
+ if (isResolvable(node.route)) {
664
+ assertResolvable(node.route, `${path}.route`);
665
+ }
666
+ if (node.invoke) {
667
+ const invokes = Array.isArray(node.invoke) ? node.invoke : [node.invoke];
668
+ invokes.forEach((invoke, index) => {
669
+ const invokePath = `${path}.invoke${Array.isArray(node.invoke) ? `[${index}]` : ''}`;
670
+ const extInv = invoke;
671
+ if (!extInv._nestedMachineJSON && !resolvedImplementations.actorSources[invoke.src]) {
672
+ throw new Error(`Missing actorSource implementation "${invoke.src}"`);
673
+ }
674
+ assertResolvable(invoke.input, `${invokePath}.input`);
675
+ assertResolvable(invoke.timeout, `${invokePath}.timeout`);
676
+ assertTransition(invoke.onDone, `${invokePath}.onDone`);
677
+ assertTransition(invoke.onError, `${invokePath}.onError`);
678
+ assertTransition(invoke.onSnapshot, `${invokePath}.onSnapshot`);
679
+ assertTransition(invoke.onTimeout, `${invokePath}.onTimeout`);
680
+ });
681
+ }
682
+ if (node.on) {
683
+ for (const descriptor of Object.keys(node.on)) {
684
+ assertTransition(node.on[descriptor], `${path}.on.${descriptor}`);
685
+ }
686
+ }
687
+ if (node.after) {
688
+ for (const delay of Object.keys(node.after)) {
689
+ if (Number.isNaN(Number(delay)) && dist_xstateActors.parseDelayToMilliseconds(delay) === undefined && !json.delays?.[delay] && !resolvedImplementations.delays[delay]) {
690
+ throw new Error(`Missing delay implementation "${delay}"`);
691
+ }
692
+ assertTransition(node.after[delay], `${path}.after.${delay}`);
693
+ }
694
+ }
695
+ assertTransition(node.always, `${path}.always`);
696
+ assertTransition(node.onTimeout, `${path}.onTimeout`);
697
+ if (node.states) {
698
+ for (const key of Object.keys(node.states)) {
699
+ assertStateNode(node.states[key], `${path}.states.${key}`);
700
+ }
701
+ }
702
+ }
703
+ if (json.actions) {
704
+ for (const key of Object.keys(json.actions)) {
705
+ const actions = Array.isArray(json.actions[key]) ? json.actions[key] : [json.actions[key]];
706
+ actions.forEach((action, index) => assertAction(action, `$.actions.${key}${actions.length > 1 ? `[${index}]` : ''}`, [key]));
707
+ }
708
+ }
709
+ if (json.guards) {
710
+ for (const key of Object.keys(json.guards)) {
711
+ assertCondition(json.guards[key].when, `$.guards.${key}.when`);
712
+ }
713
+ }
714
+ if (json.delays) {
715
+ for (const key of Object.keys(json.delays)) {
716
+ const delay = json.delays[key];
717
+ assertResolvable(delay && typeof delay === 'object' && 'duration' in delay ? delay.duration : delay, `$.delays.${key}`);
718
+ }
719
+ }
720
+ assertStateNode(json, '$');
721
+ }
173
722
  let _scxmlSessionId = 'session_' + Math.random().toString(36).slice(2);
174
723
  let _scxmlMachineName = '';
175
724
 
@@ -243,9 +792,64 @@ return updates;
243
792
  return fn(context, updates);
244
793
  }
245
794
  function createMachineFromConfig(json, implementations = {}) {
246
- assertRootImplementationMarkers(json, implementations);
247
- assertNoUnresolvedMarkers(json);
248
795
  const resolvedImplementations = mergeImplementations(json, implementations);
796
+ const expressionResolver = createExpressionResolver(json['@exprLang'], resolvedImplementations);
797
+ const {
798
+ evaluateResolvable,
799
+ resolveValue,
800
+ makeScope,
801
+ getDurationConfig
802
+ } = expressionResolver;
803
+ function resolveCondition(condition, slot, path) {
804
+ if (!condition) {
805
+ return undefined;
806
+ }
807
+ if (isResolvable(condition)) {
808
+ return args => !!evaluateResolvable(condition, slot, makeScope(args), path);
809
+ }
810
+ return args => {
811
+ const params = resolveValue(condition.params, slot, makeScope(args), `${path}.params`);
812
+ const declarativeGuard = json.guards?.[condition.type];
813
+ if (declarativeGuard) {
814
+ const guard = resolveCondition(declarativeGuard.when, 'guard', `$.guards.${condition.type}.when`);
815
+ return !!guard?.({
816
+ ...args,
817
+ params
818
+ });
819
+ }
820
+ const guardImpl = args.guards?.[condition.type];
821
+ if (!guardImpl) {
822
+ throw new Error(getMissingGuardMessage(condition.type, args.guards ?? {}));
823
+ }
824
+ return guardImpl(args, params);
825
+ };
826
+ }
827
+ function makeChoiceConfig(choice, path) {
828
+ if (!choice) {
829
+ return undefined;
830
+ }
831
+ if (isResolvable(choice)) {
832
+ return args => evaluateResolvable(choice, 'choice', makeScope(args), path);
833
+ }
834
+ validateChoiceConfig(choice, path);
835
+ return args => {
836
+ for (let index = 0; index < choice.length; index++) {
837
+ const branch = choice[index];
838
+ const guard = resolveCondition(branch.when, 'choice', `${path}[${index}].when`);
839
+ if (!guard || guard(args)) {
840
+ return {
841
+ target: branch.target,
842
+ context: branch.context ? resolveValue(branch.context, 'transitionContext', makeScope(args), `${path}[${index}].context`) : undefined,
843
+ input: branch.input !== undefined ? resolveValue(branch.input, 'input', makeScope(args), `${path}[${index}].input`) : undefined,
844
+ description: branch.description,
845
+ reenter: branch.reenter,
846
+ meta: branch.meta
847
+ };
848
+ }
849
+ }
850
+ throw new Error(`Choice state at ${path} did not match any branch.`);
851
+ };
852
+ }
249
853
 
250
854
  // Pending transition actions: set by .to functions, consumed by entry functions.
251
855
  // This bridges SCXML's exit→transition→entry action ordering with XState's
@@ -268,22 +872,13 @@ function createMachineFromConfig(json, implementations = {}) {
268
872
  function getMissingGuardMessage(type, guards) {
269
873
  return `Guard '${type}' is not implemented in machine '${json.id ?? '(machine)'}'. Available guards: ${Object.keys(guards).map(key => `'${key}'`).join(', ') || '(none)'}.`;
270
874
  }
271
- function resolveGuardConfig(guard) {
272
- if (!guard) {
273
- return undefined;
274
- }
275
- return args => {
276
- const guardImpl = args.guards?.[guard.type];
277
- if (!guardImpl) {
278
- throw new Error(getMissingGuardMessage(guard.type, args.guards ?? {}));
279
- }
280
- return guardImpl(args, guard.params);
281
- };
282
- }
283
- function resolveRouteConfig(route) {
875
+ function resolveRouteConfig(route, path) {
284
876
  if (!route || typeof route === 'function') {
285
877
  return route;
286
878
  }
879
+ if (isResolvable(route)) {
880
+ return args => evaluateResolvable(route, 'transition', makeScope(args), path);
881
+ }
287
882
  const {
288
883
  guard,
289
884
  ...routeConfig
@@ -291,13 +886,13 @@ function createMachineFromConfig(json, implementations = {}) {
291
886
  if (!guard) {
292
887
  return routeConfig;
293
888
  }
294
- const resolvedGuard = resolveGuardConfig({
889
+ const resolvedGuard = resolveCondition({
295
890
  type: guard
296
- });
891
+ }, 'guard', path);
297
892
  return args => resolvedGuard(args) ? routeConfig : undefined;
298
893
  }
299
894
  function iterNode(node, nodeKey) {
300
- const originalEntryActions = node.entry;
895
+ const originalEntryActions = toActionArray(node.entry);
301
896
  const stateId = node.id || nodeKey;
302
897
 
303
898
  // Wrap entry to first execute any pending transition actions, then normal entry.
@@ -360,7 +955,7 @@ function createMachineFromConfig(json, implementations = {}) {
360
955
  }
361
956
 
362
957
  // Execute normal entry actions
363
- if (originalEntryActions?.length) {
958
+ if (originalEntryActions.length) {
364
959
  const mergedX = context ? {
365
960
  ...x,
366
961
  context
@@ -383,7 +978,7 @@ function createMachineFromConfig(json, implementations = {}) {
383
978
  description: node.description,
384
979
  tags: node.tags,
385
980
  input: node.input,
386
- timeout: node.timeout,
981
+ timeout: getDurationConfig(node.timeout, `$.states.${nodeKey}.timeout`),
387
982
  states: node.states ? Object.entries(node.states).reduce((acc, [key, value]) => {
388
983
  acc[key] = iterNode(value, key);
389
984
  return acc;
@@ -393,18 +988,26 @@ function createMachineFromConfig(json, implementations = {}) {
393
988
  return acc;
394
989
  }, {}) : undefined,
395
990
  always: node.always ? getTransitionConfig(node.always) : undefined,
396
- choice: node.choice ? getTransitionConfig(node.choice) : undefined,
397
- route: resolveRouteConfig(node.route),
991
+ choice: makeChoiceConfig(node.choice, `$.states.${nodeKey}.choice`),
992
+ route: resolveRouteConfig(node.route, `$.states.${nodeKey}.route`),
398
993
  after: node.after ? Object.entries(node.after).reduce((acc, [key, value]) => {
399
994
  acc[key] = getTransitionConfig(value);
400
995
  return acc;
401
996
  }, {}) : undefined,
402
997
  onTimeout: node.onTimeout ? getTransitionConfig(node.onTimeout) : undefined,
403
- entry: entryFn,
404
- exit: node.exit ? iterActions(node.exit) : undefined,
998
+ entry: node.type === 'choice' ? undefined : entryFn,
999
+ exit: node.exit ? iterActions(toActionArray(node.exit)) : undefined,
405
1000
  invoke: node.invoke ? iterInvokeConfigs(node.invoke) : undefined,
406
1001
  meta: node.meta,
407
- output: node._scxmlDonedata ? makeDonedataOutput(node._scxmlDonedata) : node.output
1002
+ output: node._scxmlDonedata ? makeDonedataOutput(node._scxmlDonedata) : isResolvable(node.output) ? ({
1003
+ context,
1004
+ event,
1005
+ self
1006
+ }) => evaluateResolvable(node.output, 'output', {
1007
+ context,
1008
+ event,
1009
+ self
1010
+ }, `$.states.${nodeKey}.output`) : node.output
408
1011
  };
409
1012
  return nodeConfig;
410
1013
  }
@@ -457,23 +1060,18 @@ function createMachineFromConfig(json, implementations = {}) {
457
1060
  let src;
458
1061
  if (extInv._nestedMachineJSON) {
459
1062
  src = createMachineFromConfig(extInv._nestedMachineJSON, resolvedImplementations);
460
- } else if (isUnserializableMarker(inv.src)) {
461
- src = inv.src.id ? resolvedImplementations.actorSources[inv.src.id] : undefined;
462
- if (!src) {
463
- throw new Error(`Missing actorSources.${inv.src.id ?? ''}`);
464
- }
465
1063
  } else {
466
- src = inv.src;
1064
+ src = resolvedImplementations.actorSources[inv.src] ?? inv.src;
467
1065
  }
468
1066
  return {
469
1067
  src,
470
1068
  id: inv.id,
471
1069
  registryKey: inv.registryKey,
472
- input: inv.input,
1070
+ input: inv.input !== undefined ? args => resolveValue(inv.input, 'input', makeScope(args), '$.invoke.input') : undefined,
473
1071
  onDone: inv.onDone ? getTransitionConfig(inv.onDone) : undefined,
474
1072
  onError: inv.onError ? getTransitionConfig(inv.onError) : undefined,
475
1073
  onSnapshot: inv.onSnapshot ? getTransitionConfig(inv.onSnapshot) : undefined,
476
- timeout: inv.timeout,
1074
+ timeout: getDurationConfig(inv.timeout, '$.invoke.timeout'),
477
1075
  onTimeout: inv.onTimeout ? getTransitionConfig(inv.onTimeout) : undefined
478
1076
  };
479
1077
  });
@@ -499,8 +1097,31 @@ function createMachineFromConfig(json, implementations = {}) {
499
1097
  });
500
1098
  state.errored = true;
501
1099
  }
1100
+ function executeActionDefinition(action, x, enq, params, parentState, stack) {
1101
+ const definition = json.actions?.[action.type];
1102
+ if (!definition) {
1103
+ enq(x.actions[action.type], params);
1104
+ return {
1105
+ context: undefined,
1106
+ errored: parentState.errored
1107
+ };
1108
+ }
1109
+ if (stack.includes(action.type)) {
1110
+ raiseErrorExecution(parentState, 'action', new Error(`Circular action reference: ${stack.concat(action.type).join(' -> ')}`));
1111
+ return {
1112
+ context: undefined,
1113
+ errored: true
1114
+ };
1115
+ }
1116
+ const definitions = Array.isArray(definition) ? definition : [definition];
1117
+ return executeActions(definitions, {
1118
+ ...x,
1119
+ params
1120
+ }, enq, parentState, stack.concat(action.type));
1121
+ }
1122
+
502
1123
  /** Execute an array of SCXML action JSON descriptors with context and enqueue. */
503
- function executeActions(actions, x, enq, parentState) {
1124
+ function executeActions(actions, x, enq, parentState, stack = []) {
504
1125
  const state = parentState ?? {
505
1126
  enq,
506
1127
  errored: false
@@ -508,22 +1129,34 @@ function createMachineFromConfig(json, implementations = {}) {
508
1129
  let context;
509
1130
  for (const action of actions) {
510
1131
  if (state.errored) break;
511
- if (isBuiltInActionJSON(action)) {
1132
+ if (isResolvable(action)) {
1133
+ const result = evaluateResolvable(action, 'action', makeScope(x, {
1134
+ params: x.params,
1135
+ enq
1136
+ }), '$.actions');
1137
+ if (result && typeof result === 'object' && 'context' in result && result.context) {
1138
+ context ??= {};
1139
+ Object.assign(context, result.context);
1140
+ }
1141
+ } else if (isBuiltInActionJSON(action)) {
512
1142
  switch (action.type) {
513
1143
  case '@xstate.raise':
514
1144
  {
515
1145
  // Tag as external if it has a delay (from <send>, not <raise>).
516
1146
  // Attach _scxmlSendId so _event.sendid can be read in expressions.
517
1147
  const isExternal = action.delay !== undefined;
1148
+ const resolvedEvent = resolveValue(action.event, 'actionParams', makeScope(x, {
1149
+ params: x.params
1150
+ }), '$.actions.event');
518
1151
  const event = isExternal || action.id !== undefined ? {
519
- ...action.event,
1152
+ ...resolvedEvent,
520
1153
  ...(isExternal ? {
521
1154
  _scxmlExternal: true
522
1155
  } : {}),
523
1156
  ...(action.id !== undefined ? {
524
1157
  _scxmlSendId: action.id
525
1158
  } : {})
526
- } : action.event;
1159
+ } : resolvedEvent;
527
1160
  enq.raise(event, {
528
1161
  id: action.id,
529
1162
  delay: action.delay
@@ -537,11 +1170,15 @@ function createMachineFromConfig(json, implementations = {}) {
537
1170
  enq.log(...action.args);
538
1171
  break;
539
1172
  case '@xstate.emit':
540
- enq.emit(action.event);
1173
+ enq.emit(resolveValue(action.event, 'actionParams', makeScope(x, {
1174
+ params: x.params
1175
+ }), '$.actions.event'));
541
1176
  break;
542
1177
  case '@xstate.assign':
543
1178
  context ??= {};
544
- Object.assign(context, action.context);
1179
+ Object.assign(context, resolveValue(action.context, 'transitionContext', makeScope(x, {
1180
+ params: x.params
1181
+ }), '$.actions.context'));
545
1182
  break;
546
1183
  default:
547
1184
  throw new Error(`Unknown built-in action: ${action.type}`);
@@ -769,7 +1406,14 @@ function createMachineFromConfig(json, implementations = {}) {
769
1406
  }
770
1407
  }
771
1408
  } else {
772
- enq(x.actions[action.type], action.params);
1409
+ const params = resolveValue(action.params, 'actionParams', makeScope(x, {
1410
+ params: x.params
1411
+ }), '$.actions.params');
1412
+ const result = executeActionDefinition(action, x, enq, params, state, stack);
1413
+ if (result.context) {
1414
+ context ??= {};
1415
+ Object.assign(context, result.context);
1416
+ }
773
1417
  }
774
1418
  }
775
1419
  return {
@@ -790,31 +1434,63 @@ function createMachineFromConfig(json, implementations = {}) {
790
1434
  // a separate XState transition with its own guard and optional .to.
791
1435
  // This ensures guards are evaluated by XState's evaluateCandidate (once,
792
1436
  // with pre-exit context) and NOT re-evaluated in computeEntrySet.
793
- return transitions.map(t => {
1437
+ return transitions.map((t, index) => {
1438
+ if (isResolvable(t)) {
1439
+ return (x, enq) => evaluateResolvable(t, 'transition', makeScope(x, {
1440
+ enq
1441
+ }), `$.transition${transitions.length > 1 ? `[${index}]` : ''}`);
1442
+ }
794
1443
  const target = Array.isArray(t.target) ? t.target[0] : t.target;
795
1444
  const targetConfig = t.target;
796
- const guard = resolveGuardConfig(t.guard);
1445
+ const guard = resolveCondition(t.guard, 'guard', '$.transition.guard');
1446
+ const resolveTransitionContext = x => t.context ? resolveValue(t.context, 'transitionContext', makeScope(x), '$.transition.context') : undefined;
1447
+ const resolveTransitionInput = x => t.input !== undefined ? resolveValue(t.input, 'input', makeScope(x), '$.transition.input') : undefined;
797
1448
 
798
1449
  // No guard and no actions: simple static config
799
1450
  if (!guard && !t.actions?.length) {
1451
+ if (t.context) {
1452
+ return {
1453
+ to: x => ({
1454
+ target: targetConfig,
1455
+ context: resolveTransitionContext(x),
1456
+ reenter: t.reenter
1457
+ }),
1458
+ description: t.description,
1459
+ meta: t.meta,
1460
+ input: t.input !== undefined ? resolveTransitionInput : undefined
1461
+ };
1462
+ }
800
1463
  return {
801
1464
  target: targetConfig,
802
1465
  description: t.description,
803
1466
  reenter: t.reenter,
804
1467
  meta: t.meta,
805
- input: t.input
1468
+ input: t.input !== undefined ? resolveTransitionInput : undefined
806
1469
  };
807
1470
  }
808
1471
 
809
1472
  // Guard but no actions: static config with guard
810
1473
  if (guard && !t.actions?.length) {
1474
+ if (t.context) {
1475
+ return {
1476
+ guard,
1477
+ to: x => ({
1478
+ target: targetConfig,
1479
+ context: resolveTransitionContext(x),
1480
+ reenter: t.reenter
1481
+ }),
1482
+ description: t.description,
1483
+ meta: t.meta,
1484
+ input: t.input !== undefined ? resolveTransitionInput : undefined
1485
+ };
1486
+ }
811
1487
  return {
812
1488
  target: targetConfig,
813
1489
  guard,
814
1490
  description: t.description,
815
1491
  reenter: t.reenter,
816
1492
  meta: t.meta,
817
- input: t.input
1493
+ input: t.input !== undefined ? resolveTransitionInput : undefined
818
1494
  };
819
1495
  }
820
1496
 
@@ -827,8 +1503,9 @@ function createMachineFromConfig(json, implementations = {}) {
827
1503
  description: t.description,
828
1504
  reenter: t.reenter,
829
1505
  meta: t.meta,
830
- input: t.input,
1506
+ input: t.input !== undefined ? resolveTransitionInput : undefined,
831
1507
  to: (x, enq) => {
1508
+ const context = resolveTransitionContext(x);
832
1509
  if (t.actions?.length) {
833
1510
  // Track for parallel re-execution (dedup by reference)
834
1511
  if (!allTransitionActions.includes(t.actions)) {
@@ -840,11 +1517,16 @@ function createMachineFromConfig(json, implementations = {}) {
840
1517
  const result = executeActions(t.actions, x, enq);
841
1518
  if (result.context) {
842
1519
  return {
843
- context: result.context
1520
+ context: {
1521
+ ...context,
1522
+ ...result.context
1523
+ }
844
1524
  };
845
1525
  }
846
1526
  }
847
- return {};
1527
+ return context ? {
1528
+ context
1529
+ } : {};
848
1530
  }
849
1531
  };
850
1532
  }
@@ -858,8 +1540,9 @@ function createMachineFromConfig(json, implementations = {}) {
858
1540
  description: t.description,
859
1541
  reenter: t.reenter,
860
1542
  meta: t.meta,
861
- input: t.input,
1543
+ input: t.input !== undefined ? resolveTransitionInput : undefined,
862
1544
  to: (_x, _enq) => {
1545
+ const context = resolveTransitionContext(_x);
863
1546
  if (t.actions?.length) {
864
1547
  const targetId = target.replace(/^#/, '');
865
1548
  pendingTransitionActionsMap[targetId] = t.actions;
@@ -870,15 +1553,29 @@ function createMachineFromConfig(json, implementations = {}) {
870
1553
  }
871
1554
  return {
872
1555
  target: targetConfig,
1556
+ context,
873
1557
  reenter: t.reenter
874
1558
  };
875
1559
  }
876
1560
  };
877
1561
  });
878
1562
  }
1563
+ if (json.delays) {
1564
+ for (const key of Object.keys(json.delays)) {
1565
+ const delay = json.delays[key];
1566
+ if (typeof delay === 'number') {
1567
+ resolvedImplementations.delays[key] = delay;
1568
+ } else if (typeof delay === 'string') {
1569
+ resolvedImplementations.delays[key] = delayToMs(delay);
1570
+ } else if (delay && typeof delay === 'object') {
1571
+ resolvedImplementations.delays[key] = args => delayToMs(resolveValue(delay.duration, 'delay', makeScope(args), `$.delays.${key}.duration`));
1572
+ }
1573
+ }
1574
+ }
1575
+ assertMachineJSON(json, resolvedImplementations, expressionResolver);
879
1576
  const rootNodeConfig = iterNode(json);
880
1577
  const contextConfig = json.context ? {
881
- context: json.context
1578
+ context: args => resolveValue(json.context, 'context', args, '$.context')
882
1579
  } : {};
883
1580
  _scxmlMachineName = json.id || '';
884
1581
  _scxmlSessionId = 'session_' + Math.random().toString(36).slice(2);
@@ -949,7 +1646,7 @@ function createMachineFromConfig(json, implementations = {}) {
949
1646
  return provided;
950
1647
  }
951
1648
  function isBuiltInActionJSON(action) {
952
- return action.type.startsWith('@xstate.');
1649
+ return !isResolvable(action) && action.type.startsWith('@xstate.');
953
1650
  }
954
1651
 
955
1652
  /**
@@ -958,24 +1655,16 @@ function isBuiltInActionJSON(action) {
958
1655
  * `machineConfigToJSON` converts a machine config into a JSON-safe structure
959
1656
  * (the `MachineJSON` shape accepted by `createMachineFromConfig`). The boundary
960
1657
  * between serializable structure and runtime implementations is explicit:
961
- * functions are represented as code expressions, while actor logic, runtime
962
- * schemas, and other non-code runtime values are replaced with an `{
963
- * "$unserializable": <kind> }` marker rather than silently dropped.
964
- *
965
- * A machine definition is fully portable iff its JSON contains no
966
- * `$unserializable` markers; reviving a definition that has markers requires
967
- * re-providing those implementations (e.g. via `machine.provide(...)` or the
968
- * `actions`/`guards`/`actorSources` maps on `createMachineFromConfig`
969
- * revival).
1658
+ * functions are represented as `@code` source objects, while actor logic,
1659
+ * runtime schemas, and other non-data runtime values are omitted.
970
1660
  */
971
1661
 
972
1662
  /**
973
1663
  * Returns the JSON-serializable definition of a machine.
974
1664
  *
975
- * Inline functions are represented as `{ "@type": "code", lang: "ts", expr }`.
976
- * Actor logic and runtime schemas appear as `{ "$unserializable": ... }`
977
- * markers. A machine created via `createMachineFromConfig` returns its original
978
- * JSON config (lossless round-trip):
1665
+ * Inline functions are represented as `{ "@code": string, "@lang": "ts" }`. A
1666
+ * machine created via `createMachineFromConfig` returns its original JSON
1667
+ * config (lossless round-trip):
979
1668
  *
980
1669
  * ```ts
981
1670
  * import { serializeMachine, createMachineFromConfig } from 'xstate';
@@ -989,22 +1678,12 @@ function isBuiltInActionJSON(action) {
989
1678
  function serializeMachine(machine) {
990
1679
  return machine._json ?? machineConfigToJSON(machine.config);
991
1680
  }
992
- function marker(kind, id) {
993
- const m = {
994
- $unserializable: kind
995
- };
996
- if (id) {
997
- m.id = id;
998
- }
999
- return m;
1000
- }
1001
1681
 
1002
1682
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
1003
1683
  function codeExpression(fn) {
1004
1684
  return {
1005
- '@type': 'code',
1006
- lang: 'ts',
1007
- expr: fn.toString()
1685
+ '@code': fn.toString(),
1686
+ '@lang': 'ts'
1008
1687
  };
1009
1688
  }
1010
1689
  function isActorLogic(value) {
@@ -1020,33 +1699,37 @@ function valueToJSON(value) {
1020
1699
  return codeExpression(value);
1021
1700
  }
1022
1701
  if (value === null || typeof value !== 'object') {
1023
- return typeof value === 'bigint' || typeof value === 'symbol' ? marker('value') : value;
1702
+ return typeof value === 'bigint' || typeof value === 'symbol' ? undefined : value;
1024
1703
  }
1025
1704
  if (isActorLogic(value)) {
1026
- return marker('actor', value.id);
1705
+ return undefined;
1027
1706
  }
1028
1707
  if (isRuntimeSchema(value)) {
1029
- return marker('schema');
1708
+ return undefined;
1030
1709
  }
1031
1710
  if (Array.isArray(value)) {
1032
- return value.map(valueToJSON);
1711
+ return value.map(valueToJSON).filter(item => item !== undefined);
1033
1712
  }
1034
1713
  if (value.constructor !== Object && value.constructor !== undefined) {
1035
1714
  // Class instances (actor logic, schemas, dates, ...) are not portable.
1036
- return marker('value');
1715
+ return undefined;
1037
1716
  }
1038
1717
  const result = {};
1039
1718
  for (const key of Object.keys(value)) {
1040
1719
  const v = value[key];
1041
1720
  if (v !== undefined) {
1042
- result[key] = valueToJSON(v);
1721
+ const jsonValue = valueToJSON(v);
1722
+ if (jsonValue !== undefined) {
1723
+ result[key] = jsonValue;
1724
+ }
1043
1725
  }
1044
1726
  }
1045
1727
  return result;
1046
1728
  }
1047
1729
  function invokeToJSON(invoke) {
1048
1730
  if (Array.isArray(invoke)) {
1049
- return invoke.map(invokeToJSON);
1731
+ const values = invoke.map(invokeToJSON).filter(value => value !== undefined);
1732
+ return values.length ? values : undefined;
1050
1733
  }
1051
1734
  const def = invoke;
1052
1735
  const result = {};
@@ -1055,32 +1738,28 @@ function invokeToJSON(invoke) {
1055
1738
  if (value === undefined) {
1056
1739
  continue;
1057
1740
  }
1058
- result[key] = key === 'src' && typeof value !== 'string' ?
1059
- // Actor logic keeps its dedicated marker kind so revival knows the
1060
- // contract is an actor, not a plain function.
1061
- marker('actor', value.id) : valueToJSON(value);
1741
+ result[key] = key === 'src' && typeof value !== 'string' ? value.id : valueToJSON(value);
1742
+ if (result[key] === undefined) {
1743
+ delete result[key];
1744
+ }
1062
1745
  }
1063
- return result;
1746
+ return result.src === undefined ? undefined : result;
1064
1747
  }
1065
- function implementationsToJSON(map, kind) {
1748
+ function implementationsToJSON(map) {
1066
1749
  if (!map) {
1067
1750
  return undefined;
1068
1751
  }
1069
- // Keys are preserved — they are the contract a revived machine must
1070
- // fulfill via provide() — while values become markers (or stay, for
1071
- // JSON-safe values like numeric delays).
1072
1752
  const result = {};
1073
1753
  for (const key of Object.keys(map)) {
1074
1754
  const value = map[key];
1075
- if (kind === 'schema') {
1076
- result[key] = marker('schema', key);
1077
- } else if (typeof value === 'function') {
1078
- result[key] = codeExpression(value);
1079
- } else if (kind === 'actor' && value && typeof value === 'object') {
1080
- result[key] = marker('actor', key);
1755
+ if (typeof value === 'function') {
1756
+ result[key] = undefined;
1081
1757
  } else {
1082
1758
  result[key] = valueToJSON(value);
1083
1759
  }
1760
+ if (result[key] === undefined) {
1761
+ delete result[key];
1762
+ }
1084
1763
  }
1085
1764
  return result;
1086
1765
  }
@@ -1096,7 +1775,10 @@ function stateNodeConfigToJSON(config) {
1096
1775
  }
1097
1776
  }
1098
1777
  if (config.invoke !== undefined) {
1099
- result.invoke = invokeToJSON(config.invoke);
1778
+ const invoke = invokeToJSON(config.invoke);
1779
+ if (invoke !== undefined) {
1780
+ result.invoke = invoke;
1781
+ }
1100
1782
  }
1101
1783
  if (config.states) {
1102
1784
  const states = {};
@@ -1110,7 +1792,7 @@ function stateNodeConfigToJSON(config) {
1110
1792
 
1111
1793
  /**
1112
1794
  * Converts a machine config (as passed to `createMachine`) to its JSON-safe
1113
- * definition. See module docs for the `$unserializable` marker contract.
1795
+ * definition.
1114
1796
  */
1115
1797
  function machineConfigToJSON(config) {
1116
1798
  const result = stateNodeConfigToJSON(config);
@@ -1123,16 +1805,24 @@ function machineConfigToJSON(config) {
1123
1805
  const value = config.schemas[key];
1124
1806
  if (value && typeof value === 'object' && !('~standard' in value)) {
1125
1807
  // Map-form schemas (events/emitted): preserve event-type keys.
1126
- schemas[key] = implementationsToJSON(value, 'schema');
1808
+ schemas[key] = implementationsToJSON(value);
1127
1809
  } else {
1128
- schemas[key] = marker('schema', key);
1810
+ schemas[key] = valueToJSON(value);
1811
+ }
1812
+ if (schemas[key] === undefined) {
1813
+ delete schemas[key];
1129
1814
  }
1130
1815
  }
1131
1816
  result.schemas = schemas;
1132
1817
  }
1133
1818
  for (const key of ['actions', 'guards', 'actorSources', 'delays']) {
1134
1819
  if (config[key]) {
1135
- result[key] = implementationsToJSON(config[key], key === 'actorSources' ? 'actor' : 'function');
1820
+ const value = implementationsToJSON(config[key]);
1821
+ if (value && Object.keys(value).length) {
1822
+ result[key] = value;
1823
+ } else {
1824
+ delete result[key];
1825
+ }
1136
1826
  }
1137
1827
  }
1138
1828
  return result;
@@ -1962,6 +2652,7 @@ exports.StateNode = StateMachine.StateNode;
1962
2652
  exports.SimulatedClock = SimulatedClock;
1963
2653
  exports.SpecialTargets = SpecialTargets;
1964
2654
  exports.assertEvent = assertEvent;
2655
+ exports.createFSM = createFSM;
1965
2656
  exports.createMachine = createMachine;
1966
2657
  exports.createMachineFromConfig = createMachineFromConfig;
1967
2658
  exports.createStateConfig = createStateConfig;