xstate 6.0.0-alpha.58 → 6.0.0-alpha.59

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 (54) hide show
  1. package/dist/{createActor-3dc6cdb7.development.esm.js → createActor-224ece29.development.esm.js} +124 -46
  2. package/dist/{createActor-c7c704c2.esm.js → createActor-5236aa33.esm.js} +124 -46
  3. package/dist/{createActor-2213c48b.development.cjs.js → createActor-72b67075.development.cjs.js} +124 -46
  4. package/dist/{createActor-c4723a2b.cjs.js → createActor-7d8aebcc.cjs.js} +124 -46
  5. package/dist/declarations/src/StateMachine.d.ts +10 -0
  6. package/dist/declarations/src/graph/graph.d.ts +0 -3
  7. package/dist/declarations/src/system.d.ts +1 -8
  8. package/dist/declarations/src/types.d.ts +26 -0
  9. package/dist/declarations/src/types.v6.d.ts +10 -0
  10. package/dist/{effectDescriptor-3121155c.cjs.js → effectDescriptor-3f1dfc32.cjs.js} +1 -1
  11. package/dist/{effectDescriptor-c0976206.development.cjs.js → effectDescriptor-48919b13.development.cjs.js} +1 -1
  12. package/dist/{effectDescriptor-95202c94.development.esm.js → effectDescriptor-7740e170.development.esm.js} +1 -1
  13. package/dist/{effectDescriptor-9cb5c006.esm.js → effectDescriptor-e04e6848.esm.js} +1 -1
  14. package/dist/{types-91238aab.cjs.js → types-34ac08ad.cjs.js} +19 -3
  15. package/dist/{types-94ac7819.development.esm.js → types-7df72520.development.esm.js} +19 -3
  16. package/dist/{types-939a97c6.development.cjs.js → types-80c6a857.development.cjs.js} +19 -3
  17. package/dist/{types-1378f0fa.esm.js → types-b0313706.esm.js} +19 -3
  18. package/dist/xstate-actors.cjs.js +16 -5
  19. package/dist/xstate-actors.development.cjs.js +16 -5
  20. package/dist/xstate-actors.development.esm.js +17 -6
  21. package/dist/xstate-actors.esm.js +17 -6
  22. package/dist/xstate-actors.umd.min.js +1 -1
  23. package/dist/xstate-actors.umd.min.js.map +1 -1
  24. package/dist/xstate-durable.cjs.js +2 -2
  25. package/dist/xstate-durable.development.cjs.js +2 -2
  26. package/dist/xstate-durable.development.esm.js +2 -2
  27. package/dist/xstate-durable.esm.js +2 -2
  28. package/dist/xstate-durable.umd.min.js +1 -1
  29. package/dist/xstate-durable.umd.min.js.map +1 -1
  30. package/dist/xstate-graph.cjs.js +56 -42
  31. package/dist/xstate-graph.development.cjs.js +56 -42
  32. package/dist/xstate-graph.development.esm.js +56 -42
  33. package/dist/xstate-graph.esm.js +56 -42
  34. package/dist/xstate-graph.umd.min.js +1 -1
  35. package/dist/xstate-graph.umd.min.js.map +1 -1
  36. package/dist/xstate-scxml.cjs.js +72 -55
  37. package/dist/xstate-scxml.development.cjs.js +72 -55
  38. package/dist/xstate-scxml.development.esm.js +72 -55
  39. package/dist/xstate-scxml.esm.js +72 -55
  40. package/dist/xstate-scxml.umd.min.js +1 -1
  41. package/dist/xstate-scxml.umd.min.js.map +1 -1
  42. package/dist/xstate-validation.cjs.js +1 -1
  43. package/dist/xstate-validation.development.cjs.js +1 -1
  44. package/dist/xstate-validation.development.esm.js +1 -1
  45. package/dist/xstate-validation.esm.js +1 -1
  46. package/dist/xstate-validation.umd.min.js.map +1 -1
  47. package/dist/xstate.cjs.js +18 -23
  48. package/dist/xstate.development.cjs.js +18 -23
  49. package/dist/xstate.development.esm.js +20 -25
  50. package/dist/xstate.esm.js +20 -25
  51. package/dist/xstate.umd.min.js +1 -1
  52. package/dist/xstate.umd.min.js.map +1 -1
  53. package/package.json +2 -2
  54. package/dist/declarations/src/inspectionAmbient.d.ts +0 -1
@@ -2669,6 +2669,7 @@ function computeExitSet(transitions, stateNodeSet, snapshot, resolveTransition)
2669
2669
  return [...statesToExit];
2670
2670
  }
2671
2671
  function initialMicrostep(root, preInitialState, actorScope, initEvent, internalQueue) {
2672
+ root.machine._microstepHooks?.begin(actorScope.self);
2672
2673
  const initialStateNodes = [...getInitialStateNodes(root)];
2673
2674
  const statesForDefaultEntry = new Set();
2674
2675
  const collectDefaultEntryStates = stateNode => {
@@ -2697,6 +2698,10 @@ function initialMicrostep(root, preInitialState, actorScope, initEvent, internal
2697
2698
  /** https://www.w3.org/TR/scxml/#microstepProcedure */
2698
2699
  function microstep(transitions, currentSnapshot, actorScope, event, isInitial, internalQueue, selectionResults) {
2699
2700
  const executableActions = [];
2701
+ const internalEvents = currentSnapshot.machine._microstepHooks?.drain(actorScope.self);
2702
+ if (internalEvents?.length) {
2703
+ internalQueue.push(...internalEvents);
2704
+ }
2700
2705
  if (!transitions.length) {
2701
2706
  return [currentSnapshot, executableActions];
2702
2707
  }
@@ -3411,6 +3416,11 @@ function macrostep(snapshot, event, actorScope, internalQueue, initialMicrosteps
3411
3416
  // eventless transitions should always be selected after selecting *regular* transitions
3412
3417
  // by assigning `undefined` to `previousState` we ensure that `shouldSelectEventlessTransitions` gets always computed to true in such a case
3413
3418
  const previousState = enabledTransitions.length ? nextSnapshot : undefined;
3419
+ // Failed eventless guards can raise events even when no transition wins.
3420
+ const raisedEvents = nextSnapshot.machine._microstepHooks?.drain(actorScope.self);
3421
+ if (raisedEvents?.length) {
3422
+ internalQueue.push(...raisedEvents);
3423
+ }
3414
3424
  if (!enabledTransitions.length) {
3415
3425
  if (!internalQueue.length) {
3416
3426
  break;
@@ -3525,6 +3535,7 @@ function cancelTimers(snapshot, event, actorScope) {
3525
3535
  return resolveActionsWithContext(snapshot, event, actorScope, actions);
3526
3536
  }
3527
3537
  function selectEventlessTransitions(snapshot, event, actorScope) {
3538
+ snapshot.machine._microstepHooks?.begin(actorScope.self);
3528
3539
  const enabledTransitionSet = new Set();
3529
3540
  const atomicStates = snapshot.nodes.filter(isAtomicStateNode);
3530
3541
  for (const atomicStateNode of atomicStates) {
@@ -4066,7 +4077,17 @@ function mapValues(collection, iteratee) {
4066
4077
  const collectionKeys = Object.keys(collection);
4067
4078
  for (let i = 0; i < collectionKeys.length; i++) {
4068
4079
  const key = collectionKeys[i];
4069
- result[key] = iteratee(collection[key], key, collection, i);
4080
+ const value = iteratee(collection[key], key, collection, i);
4081
+ if (key === '__proto__') {
4082
+ Object.defineProperty(result, key, {
4083
+ value,
4084
+ enumerable: true,
4085
+ configurable: true,
4086
+ writable: true
4087
+ });
4088
+ } else {
4089
+ result[key] = value;
4090
+ }
4070
4091
  }
4071
4092
  return result;
4072
4093
  }
@@ -4233,23 +4254,14 @@ function matchesEventDescriptor(eventType, descriptor) {
4233
4254
  }
4234
4255
 
4235
4256
  function getStepEffect(actor, key) {
4236
- return actor.getSnapshot().effects?.[key];
4237
- }
4238
- function waitForStep(actor, key) {
4239
- return new Promise((resolve, reject) => {
4240
- const subscription = actor.subscribe(snapshot => {
4241
- const effect = snapshot.effects?.[key];
4242
- if (effect?.status === 'done') {
4243
- subscription.unsubscribe();
4244
- resolve(effect.output);
4245
- } else if (effect?.status === 'error') {
4246
- subscription.unsubscribe();
4247
- reject(effect.error);
4248
- }
4249
- });
4250
- });
4257
+ const effects = actor.getSnapshot().effects;
4258
+ return effects && Object.prototype.hasOwnProperty.call(effects, key) ? effects[key] : undefined;
4251
4259
  }
4252
4260
 
4261
+ // Live promises belong to an actor incarnation, unlike its persisted journal.
4262
+ // A restored active record has no promise here and must be retried locally.
4263
+ const pendingSteps = new WeakMap();
4264
+
4253
4265
  /**
4254
4266
  * Runs one keyed step of an async actor with the built-in journal: the
4255
4267
  * result is memoized in the actor's own snapshot (`effects[key]`), so a
@@ -4267,29 +4279,66 @@ async function runStep(actor, key, exec, sendSelf = event => void actor.system.s
4267
4279
  if (effect?.status === 'error') {
4268
4280
  throw effect.error;
4269
4281
  }
4270
- if (effect?.status === 'active') {
4271
- return waitForStep(actor, key);
4282
+ const running = pendingSteps.get(actor)?.get(key);
4283
+ if (running) {
4284
+ return running;
4272
4285
  }
4273
- sendSelf({
4274
- type: XSTATE_LOGIC_EFFECT_START,
4275
- key
4276
- });
4277
- try {
4278
- const output = await exec();
4279
- sendSelf({
4280
- type: XSTATE_LOGIC_EFFECT_RESOLVE,
4281
- key,
4282
- output
4283
- });
4284
- return output;
4285
- } catch (error) {
4286
- sendSelf({
4287
- type: XSTATE_LOGIC_EFFECT_REJECT,
4288
- key,
4289
- error
4290
- });
4291
- throw error;
4286
+ if (actor.getSnapshot().status !== 'active') {
4287
+ throw new Error(`Actor terminated before step "${key}" completed`);
4292
4288
  }
4289
+ let resolve;
4290
+ let reject;
4291
+ const promise = new Promise((resolvePromise, rejectPromise) => {
4292
+ resolve = resolvePromise;
4293
+ reject = rejectPromise;
4294
+ });
4295
+ let steps = pendingSteps.get(actor);
4296
+ if (!steps) {
4297
+ steps = new Map();
4298
+ pendingSteps.set(actor, steps);
4299
+ }
4300
+ steps.set(key, promise);
4301
+ // oxlint-disable-next-line prefer-const
4302
+ let subscription;
4303
+ const cleanup = () => {
4304
+ subscription?.unsubscribe();
4305
+ steps.delete(key);
4306
+ if (!steps.size) {
4307
+ pendingSteps.delete(actor);
4308
+ }
4309
+ };
4310
+ void promise.then(cleanup, cleanup);
4311
+ subscription = actor.subscribe({
4312
+ error: reject,
4313
+ complete: () => reject(new Error(`Actor terminated before step "${key}" completed`))
4314
+ });
4315
+ void (async () => {
4316
+ try {
4317
+ sendSelf({
4318
+ type: XSTATE_LOGIC_EFFECT_START,
4319
+ key
4320
+ });
4321
+ const output = await exec();
4322
+ if (actor.getSnapshot().status === 'active') {
4323
+ sendSelf({
4324
+ type: XSTATE_LOGIC_EFFECT_RESOLVE,
4325
+ key,
4326
+ output
4327
+ });
4328
+ resolve(output);
4329
+ }
4330
+ } catch (error) {
4331
+ if (actor.getSnapshot().status === 'active') {
4332
+ sendSelf({
4333
+ type: XSTATE_LOGIC_EFFECT_REJECT,
4334
+ key,
4335
+ error
4336
+ });
4337
+ }
4338
+ reject(error);
4339
+ }
4340
+ })().catch(reject);
4341
+ return promise;
4293
4342
  }
4294
4343
 
4295
4344
  /**
@@ -4931,6 +4980,8 @@ class RuntimeSystem {
4931
4980
  }
4932
4981
  }
4933
4982
  }
4983
+
4984
+ /** @internal */
4934
4985
  function createRuntimeSystem(rootActor, options) {
4935
4986
  return new RuntimeSystem(rootActor, options);
4936
4987
  }
@@ -5283,12 +5334,12 @@ class Actor {
5283
5334
  }
5284
5335
 
5285
5336
  /** Recover via the logic's error event if possible; otherwise error out. */
5286
- _recoverOrError(err, snapshot) {
5337
+ _recoverOrError(err, snapshot, previousSnapshot) {
5287
5338
  if (this._tryHandleExecutionError(err, snapshot)) {
5288
5339
  return true;
5289
5340
  }
5290
5341
  this._setErrorSnapshot(err);
5291
- this._error(err);
5342
+ this._error(err, previousSnapshot);
5292
5343
  return false;
5293
5344
  }
5294
5345
  _tryHandleExecutionError(err, snapshot = this._snapshot) {
@@ -5621,6 +5672,7 @@ class Actor {
5621
5672
  return this;
5622
5673
  }
5623
5674
  _process(event) {
5675
+ const previousSnapshot = this._snapshot;
5624
5676
  let nextState;
5625
5677
  let caughtError;
5626
5678
  try {
@@ -5651,7 +5703,7 @@ class Actor {
5651
5703
  executeExecutableEffects(effects, this._actorScope);
5652
5704
  this.update(snapshot, event);
5653
5705
  } catch (err) {
5654
- if (!this._recoverOrError(err, snapshot)) {
5706
+ if (!this._recoverOrError(err, snapshot, previousSnapshot)) {
5655
5707
  this._inspectTransition(this._snapshot, event);
5656
5708
  }
5657
5709
  return;
@@ -5737,8 +5789,39 @@ class Actor {
5737
5789
  }
5738
5790
  });
5739
5791
  }
5740
- _error(err) {
5792
+ _error(err, previousSnapshot) {
5741
5793
  this._stopProcedure();
5794
+ // Transition calculation or effect execution may fail before child stop
5795
+ // effects run. Keep the previous snapshot's children when calculation has
5796
+ // already removed them, and include children owned by non-machine logic
5797
+ // from the runtime registry.
5798
+ const children = new Set();
5799
+ for (const snapshot of [this._snapshot, previousSnapshot]) {
5800
+ const snapshotChildren = snapshot?.children;
5801
+ for (const child of Object.values(snapshotChildren ?? {})) {
5802
+ if (child?._parent === this && typeof child.getSnapshot === 'function') {
5803
+ children.add(child);
5804
+ }
5805
+ }
5806
+ }
5807
+ for (const child of this.system._peekChildren?.()?.values() ?? []) {
5808
+ if (child._parent === this) {
5809
+ children.add(child);
5810
+ }
5811
+ }
5812
+ for (const child of children) {
5813
+ try {
5814
+ if (child.getSnapshot().status !== 'active') {
5815
+ continue;
5816
+ }
5817
+ const result = this.system.stopActor(child);
5818
+ if (result) {
5819
+ void Promise.resolve(result).catch(reportUnhandledError);
5820
+ }
5821
+ } catch (error) {
5822
+ reportUnhandledError(error);
5823
+ }
5824
+ }
5742
5825
  if (!this.observers?.size) {
5743
5826
  if (!this._parent) {
5744
5827
  this._reportUnlessObserved(err);
@@ -5765,11 +5848,6 @@ class Actor {
5765
5848
  this.system._relay(this, this._parent, createErrorActorEvent(this.id, err, this.sessionId));
5766
5849
  }
5767
5850
  }
5768
- // TODO: atm children don't belong entirely to the actor so
5769
- // in a way - it's not even super aware of them
5770
- // so we can't stop them from here but we really should!
5771
- // right now, they are being stopped within the machine's transition
5772
- // but that could throw and leave us with "orphaned" active actors
5773
5851
  _stopProcedure() {
5774
5852
  if (this._processingStatus !== ProcessingStatus.Running) {
5775
5853
  // Actor already stopped; do nothing
@@ -2651,6 +2651,7 @@ function computeExitSet(transitions, stateNodeSet, snapshot, resolveTransition)
2651
2651
  return [...statesToExit];
2652
2652
  }
2653
2653
  function initialMicrostep(root, preInitialState, actorScope, initEvent, internalQueue) {
2654
+ root.machine._microstepHooks?.begin(actorScope.self);
2654
2655
  const initialStateNodes = [...getInitialStateNodes(root)];
2655
2656
  const statesForDefaultEntry = new Set();
2656
2657
  const collectDefaultEntryStates = stateNode => {
@@ -2679,6 +2680,10 @@ function initialMicrostep(root, preInitialState, actorScope, initEvent, internal
2679
2680
  /** https://www.w3.org/TR/scxml/#microstepProcedure */
2680
2681
  function microstep(transitions, currentSnapshot, actorScope, event, isInitial, internalQueue, selectionResults) {
2681
2682
  const executableActions = [];
2683
+ const internalEvents = currentSnapshot.machine._microstepHooks?.drain(actorScope.self);
2684
+ if (internalEvents?.length) {
2685
+ internalQueue.push(...internalEvents);
2686
+ }
2682
2687
  if (!transitions.length) {
2683
2688
  return [currentSnapshot, executableActions];
2684
2689
  }
@@ -3393,6 +3398,11 @@ function macrostep(snapshot, event, actorScope, internalQueue, initialMicrosteps
3393
3398
  // eventless transitions should always be selected after selecting *regular* transitions
3394
3399
  // by assigning `undefined` to `previousState` we ensure that `shouldSelectEventlessTransitions` gets always computed to true in such a case
3395
3400
  const previousState = enabledTransitions.length ? nextSnapshot : undefined;
3401
+ // Failed eventless guards can raise events even when no transition wins.
3402
+ const raisedEvents = nextSnapshot.machine._microstepHooks?.drain(actorScope.self);
3403
+ if (raisedEvents?.length) {
3404
+ internalQueue.push(...raisedEvents);
3405
+ }
3396
3406
  if (!enabledTransitions.length) {
3397
3407
  if (!internalQueue.length) {
3398
3408
  break;
@@ -3507,6 +3517,7 @@ function cancelTimers(snapshot, event, actorScope) {
3507
3517
  return resolveActionsWithContext(snapshot, event, actorScope, actions);
3508
3518
  }
3509
3519
  function selectEventlessTransitions(snapshot, event, actorScope) {
3520
+ snapshot.machine._microstepHooks?.begin(actorScope.self);
3510
3521
  const enabledTransitionSet = new Set();
3511
3522
  const atomicStates = snapshot.nodes.filter(isAtomicStateNode);
3512
3523
  for (const atomicStateNode of atomicStates) {
@@ -4045,7 +4056,17 @@ function mapValues(collection, iteratee) {
4045
4056
  const collectionKeys = Object.keys(collection);
4046
4057
  for (let i = 0; i < collectionKeys.length; i++) {
4047
4058
  const key = collectionKeys[i];
4048
- result[key] = iteratee(collection[key], key, collection, i);
4059
+ const value = iteratee(collection[key], key, collection, i);
4060
+ if (key === '__proto__') {
4061
+ Object.defineProperty(result, key, {
4062
+ value,
4063
+ enumerable: true,
4064
+ configurable: true,
4065
+ writable: true
4066
+ });
4067
+ } else {
4068
+ result[key] = value;
4069
+ }
4049
4070
  }
4050
4071
  return result;
4051
4072
  }
@@ -4203,23 +4224,14 @@ function matchesEventDescriptor(eventType, descriptor) {
4203
4224
  }
4204
4225
 
4205
4226
  function getStepEffect(actor, key) {
4206
- return actor.getSnapshot().effects?.[key];
4207
- }
4208
- function waitForStep(actor, key) {
4209
- return new Promise((resolve, reject) => {
4210
- const subscription = actor.subscribe(snapshot => {
4211
- const effect = snapshot.effects?.[key];
4212
- if (effect?.status === 'done') {
4213
- subscription.unsubscribe();
4214
- resolve(effect.output);
4215
- } else if (effect?.status === 'error') {
4216
- subscription.unsubscribe();
4217
- reject(effect.error);
4218
- }
4219
- });
4220
- });
4227
+ const effects = actor.getSnapshot().effects;
4228
+ return effects && Object.prototype.hasOwnProperty.call(effects, key) ? effects[key] : undefined;
4221
4229
  }
4222
4230
 
4231
+ // Live promises belong to an actor incarnation, unlike its persisted journal.
4232
+ // A restored active record has no promise here and must be retried locally.
4233
+ const pendingSteps = new WeakMap();
4234
+
4223
4235
  /**
4224
4236
  * Runs one keyed step of an async actor with the built-in journal: the
4225
4237
  * result is memoized in the actor's own snapshot (`effects[key]`), so a
@@ -4237,29 +4249,66 @@ async function runStep(actor, key, exec, sendSelf = event => void actor.system.s
4237
4249
  if (effect?.status === 'error') {
4238
4250
  throw effect.error;
4239
4251
  }
4240
- if (effect?.status === 'active') {
4241
- return waitForStep(actor, key);
4252
+ const running = pendingSteps.get(actor)?.get(key);
4253
+ if (running) {
4254
+ return running;
4242
4255
  }
4243
- sendSelf({
4244
- type: XSTATE_LOGIC_EFFECT_START,
4245
- key
4246
- });
4247
- try {
4248
- const output = await exec();
4249
- sendSelf({
4250
- type: XSTATE_LOGIC_EFFECT_RESOLVE,
4251
- key,
4252
- output
4253
- });
4254
- return output;
4255
- } catch (error) {
4256
- sendSelf({
4257
- type: XSTATE_LOGIC_EFFECT_REJECT,
4258
- key,
4259
- error
4260
- });
4261
- throw error;
4256
+ if (actor.getSnapshot().status !== 'active') {
4257
+ throw new Error(`Actor terminated before step "${key}" completed`);
4262
4258
  }
4259
+ let resolve;
4260
+ let reject;
4261
+ const promise = new Promise((resolvePromise, rejectPromise) => {
4262
+ resolve = resolvePromise;
4263
+ reject = rejectPromise;
4264
+ });
4265
+ let steps = pendingSteps.get(actor);
4266
+ if (!steps) {
4267
+ steps = new Map();
4268
+ pendingSteps.set(actor, steps);
4269
+ }
4270
+ steps.set(key, promise);
4271
+ // oxlint-disable-next-line prefer-const
4272
+ let subscription;
4273
+ const cleanup = () => {
4274
+ subscription?.unsubscribe();
4275
+ steps.delete(key);
4276
+ if (!steps.size) {
4277
+ pendingSteps.delete(actor);
4278
+ }
4279
+ };
4280
+ void promise.then(cleanup, cleanup);
4281
+ subscription = actor.subscribe({
4282
+ error: reject,
4283
+ complete: () => reject(new Error(`Actor terminated before step "${key}" completed`))
4284
+ });
4285
+ void (async () => {
4286
+ try {
4287
+ sendSelf({
4288
+ type: XSTATE_LOGIC_EFFECT_START,
4289
+ key
4290
+ });
4291
+ const output = await exec();
4292
+ if (actor.getSnapshot().status === 'active') {
4293
+ sendSelf({
4294
+ type: XSTATE_LOGIC_EFFECT_RESOLVE,
4295
+ key,
4296
+ output
4297
+ });
4298
+ resolve(output);
4299
+ }
4300
+ } catch (error) {
4301
+ if (actor.getSnapshot().status === 'active') {
4302
+ sendSelf({
4303
+ type: XSTATE_LOGIC_EFFECT_REJECT,
4304
+ key,
4305
+ error
4306
+ });
4307
+ }
4308
+ reject(error);
4309
+ }
4310
+ })().catch(reject);
4311
+ return promise;
4263
4312
  }
4264
4313
 
4265
4314
  /**
@@ -4898,6 +4947,8 @@ class RuntimeSystem {
4898
4947
  }
4899
4948
  }
4900
4949
  }
4950
+
4951
+ /** @internal */
4901
4952
  function createRuntimeSystem(rootActor, options) {
4902
4953
  return new RuntimeSystem(rootActor, options);
4903
4954
  }
@@ -5250,12 +5301,12 @@ class Actor {
5250
5301
  }
5251
5302
 
5252
5303
  /** Recover via the logic's error event if possible; otherwise error out. */
5253
- _recoverOrError(err, snapshot) {
5304
+ _recoverOrError(err, snapshot, previousSnapshot) {
5254
5305
  if (this._tryHandleExecutionError(err, snapshot)) {
5255
5306
  return true;
5256
5307
  }
5257
5308
  this._setErrorSnapshot(err);
5258
- this._error(err);
5309
+ this._error(err, previousSnapshot);
5259
5310
  return false;
5260
5311
  }
5261
5312
  _tryHandleExecutionError(err, snapshot = this._snapshot) {
@@ -5588,6 +5639,7 @@ class Actor {
5588
5639
  return this;
5589
5640
  }
5590
5641
  _process(event) {
5642
+ const previousSnapshot = this._snapshot;
5591
5643
  let nextState;
5592
5644
  let caughtError;
5593
5645
  try {
@@ -5618,7 +5670,7 @@ class Actor {
5618
5670
  executeExecutableEffects(effects, this._actorScope);
5619
5671
  this.update(snapshot, event);
5620
5672
  } catch (err) {
5621
- if (!this._recoverOrError(err, snapshot)) {
5673
+ if (!this._recoverOrError(err, snapshot, previousSnapshot)) {
5622
5674
  this._inspectTransition(this._snapshot, event);
5623
5675
  }
5624
5676
  return;
@@ -5704,8 +5756,39 @@ class Actor {
5704
5756
  }
5705
5757
  });
5706
5758
  }
5707
- _error(err) {
5759
+ _error(err, previousSnapshot) {
5708
5760
  this._stopProcedure();
5761
+ // Transition calculation or effect execution may fail before child stop
5762
+ // effects run. Keep the previous snapshot's children when calculation has
5763
+ // already removed them, and include children owned by non-machine logic
5764
+ // from the runtime registry.
5765
+ const children = new Set();
5766
+ for (const snapshot of [this._snapshot, previousSnapshot]) {
5767
+ const snapshotChildren = snapshot?.children;
5768
+ for (const child of Object.values(snapshotChildren ?? {})) {
5769
+ if (child?._parent === this && typeof child.getSnapshot === 'function') {
5770
+ children.add(child);
5771
+ }
5772
+ }
5773
+ }
5774
+ for (const child of this.system._peekChildren?.()?.values() ?? []) {
5775
+ if (child._parent === this) {
5776
+ children.add(child);
5777
+ }
5778
+ }
5779
+ for (const child of children) {
5780
+ try {
5781
+ if (child.getSnapshot().status !== 'active') {
5782
+ continue;
5783
+ }
5784
+ const result = this.system.stopActor(child);
5785
+ if (result) {
5786
+ void Promise.resolve(result).catch(reportUnhandledError);
5787
+ }
5788
+ } catch (error) {
5789
+ reportUnhandledError(error);
5790
+ }
5791
+ }
5709
5792
  if (!this.observers?.size) {
5710
5793
  if (!this._parent) {
5711
5794
  this._reportUnlessObserved(err);
@@ -5732,11 +5815,6 @@ class Actor {
5732
5815
  this.system._relay(this, this._parent, createErrorActorEvent(this.id, err, this.sessionId));
5733
5816
  }
5734
5817
  }
5735
- // TODO: atm children don't belong entirely to the actor so
5736
- // in a way - it's not even super aware of them
5737
- // so we can't stop them from here but we really should!
5738
- // right now, they are being stopped within the machine's transition
5739
- // but that could throw and leave us with "orphaned" active actors
5740
5818
  _stopProcedure() {
5741
5819
  if (this._processingStatus !== ProcessingStatus.Running) {
5742
5820
  // Actor already stopped; do nothing