xstate 6.0.0-alpha.63 → 6.0.0-alpha.65

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 (66) hide show
  1. package/README.md +45 -1
  2. package/dist/{createActor-f71670cf.development.cjs.js → createActor-1df42c9e.development.cjs.js} +238 -36
  3. package/dist/{createActor-523a98c7.esm.js → createActor-4629a290.esm.js} +162 -34
  4. package/dist/{createActor-48f753a9.development.esm.js → createActor-8f57b47b.development.esm.js} +234 -37
  5. package/dist/{createActor-eba8226e.cjs.js → createActor-b987e971.cjs.js} +166 -33
  6. package/dist/declarations/src/SimulatedClock.d.ts +1 -1
  7. package/dist/declarations/src/State.d.ts +25 -3
  8. package/dist/declarations/src/StateMachine.d.ts +20 -7
  9. package/dist/declarations/src/actors/promise.d.ts +5 -5
  10. package/dist/declarations/src/createMachine.d.ts +8 -6
  11. package/dist/declarations/src/createMachineFromConfig.d.ts +13 -2
  12. package/dist/declarations/src/durable/index.d.ts +13 -0
  13. package/dist/declarations/src/eventTypes.d.ts +8 -0
  14. package/dist/declarations/src/graph/index.d.ts +1 -0
  15. package/dist/declarations/src/graph/types.d.ts +20 -2
  16. package/dist/declarations/src/index.d.ts +2 -1
  17. package/dist/declarations/src/machineVersions.d.ts +2 -2
  18. package/dist/declarations/src/serialize.d.ts +7 -2
  19. package/dist/declarations/src/setup.d.ts +208 -30
  20. package/dist/declarations/src/systemTransition.d.ts +103 -0
  21. package/dist/declarations/src/types.d.ts +47 -26
  22. package/dist/declarations/src/types.v6.d.ts +18 -8
  23. package/dist/declarations/src/utils.d.ts +9 -0
  24. package/dist/{effectDescriptor-bc9e3e81.cjs.js → effectDescriptor-4cf24593.cjs.js} +1 -1
  25. package/dist/{effectDescriptor-ad75e430.esm.js → effectDescriptor-973fc1ba.esm.js} +1 -1
  26. package/dist/{effectDescriptor-8c87a3e7.development.cjs.js → effectDescriptor-b2b7deaa.development.cjs.js} +1 -1
  27. package/dist/{effectDescriptor-eadf1b25.development.esm.js → effectDescriptor-c4671afc.development.esm.js} +1 -1
  28. package/dist/{index-83f11f07.cjs.js → index-0cf33789.development.cjs.js} +37 -15
  29. package/dist/{index-2a4be1f2.development.esm.js → index-15382daf.development.esm.js} +37 -15
  30. package/dist/{index-211a5dce.esm.js → index-52ad3d45.esm.js} +37 -15
  31. package/dist/{index-3cb56f46.development.cjs.js → index-f201d440.cjs.js} +37 -15
  32. package/dist/xstate-actors.cjs.js +2 -2
  33. package/dist/xstate-actors.development.cjs.js +2 -2
  34. package/dist/xstate-actors.development.esm.js +2 -2
  35. package/dist/xstate-actors.esm.js +2 -2
  36. package/dist/xstate-actors.umd.min.js +1 -1
  37. package/dist/xstate-actors.umd.min.js.map +1 -1
  38. package/dist/xstate-durable.cjs.js +36 -4
  39. package/dist/xstate-durable.development.cjs.js +36 -4
  40. package/dist/xstate-durable.development.esm.js +36 -4
  41. package/dist/xstate-durable.esm.js +36 -4
  42. package/dist/xstate-durable.umd.min.js +1 -1
  43. package/dist/xstate-durable.umd.min.js.map +1 -1
  44. package/dist/xstate-graph.cjs.js +8 -4
  45. package/dist/xstate-graph.cjs.mjs +1 -0
  46. package/dist/xstate-graph.development.cjs.js +8 -4
  47. package/dist/xstate-graph.development.cjs.mjs +1 -0
  48. package/dist/xstate-graph.development.esm.js +8 -4
  49. package/dist/xstate-graph.esm.js +8 -4
  50. package/dist/xstate-graph.umd.min.js +1 -1
  51. package/dist/xstate-graph.umd.min.js.map +1 -1
  52. package/dist/xstate-validation.cjs.js +24 -7
  53. package/dist/xstate-validation.development.cjs.js +24 -7
  54. package/dist/xstate-validation.development.esm.js +24 -7
  55. package/dist/xstate-validation.esm.js +24 -7
  56. package/dist/xstate-validation.umd.min.js +1 -1
  57. package/dist/xstate-validation.umd.min.js.map +1 -1
  58. package/dist/xstate.cjs.js +1316 -291
  59. package/dist/xstate.cjs.mjs +3 -0
  60. package/dist/xstate.development.cjs.js +1335 -291
  61. package/dist/xstate.development.cjs.mjs +3 -0
  62. package/dist/xstate.development.esm.js +1335 -294
  63. package/dist/xstate.esm.js +1316 -294
  64. package/dist/xstate.umd.min.js +1 -1
  65. package/dist/xstate.umd.min.js.map +1 -1
  66. package/package.json +1 -1
package/README.md CHANGED
@@ -17,12 +17,44 @@ XState is a state management and orchestration solution for JavaScript and TypeS
17
17
 
18
18
  It uses event-driven programming, state machines, statecharts, and the actor model to handle complex logic in predictable, robust, and visual ways. XState provides a powerful and flexible way to manage application and workflow state by allowing developers to model logic as actors and state machines.
19
19
 
20
+ <!-- experimental whole-system transitions from packages/core/src/systemTransition.ts -->
21
+
22
+ For pure actor-system simulation, `initialSystemTransition`, `systemTransition`
23
+ and `advanceSystemTime` return an immutable system snapshot and external effects
24
+ as data. Branch or replay actors, messages and virtual timers without starting live
25
+ actors. See [pure system transitions](../../docs/system-transitions.md).
26
+
20
27
  <!-- durable execution and checkpoint restoration from packages/core/src/durable/index.ts -->
21
28
 
22
29
  For durable hosts, the experimental `xstate/durable` entry point provides
23
30
  `createDurable`. Resume a checkpoint with `execution.restore(persistedSnapshot)`
24
31
  and execute the returned effects to restart active embedded children and pending
25
- timers without replaying entry actions. See [durable execution](../../docs/durable-execution.md).
32
+ timers without replaying entry actions. Await `executeEffects()` before persisting
33
+ to retain accepted timer deadlines. The host handles root error snapshots;
34
+ `run()` rejects with the root error. See [durable execution](../../docs/durable-execution.md).
35
+
36
+ <!-- state context refinements from packages/core/src/setup.ts and packages/core/src/State.ts -->
37
+
38
+ For [typestates](../../docs/typestates.md), declare state-level `schemas.context`
39
+ in `setup({ states })`. State functions receive narrowed context, transitions
40
+ check the target context requirements, and `snapshot.matches(...)` narrows
41
+ context when reading snapshots.
42
+
43
+ <!-- typed inline invocation from packages/core/src/setup.ts -->
44
+
45
+ Use `s.createInvoke({ src, input, onDone })` inline in `s.createMachine(...)` for an actor used in one state. It infers the actor's input and output and the enclosing state's narrowed context without registering that actor in `setup.actors`. See [typed inline invokes](../../docs/invoke.md#typed-inline-invokes).
46
+
47
+ Pass an async function directly as `src`, with `schemas.input`, `schemas.output`, and `schemas.error` on the same invoke. Output also infers from the async return when its schema is omitted. Existing logic values use their own schemas.
48
+
49
+ The helper requires input for actors whose input excludes `undefined`, and checks child IDs and source compatibility against `schemas.children` declared in the setup or machine.
50
+
51
+ <!-- graph traversal events from packages/core/src/graph/index.ts -->
52
+
53
+ `getAllOwnEvents(snapshot)` from `xstate/graph` includes invoke completions, errors, and delayed events. Compose it with custom traversal payloads: supplied `events` replace the defaults. See [model-based testing](../../docs/model-based-testing.md).
54
+
55
+ <!-- JSON loading and runtime validation from packages/core/src/createMachineFromConfig.ts -->
56
+
57
+ Revive JSON definitions with `createMachineFromConfig(json, { schemas, validator, ...sources })` to restore runtime validation. The loader validates structural keys and references and copies its input; serialized definitions are independent copies. See [serialization](../../docs/serialization.md).
26
58
 
27
59
  For flat finite state machines, `xstate/fsm` provides a tiny pure transition
28
60
  table with context and TypeScript support:
@@ -618,3 +650,15 @@ For these reasons, it is impractical for our team to be bound by decisions taken
618
650
  ### Packages
619
651
 
620
652
  Most of the packages in the XState family declare a peer dependency on XState itself. We’ll be cautious about maintaining compatibility with already-released packages when releasing a new version of XState, **but** each release of packages depending on XState will always adjust the declared peer dependency range to include the latest version of XState. For example, you should always be able to update `xstate` without `@xstate/react`. But when you update `@xstate/react`, we highly recommend updating `xstate` too.
653
+
654
+ <!-- public input validation contract from src/StateMachine.ts -->
655
+ Validate external input with `machine.eventSchema` before `actor.send()`.
656
+ It validates complete public events, excludes internal/runtime events, and allows
657
+ explicitly configured routes. Payload checks require runtime schemas;
658
+ `types<T>()` supplies types only. See [public input validation](../../docs/internal-events.md#validating-public-input).
659
+
660
+ <!-- entry effects and terminal transition behavior from packages/core/src/stateUtils.ts and transitionActions.ts -->
661
+
662
+ Functions passed to `enq(...)` run as effects; their returned context or child patches are ignored, with a development warning. Return a computed patch from the transition, entry, or exit function instead. Entry/exit functions using enqueue must declare exactly `(args, enq)`; default/rest parameter wrappers receive a development diagnostic. See [actions](../../docs/actions.md).
663
+
664
+ Pure transitions preserve terminal snapshots and return no effects. `snapshot.can(...)` returns `false` for terminal snapshots; active dry runs propagate evaluation errors. See [snapshots](../../docs/snapshots.md) and [states](../../docs/states.md).
@@ -183,6 +183,20 @@ function reportUnhandledError(err) {
183
183
 
184
184
  const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
185
185
 
186
+ // Runtime metadata stays outside pure snapshots. Timer objects survive unrelated
187
+ // transitions; a cancelled/replaced timer gets a new object and a new deadline.
188
+ const starts = new WeakMap();
189
+
190
+ /** @internal */
191
+ function getTimerStart(timer) {
192
+ return starts.get(timer) ?? timer.startedAt;
193
+ }
194
+
195
+ /** @internal */
196
+ function recordTimerStart(timer, startedAt) {
197
+ starts.set(timer, startedAt);
198
+ }
199
+
186
200
  function assertSendToEvent(event) {
187
201
  if (typeof event === 'string') {
188
202
  throw new Error(// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
@@ -241,6 +255,9 @@ function resolveRegisteredActorSource(actors, logic) {
241
255
  return cache.get(logic);
242
256
  }
243
257
 
258
+ /** Static scheduling metadata on actor definitions, never on live actors. @internal */
259
+ const systemLogicMetadata = Symbol('xstate.systemLogicMetadata');
260
+
244
261
  const subscriptions = /* #__PURE__ */new WeakMap();
245
262
 
246
263
  /**
@@ -259,7 +276,7 @@ id) {
259
276
  error: undefined,
260
277
  input
261
278
  }, []];
262
- return {
279
+ const logic = {
263
280
  id,
264
281
  start: (state, {
265
282
  self,
@@ -323,6 +340,12 @@ id) {
323
340
  getPersistedSnapshot: snapshot => snapshot,
324
341
  restoreSnapshot: snapshot => snapshot
325
342
  };
343
+ Object.defineProperty(logic, systemLogicMetadata, {
344
+ value: {
345
+ kind: id
346
+ }
347
+ });
348
+ return logic;
326
349
  }
327
350
 
328
351
  /**
@@ -507,7 +530,33 @@ function withActorScope(args, actorScope) {
507
530
  }
508
531
 
509
532
  function execCustomEffect() {
510
- return this.action?.(...this.args);
533
+ const result = this.action?.(...this.args);
534
+ {
535
+ warnOnIgnoredEffectResult(this, result);
536
+ }
537
+ return result;
538
+ }
539
+ const warnedEffectResults = new WeakSet();
540
+
541
+ /**
542
+ * A function passed to `enq(...)` runs as an effect after the transition is
543
+ * computed, so a `{ context }` or `{ children }` it returns is never applied.
544
+ * Development builds say so, once per function.
545
+ */
546
+ function warnOnIgnoredEffectResult(effect, result) {
547
+ if (!effect.action || result === null || typeof result !== 'object' || warnedEffectResults.has(effect.action)) {
548
+ return;
549
+ }
550
+ const prototype = Object.getPrototypeOf(result);
551
+ if (prototype !== Object.prototype && prototype !== null) {
552
+ return;
553
+ }
554
+ const keys = ['context', 'children'].filter(key => key in result);
555
+ if (!keys.length) {
556
+ return;
557
+ }
558
+ warnedEffectResults.add(effect.action);
559
+ console.warn(`Action "${effect.type || '(anonymous)'}" returned { ${keys.join(', ')} }, which is ignored: functions passed to enq(...) run as effects after the transition. Return the patch from the transition, entry or exit function instead; a named action can be called there directly and its result returned.`);
511
560
  }
512
561
  function execEmitEffect(runtime = this.source.system) {
513
562
  return runtime.emitEvent(this.source, this.event);
@@ -1036,6 +1085,16 @@ function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubsc
1036
1085
  props[key] = guard(props[key]);
1037
1086
  }
1038
1087
  const enqueue = createEnqueueObject(props, guard((action, ...args) => {
1088
+ // A named action is `undefined` when it has no implementation. Skip it:
1089
+ // an `{ action, args }` record without a function would be emitted as
1090
+ // an event. Speculative passes (`can()`, conflict resolution) run with
1091
+ // `createActors: false`; warn only when the transition really runs.
1092
+ if (typeof action !== 'function') {
1093
+ if (createActors) {
1094
+ warnNotAFunction(action);
1095
+ }
1096
+ return;
1097
+ }
1039
1098
  pushBuiltInAction(actions, action, ...args);
1040
1099
  }));
1041
1100
  enqueueClosers.set(enqueue, () => {
@@ -1060,6 +1119,10 @@ function lateEnqueueCall() {
1060
1119
  throw new Error('enq.* called after the transition function returned');
1061
1120
  }
1062
1121
  }
1122
+ function warnNotAFunction(value) {
1123
+ const received = typeof value === 'string' ? `the string "${value}"` : value !== null && typeof value === 'object' ? 'an object' : String(value);
1124
+ console.warn(`enq(...) received ${received} instead of a function, so nothing was enqueued. A named action is undefined when it has no implementation: implement it in setup({ actions }) or the machine config, or pass it to machine.provide({ actions }).`);
1125
+ }
1063
1126
  function getBuiltInActionFields(action, args) {
1064
1127
  switch (action) {
1065
1128
  case builtInActions['@xstate.spawn']:
@@ -2265,6 +2328,10 @@ function getDelayedTransitions(stateNode) {
2265
2328
  }));
2266
2329
  }
2267
2330
  function formatTransition(stateNode, descriptor, transitionConfig) {
2331
+ const removedKey = getRemovedTransitionKey(transitionConfig);
2332
+ if (removedKey) {
2333
+ throw new Error(`Transition "${descriptor || 'always'}" in state "${stateNode.id}" uses ${removedKey}, which was removed in v6. Use a transition function.`);
2334
+ }
2268
2335
  const normalizedTarget = normalizeTarget(transitionConfig.target);
2269
2336
  const reenter = transitionConfig.reenter ?? false;
2270
2337
  const target = resolveTarget(stateNode, normalizedTarget);
@@ -2275,14 +2342,46 @@ function formatTransition(stateNode, descriptor, transitionConfig) {
2275
2342
  source: stateNode,
2276
2343
  reenter,
2277
2344
  eventType: descriptor,
2278
- toJSON: () => ({
2279
- ...transition,
2280
- source: `#${stateNode.id}`,
2281
- target: target ? target.map(t => `#${t.id}`) : undefined
2282
- })
2345
+ toJSON: () => transitionDefinitionToJSON(transition)
2283
2346
  };
2284
2347
  return transition;
2285
2348
  }
2349
+ function transitionDefinitionToJSON(transition) {
2350
+ const {
2351
+ definition: _,
2352
+ ...serializableTransition
2353
+ } = transition;
2354
+ return {
2355
+ ...serializableTransition,
2356
+ source: `#${transition.source.id}`,
2357
+ target: transition.target?.map(target => `#${target.id}`)
2358
+ };
2359
+ }
2360
+
2361
+ /**
2362
+ * Returns the v5 key of a transition object that v6 would misread, in every
2363
+ * build: `cond` is never read, so the transition would always be taken,
2364
+ * transition `actions` would not run as written, and only a function `guard`
2365
+ * can be called. Development builds explain each key earlier, in
2366
+ * `diagnoseAuthorConfig`. Compiled configs (JSON, SCXML) never set them.
2367
+ */
2368
+ function getRemovedTransitionKey(transitionConfig) {
2369
+ const {
2370
+ cond,
2371
+ actions,
2372
+ guard
2373
+ } = transitionConfig;
2374
+ if (cond !== undefined) {
2375
+ return '"cond"';
2376
+ }
2377
+ if (actions !== undefined) {
2378
+ return '"actions"';
2379
+ }
2380
+ if (guard !== undefined && typeof guard !== 'function') {
2381
+ return 'an object-form "guard"';
2382
+ }
2383
+ return undefined;
2384
+ }
2286
2385
  function isStateNodeDescendantOf(stateNode, ancestor) {
2287
2386
  for (let current = stateNode.parent; current; current = current.parent) {
2288
2387
  if (current === ancestor) {
@@ -2625,12 +2724,18 @@ function removeConflictingTransitions(enabledTransitions, stateNodeSet, snapshot
2625
2724
  }
2626
2725
  function createTransitionResultResolver(snapshot, event, actorScope, resolveActions, selectionResults) {
2627
2726
  let cache;
2628
- return transition => {
2629
- if (!transition.to) {
2630
- return getTransitionResult(transition, snapshot, event, actorScope, {
2631
- resolveActions,
2632
- selectionResult: selectionResults?.get(transition)
2633
- });
2727
+ return (transition, applied) => {
2728
+ if (!transition.to && !applied) {
2729
+ // An object transition's targets are static, so reading them must not
2730
+ // run its `context` and `input` mappers
2731
+ return {
2732
+ targets: transition.target,
2733
+ context: undefined,
2734
+ reenter: transition.reenter,
2735
+ actions: undefined,
2736
+ internalEvents: undefined,
2737
+ input: undefined
2738
+ };
2634
2739
  }
2635
2740
  let result = cache?.get(transition);
2636
2741
  if (!result) {
@@ -2858,13 +2963,20 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2858
2963
  children: args.children,
2859
2964
  actions: args.actions,
2860
2965
  actors: args.actors,
2966
+ guards: currentSnapshot.machine.sources.guards,
2967
+ delays: currentSnapshot.machine.sources.delays,
2861
2968
  input,
2862
2969
  stateNode
2863
2970
  }, actorScope) : {
2864
2971
  ...args,
2972
+ guards: currentSnapshot.machine.sources.guards,
2973
+ delays: currentSnapshot.machine.sources.delays,
2865
2974
  input,
2866
2975
  stateNode
2867
- }, enqueue), '_special' in transitionFn ? {
2976
+ },
2977
+ // This function runs during action resolution, where enqueued
2978
+ // effects are no longer collected.
2979
+ createIgnoredEnqueue(transitionFn, kind, stateNode.id) ), '_special' in transitionFn ? {
2868
2980
  _special: true
2869
2981
  } : {});
2870
2982
  return [[wrappedAction], undefined, undefined];
@@ -2902,7 +3014,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2902
3014
  }
2903
3015
  for (const exitStateNode of statesToExit) {
2904
3016
  const stateInput = getStateInput(currentSnapshot, exitStateNode.id);
2905
- const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, currentSnapshot.children, stateInput, 'exit', exitStateNode) : [[], undefined, undefined];
3017
+ const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, nextState.children, stateInput, 'exit', exitStateNode) : [[], undefined, undefined];
2906
3018
  if (internalEvents?.length) {
2907
3019
  internalQueue.push(...internalEvents);
2908
3020
  }
@@ -2932,11 +3044,20 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
2932
3044
  let context = nextState.context;
2933
3045
  const transitionActions = [];
2934
3046
  const internalEvents = [];
3047
+ const resolvedTransitions = [];
2935
3048
  for (const t of filteredTransitions) {
2936
- if (t.actions) {
2937
- transitionActions.push(...toArray(t.actions));
3049
+ const res = getCurrentTransitionResult(t, true);
3050
+ if (t.to) {
3051
+ const resolvedTransition = {
3052
+ ...t,
3053
+ target: res.targets,
3054
+ definition: t
3055
+ };
3056
+ resolvedTransition.toJSON = () => transitionDefinitionToJSON(resolvedTransition);
3057
+ resolvedTransitions.push(resolvedTransition);
3058
+ } else {
3059
+ resolvedTransitions.push(t);
2938
3060
  }
2939
- const res = getCurrentTransitionResult(t);
2940
3061
  if (res.context !== undefined) {
2941
3062
  context = mergeContextPatch(context, res.context);
2942
3063
  }
@@ -3092,7 +3213,7 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
3092
3213
  const {
3093
3214
  targets,
3094
3215
  input
3095
- } = getCurrentTransitionResult(transition);
3216
+ } = getCurrentTransitionResult(transition, true);
3096
3217
  if (input && targets) {
3097
3218
  for (const targetNode of targets.filter(targetNode => enteredTargetsByTransition.get(transition)?.has(targetNode))) {
3098
3219
  stateInputMap[targetNode.id] = input;
@@ -3101,12 +3222,12 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
3101
3222
  }
3102
3223
  }
3103
3224
  const completedNodes = new Set();
3104
- const children = {
3105
- ...nextState.children
3106
- };
3107
3225
  for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
3108
3226
  mutStateNodeSet.add(stateNodeToEnter);
3109
3227
  const actions = [];
3228
+ // Read the children for each state: the entry actions of the states
3229
+ // entered before this one may have spawned or stopped children.
3230
+ let children = nextState.children;
3110
3231
 
3111
3232
  // Final states are inert, so (as in SCXML) their invocations never
3112
3233
  // start.
@@ -3145,7 +3266,10 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
3145
3266
  args: [actor]
3146
3267
  });
3147
3268
  if (invokeDef.id) {
3148
- children[invokeDef.id] = actor;
3269
+ children = {
3270
+ ...children,
3271
+ [invokeDef.id]: actor
3272
+ };
3149
3273
  }
3150
3274
  }
3151
3275
  if (invoked) {
@@ -3265,7 +3389,12 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
3265
3389
  nextStateNodesToExit.forEach(stateNode => {
3266
3390
  if (stateNode.exit) {
3267
3391
  const stateInput = getStateInput(nextState, stateNode.id);
3268
- const [exitActions,, nextInternalEvents] = getStateActionsAndContext(stateNode.exit, nextState.context, nextState.children, stateInput, 'exit', stateNode);
3392
+ const [exitActions, nextContext, nextInternalEvents] = getStateActionsAndContext(stateNode.exit, nextState.context, nextState.children, stateInput, 'exit', stateNode);
3393
+ if (nextContext) {
3394
+ nextState = cloneMachineSnapshot(nextState, {
3395
+ context: nextContext
3396
+ });
3397
+ }
3269
3398
  allExitActions.push(...exitActions);
3270
3399
  if (nextInternalEvents?.length) {
3271
3400
  internalQueue.push(...nextInternalEvents);
@@ -3286,14 +3415,14 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
3286
3415
  // If context was changed (e.g. by entry actions during self-transition),
3287
3416
  // clone to ensure reference inequality for eventless transition re-evaluation
3288
3417
  if (nextState.context !== originalContext) {
3289
- return [nextState === currentSnapshot ? cloneMachineSnapshot(nextState) : nextState, executableActions];
3418
+ return [nextState === currentSnapshot ? cloneMachineSnapshot(nextState) : nextState, executableActions, resolvedTransitions];
3290
3419
  }
3291
- return [nextState, executableActions];
3420
+ return [nextState, executableActions, resolvedTransitions];
3292
3421
  }
3293
3422
  return [cloneMachineSnapshot(nextState, {
3294
3423
  _nodes: nextStateNodes,
3295
3424
  historyValue
3296
- }), executableActions];
3425
+ }), executableActions, resolvedTransitions];
3297
3426
  }
3298
3427
  }
3299
3428
 
@@ -3403,6 +3532,15 @@ class InfiniteTransitionError extends Error {
3403
3532
  }
3404
3533
  }
3405
3534
  function macrostep(snapshot, event, actorScope, internalQueue, initialMicrosteps = []) {
3535
+ // A done, error or stopped snapshot processes no events, as in an actor:
3536
+ // every event is unhandled. The initial macrostep passes `initialMicrosteps`
3537
+ // and may start from a snapshot that is already done.
3538
+ if (snapshot.status !== 'active' && !initialMicrosteps.length) {
3539
+ return {
3540
+ snapshot,
3541
+ microsteps: [[snapshot, [], []]]
3542
+ };
3543
+ }
3406
3544
  let nextSnapshot = snapshot;
3407
3545
  const microsteps = initialMicrosteps.slice();
3408
3546
  // Whether a transition handled the external event. A handled event always
@@ -3471,6 +3609,7 @@ function macrostep(snapshot, event, actorScope, internalQueue, initialMicrosteps
3471
3609
  };
3472
3610
  }
3473
3611
  function addMicrostep(step, transitions) {
3612
+ transitions = step[2] ?? transitions;
3474
3613
  // collect microsteps; their transitions are surfaced on the enclosing
3475
3614
  // '@xstate.transition' event via its `microsteps[]` facet (there is no
3476
3615
  // standalone microstep event) and returned by `getMicrosteps()`
@@ -3626,6 +3765,42 @@ function assertSyncTransitionResult(res, event, sourceId, kind) {
3626
3765
  throw new Error(kind ? `${kind === 'entry' ? 'Entry' : 'Exit'} functions must be synchronous. The ${kind} function of state "${sourceId}" returned a promise (event "${event.type}"). Move async work into an invoked or spawned actor, or enq.effect.` : `Transition functions must be synchronous. Transition for event "${event.type}" in state "${sourceId}" returned a promise. Move async work into an invoked or spawned actor, or enq.effect.`);
3627
3766
  }
3628
3767
  }
3768
+ const ignoredEnqueues = new WeakMap();
3769
+
3770
+ /**
3771
+ * The enqueue object that an entry or exit function gets in development when it
3772
+ * is not declared with exactly two parameters. Such a function runs during
3773
+ * action resolution, where enqueued effects are no longer collected, so every
3774
+ * call is ignored. This one warns, once per function.
3775
+ */
3776
+ function createIgnoredEnqueue(fn, kind, stateNodeId) {
3777
+ let enqueue = ignoredEnqueues.get(fn);
3778
+ if (enqueue) {
3779
+ return enqueue;
3780
+ }
3781
+ let warned = false;
3782
+ const ignore = (method, result) => (..._args) => {
3783
+ if (!warned) {
3784
+ warned = true;
3785
+ console.warn(`The ${kind} function of state "${stateNodeId}" called ${method}(...), which was ignored. Only ${kind} functions declared with exactly two parameters, (args, enq) => ..., get a working enq. This one has fn.length ${fn.length}; fn.length stops counting at a default or rest parameter.`);
3786
+ }
3787
+ return result?.();
3788
+ };
3789
+ const emptyActor = () => ({});
3790
+ enqueue = createEnqueueObject({
3791
+ cancel: ignore('enq.cancel'),
3792
+ emit: ignore('enq.emit'),
3793
+ log: ignore('enq.log'),
3794
+ raise: ignore('enq.raise'),
3795
+ sendTo: ignore('enq.sendTo'),
3796
+ stop: ignore('enq.stop'),
3797
+ spawn: ignore('enq.spawn', emptyActor),
3798
+ listen: ignore('enq.listen', emptyActor),
3799
+ subscribeTo: ignore('enq.subscribeTo', emptyActor)
3800
+ }, ignore('enq'));
3801
+ ignoredEnqueues.set(fn, enqueue);
3802
+ return enqueue;
3803
+ }
3629
3804
  let transitionEffectEnqueue;
3630
3805
  function getTransitionEffectEnqueue() {
3631
3806
  return transitionEffectEnqueue ??= createEnqueueObject({
@@ -3955,6 +4130,17 @@ function isMachineSnapshot(value) {
3955
4130
  return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
3956
4131
  }
3957
4132
 
4133
+ /**
4134
+ * The values that `snapshot.matches(...)` accepts for a state value: a state
4135
+ * key, a dot-delimited path to a descendant state, or a partial state value.
4136
+ * Non-literal state values accept any value.
4137
+ */
4138
+
4139
+ /**
4140
+ * `unknown` if `matches(...)` accepts `TTestStateValue` for `TStateValue`,
4141
+ * otherwise `never`. Non-literal test values are always accepted.
4142
+ */
4143
+
3958
4144
  /** @public */
3959
4145
 
3960
4146
  const machineSnapshotMatches = function matches(testValue) {
@@ -4158,7 +4344,7 @@ function getPersistedSnapshot(snapshot, options) {
4158
4344
  // readings (a simulated clock, a monotonic counter) are meaningless in any
4159
4345
  // other process, and restoring them under the wall clock would fire every
4160
4346
  // pending delay instantly. Pure-transition snapshots have no local
4161
- // schedule and persist no timestamp.
4347
+ // schedule; durable executions record an accepted start separately.
4162
4348
  const scheduledTimers = snapshotActor?.system?._clock?.now ? undefined : snapshotActor?.system?._snapshot?._scheduledTimers;
4163
4349
  for (const id in timers) {
4164
4350
  const timer = timers[id];
@@ -4189,10 +4375,10 @@ function getPersistedSnapshot(snapshot, options) {
4189
4375
  // deadline. Derived from dueAt, not the scheduling moment, so repeated
4190
4376
  // persist/restore cycles keep the same deadline. Without a live schedule
4191
4377
  // (a restored-but-never-started actor, a pure-transition snapshot) the
4192
- // timer's carried-in start — which only a wall-clock actor ever stamped —
4193
- // passes through, so re-persisting cannot push the deadline back.
4378
+ // timer's accepted durable start or carried-in start passes through,
4379
+ // so re-persisting cannot push the deadline back.
4194
4380
  const scheduled = scheduledTimers?.[`${snapshotActor.sessionId}.${id}`];
4195
- const startedAt = scheduled ? scheduled.dueAt - timer.delay : timer.startedAt;
4381
+ const startedAt = scheduled ? scheduled.dueAt - timer.delay : getTimerStart(timer);
4196
4382
  timersJson[id] = {
4197
4383
  id: timer.id,
4198
4384
  delay: timer.delay,
@@ -4479,7 +4665,14 @@ function getAllOwnEventDescriptors(snapshot) {
4479
4665
  return [...new Set([...snapshot.nodes.flatMap(sn => sn.ownEvents)])];
4480
4666
  }
4481
4667
 
4482
- /** @internal Events synthesized from active transition descriptors. */
4668
+ /**
4669
+ * Returns one event per transition of the active state nodes of `snapshot`,
4670
+ * including the internal events for invoked actors (`xstate.done.actor`,
4671
+ * `xstate.error.actor`) and delayed transitions (`xstate.after`). Graph
4672
+ * traversal sends these when the `events` option is not given.
4673
+ *
4674
+ * @public
4675
+ */
4483
4676
  function getAllOwnEvents(snapshot) {
4484
4677
  const events = snapshot.nodes.flatMap(stateNode => [...stateNode.transitions.values()].flatMap(transitions => transitions.map(transition => {
4485
4678
  const event = {
@@ -6040,7 +6233,10 @@ class Actor {
6040
6233
  const [nextSnapshot, effects] = nextState;
6041
6234
  if (nextSnapshot === previousSnapshot && !effects.length &&
6042
6235
  // State machine snapshots only; other logic may ignore events freely.
6043
- 'machine' in previousSnapshot && !event.type.startsWith('xstate.')) {
6236
+ 'machine' in previousSnapshot && (
6237
+ // `xstate.route` is sent by users, so a route that matches nothing is
6238
+ // reported like any other unhandled event.
6239
+ !event.type.startsWith('xstate.') || event.type === 'xstate.route')) {
6044
6240
  this._reportUnhandledEvent(event);
6045
6241
  }
6046
6242
  snapshot = nextSnapshot;
@@ -6061,10 +6257,11 @@ class Actor {
6061
6257
  _reportUnhandledEvent(event) {
6062
6258
  safeCall(() => this.options.onUnhandledEvent?.(event, this._snapshot));
6063
6259
  {
6260
+ const to = event.type === 'xstate.route' ? ` to ${JSON.stringify(event.to)}` : '';
6064
6261
  const warned = this._warnedUnhandledTypes ??= new Set();
6065
- if (!warned.has(event.type)) {
6066
- warned.add(event.type);
6067
- console.warn(`Actor ${this.id} received event "${event.type}" in state ${JSON.stringify(this._snapshot.value)} with no matching transition`);
6262
+ if (!warned.has(event.type + to)) {
6263
+ warned.add(event.type + to);
6264
+ console.warn(`Actor ${this.id} received event "${event.type}"${to} in state ${JSON.stringify(this._snapshot.value)} with no matching transition`);
6068
6265
  }
6069
6266
  }
6070
6267
  }
@@ -6395,7 +6592,9 @@ exports.cloneMachineSnapshot = cloneMachineSnapshot;
6395
6592
  exports.createActor = createActor;
6396
6593
  exports.createCustomEffect = createCustomEffect;
6397
6594
  exports.createDeadLetterEffect = createDeadLetterEffect;
6595
+ exports.createDoneActorEvent = createDoneActorEvent;
6398
6596
  exports.createEmitEffect = createEmitEffect;
6597
+ exports.createErrorActorEvent = createErrorActorEvent;
6399
6598
  exports.createErrorPlatformEvent = createErrorPlatformEvent;
6400
6599
  exports.createInertActorScope = createInertActorScope;
6401
6600
  exports.createInitEvent = createInitEvent;
@@ -6430,6 +6629,7 @@ exports.getRootActorId = getRootActorId;
6430
6629
  exports.getSnapshotActorRef = getSnapshotActorRef;
6431
6630
  exports.getStateNodeByPath = getStateNodeByPath;
6432
6631
  exports.getStateNodes = getStateNodes;
6632
+ exports.getTimerStart = getTimerStart;
6433
6633
  exports.getTransitionResult = getTransitionResult;
6434
6634
  exports.hasAmbientInspector = hasAmbientInspector;
6435
6635
  exports.hasEffect = hasEffect;
@@ -6453,6 +6653,7 @@ exports.mergeActorIdCounters = mergeActorIdCounters;
6453
6653
  exports.parseDelayToMilliseconds = parseDelayToMilliseconds;
6454
6654
  exports.parseGeneratedActorId = parseGeneratedActorId;
6455
6655
  exports.pathToStateValue = pathToStateValue;
6656
+ exports.recordTimerStart = recordTimerStart;
6456
6657
  exports.reserveChildId = reserveChildId;
6457
6658
  exports.resolveActionsWithContext = resolveActionsWithContext;
6458
6659
  exports.resolveReferencedActor = resolveReferencedActor;
@@ -6463,6 +6664,7 @@ exports.setInertActorScopeSnapshot = setInertActorScopeSnapshot;
6463
6664
  exports.setSnapshotActorRef = setSnapshotActorRef;
6464
6665
  exports.stopActor = stopActor;
6465
6666
  exports.subscriptionLogic = subscriptionLogic;
6667
+ exports.systemLogicMetadata = systemLogicMetadata;
6466
6668
  exports.takeSpawnAllocationCounters = takeSpawnAllocationCounters;
6467
6669
  exports.terminateActor = terminateActor;
6468
6670
  exports.toArray = toArray;