xstate 5.0.0-beta.14 → 5.0.0-beta.16
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.
- package/actions/dist/xstate-actions.cjs.js +1 -3
- package/actions/dist/xstate-actions.cjs.mjs +0 -2
- package/actions/dist/xstate-actions.development.cjs.js +1 -3
- package/actions/dist/xstate-actions.development.esm.js +1 -1
- package/actions/dist/xstate-actions.esm.js +1 -1
- package/actions/dist/xstate-actions.umd.min.js +1 -1
- package/actions/dist/xstate-actions.umd.min.js.map +1 -1
- package/actors/dist/xstate-actors.cjs.js +1 -1
- package/actors/dist/xstate-actors.development.cjs.js +1 -1
- package/actors/dist/xstate-actors.development.esm.js +1 -1
- package/actors/dist/xstate-actors.esm.js +1 -1
- package/actors/dist/xstate-actors.umd.min.js +1 -1
- package/actors/dist/xstate-actors.umd.min.js.map +1 -1
- package/dist/{actions-444a17c3.esm.js → actions-0386b622.esm.js} +934 -997
- package/dist/{actions-73b8d456.development.cjs.js → actions-0f903c0d.development.cjs.js} +863 -928
- package/dist/{actions-17c3bcfa.cjs.js → actions-6b9073db.cjs.js} +934 -999
- package/dist/{actions-60622c0c.development.esm.js → actions-6f7fbc84.development.esm.js} +863 -926
- package/dist/declarations/src/State.d.ts +1 -11
- package/dist/declarations/src/StateMachine.d.ts +4 -11
- package/dist/declarations/src/actionTypes.d.ts +1 -1
- package/dist/declarations/src/actions/assign.d.ts +2 -2
- package/dist/declarations/src/actions/cancel.d.ts +2 -2
- package/dist/declarations/src/actions/send.d.ts +12 -22
- package/dist/declarations/src/actions/stop.d.ts +2 -2
- package/dist/declarations/src/actions.d.ts +1 -4
- package/dist/declarations/src/actors/callback.d.ts +3 -3
- package/dist/declarations/src/actors/index.d.ts +1 -1
- package/dist/declarations/src/actors/promise.d.ts +13 -3
- package/dist/declarations/src/guards.d.ts +1 -1
- package/dist/declarations/src/interpreter.d.ts +2 -2
- package/dist/declarations/src/stateUtils.d.ts +3 -2
- package/dist/declarations/src/types.d.ts +44 -71
- package/dist/declarations/src/utils.d.ts +3 -3
- package/dist/xstate.cjs.js +44 -59
- package/dist/xstate.development.cjs.js +44 -59
- package/dist/xstate.development.esm.js +45 -60
- package/dist/xstate.esm.js +45 -60
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/guards/dist/xstate-guards.cjs.js +1 -1
- package/guards/dist/xstate-guards.development.cjs.js +1 -1
- package/guards/dist/xstate-guards.development.esm.js +1 -1
- package/guards/dist/xstate-guards.esm.js +1 -1
- package/guards/dist/xstate-guards.umd.min.js +1 -1
- package/guards/dist/xstate-guards.umd.min.js.map +1 -1
- package/package.json +2 -1
|
@@ -37,7 +37,7 @@ var dev_dist_xstateDev = require('../dev/dist/xstate-dev.development.cjs.js');
|
|
|
37
37
|
let ActionTypes = /*#__PURE__*/function (ActionTypes) {
|
|
38
38
|
ActionTypes["Stop"] = "xstate.stop";
|
|
39
39
|
ActionTypes["Raise"] = "xstate.raise";
|
|
40
|
-
ActionTypes["
|
|
40
|
+
ActionTypes["SendTo"] = "xstate.sendTo";
|
|
41
41
|
ActionTypes["Cancel"] = "xstate.cancel";
|
|
42
42
|
ActionTypes["Assign"] = "xstate.assign";
|
|
43
43
|
ActionTypes["After"] = "xstate.after";
|
|
@@ -63,7 +63,7 @@ let SpecialTargets = /*#__PURE__*/function (SpecialTargets) {
|
|
|
63
63
|
// xstate-specific action types
|
|
64
64
|
const stop$1 = ActionTypes.Stop;
|
|
65
65
|
const raise$1 = ActionTypes.Raise;
|
|
66
|
-
const
|
|
66
|
+
const sendTo$1 = ActionTypes.SendTo;
|
|
67
67
|
const cancel$1 = ActionTypes.Cancel;
|
|
68
68
|
const assign$1 = ActionTypes.Assign;
|
|
69
69
|
const after$1 = ActionTypes.After;
|
|
@@ -81,7 +81,7 @@ var actionTypes = /*#__PURE__*/Object.freeze({
|
|
|
81
81
|
__proto__: null,
|
|
82
82
|
stop: stop$1,
|
|
83
83
|
raise: raise$1,
|
|
84
|
-
|
|
84
|
+
sendTo: sendTo$1,
|
|
85
85
|
cancel: cancel$1,
|
|
86
86
|
assign: assign$1,
|
|
87
87
|
after: after$1,
|
|
@@ -102,9 +102,9 @@ const NULL_EVENT = '';
|
|
|
102
102
|
const STATE_IDENTIFIER = '#';
|
|
103
103
|
const WILDCARD = '*';
|
|
104
104
|
|
|
105
|
-
function matchesState(parentStateId, childStateId
|
|
106
|
-
const parentStateValue = toStateValue(parentStateId
|
|
107
|
-
const childStateValue = toStateValue(childStateId
|
|
105
|
+
function matchesState(parentStateId, childStateId) {
|
|
106
|
+
const parentStateValue = toStateValue(parentStateId);
|
|
107
|
+
const childStateValue = toStateValue(childStateId);
|
|
108
108
|
if (isString(childStateValue)) {
|
|
109
109
|
if (isString(parentStateValue)) {
|
|
110
110
|
return childStateValue === parentStateValue;
|
|
@@ -123,12 +123,12 @@ function matchesState(parentStateId, childStateId, delimiter = STATE_DELIMITER)
|
|
|
123
123
|
return matchesState(parentStateValue[key], childStateValue[key]);
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
|
-
function toStatePath(stateId
|
|
126
|
+
function toStatePath(stateId) {
|
|
127
127
|
try {
|
|
128
128
|
if (isArray(stateId)) {
|
|
129
129
|
return stateId;
|
|
130
130
|
}
|
|
131
|
-
return stateId.toString().split(
|
|
131
|
+
return stateId.toString().split(STATE_DELIMITER);
|
|
132
132
|
} catch (e) {
|
|
133
133
|
throw new Error(`'${stateId}' is not a valid state path.`);
|
|
134
134
|
}
|
|
@@ -136,7 +136,7 @@ function toStatePath(stateId, delimiter) {
|
|
|
136
136
|
function isStateLike(state) {
|
|
137
137
|
return typeof state === 'object' && 'value' in state && 'context' in state && 'event' in state;
|
|
138
138
|
}
|
|
139
|
-
function toStateValue(stateValue
|
|
139
|
+
function toStateValue(stateValue) {
|
|
140
140
|
if (isStateLike(stateValue)) {
|
|
141
141
|
return stateValue.value;
|
|
142
142
|
}
|
|
@@ -146,7 +146,7 @@ function toStateValue(stateValue, delimiter) {
|
|
|
146
146
|
if (typeof stateValue !== 'string') {
|
|
147
147
|
return stateValue;
|
|
148
148
|
}
|
|
149
|
-
const statePath = toStatePath(stateValue
|
|
149
|
+
const statePath = toStatePath(stateValue);
|
|
150
150
|
return pathToStateValue(statePath);
|
|
151
151
|
}
|
|
152
152
|
function pathToStateValue(statePath) {
|
|
@@ -159,8 +159,9 @@ function pathToStateValue(statePath) {
|
|
|
159
159
|
if (i === statePath.length - 2) {
|
|
160
160
|
marker[statePath[i]] = statePath[i + 1];
|
|
161
161
|
} else {
|
|
162
|
-
|
|
163
|
-
marker =
|
|
162
|
+
const previous = marker;
|
|
163
|
+
marker = {};
|
|
164
|
+
previous[statePath[i]] = marker;
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
return value;
|
|
@@ -305,22 +306,19 @@ function isDynamicAction(action) {
|
|
|
305
306
|
}
|
|
306
307
|
|
|
307
308
|
/**
|
|
308
|
-
* Sends an event
|
|
309
|
-
* send the event in the next step, after the current step is finished executing.
|
|
310
|
-
*
|
|
311
|
-
* @deprecated Use the `sendTo(...)` action creator instead.
|
|
309
|
+
* Sends an event to an actor.
|
|
312
310
|
*
|
|
313
|
-
* @param
|
|
314
|
-
* @param
|
|
311
|
+
* @param actor The `ActorRef` to send the event to.
|
|
312
|
+
* @param event The event to send, or an expression that evaluates to the event to send
|
|
313
|
+
* @param options Send action options
|
|
315
314
|
* - `id` - The unique send event identifier (used with `cancel()`).
|
|
316
315
|
* - `delay` - The number of milliseconds to delay the sending of the event.
|
|
317
|
-
* - `to` - The target of this event (by default, the machine the event was sent from).
|
|
318
316
|
*/
|
|
319
|
-
function
|
|
317
|
+
function sendTo(actor, eventOrExpr, options) {
|
|
320
318
|
return createDynamicAction({
|
|
321
|
-
type:
|
|
319
|
+
type: sendTo$1,
|
|
322
320
|
params: {
|
|
323
|
-
to:
|
|
321
|
+
to: actor,
|
|
324
322
|
delay: options ? options.delay : undefined,
|
|
325
323
|
event: eventOrExpr,
|
|
326
324
|
id: options && options.id !== undefined ? options.id : isFunction(eventOrExpr) ? eventOrExpr.name : eventOrExpr.type
|
|
@@ -330,7 +328,7 @@ function send(eventOrExpr, options) {
|
|
|
330
328
|
state
|
|
331
329
|
}) => {
|
|
332
330
|
const params = {
|
|
333
|
-
to:
|
|
331
|
+
to: actor,
|
|
334
332
|
delay: options ? options.delay : undefined,
|
|
335
333
|
event: eventOrExpr,
|
|
336
334
|
// TODO: don't auto-generate IDs here like that
|
|
@@ -343,7 +341,7 @@ function send(eventOrExpr, options) {
|
|
|
343
341
|
self: actorContext?.self ?? null,
|
|
344
342
|
system: actorContext?.system
|
|
345
343
|
};
|
|
346
|
-
const delaysMap = state.machine.
|
|
344
|
+
const delaysMap = state.machine.implementations.delays;
|
|
347
345
|
|
|
348
346
|
// TODO: helper function for resolving Expr
|
|
349
347
|
if (typeof eventOrExpr === 'string') {
|
|
@@ -378,13 +376,12 @@ function send(eventOrExpr, options) {
|
|
|
378
376
|
targetActorRef = resolvedTarget || actorContext?.self;
|
|
379
377
|
}
|
|
380
378
|
const resolvedAction = {
|
|
381
|
-
type:
|
|
379
|
+
type: sendTo$1,
|
|
382
380
|
params: {
|
|
383
381
|
...params,
|
|
384
382
|
to: targetActorRef,
|
|
385
383
|
event: resolvedEvent,
|
|
386
|
-
delay: resolvedDelay
|
|
387
|
-
internal: resolvedTarget === SpecialTargets.Internal
|
|
384
|
+
delay: resolvedDelay
|
|
388
385
|
},
|
|
389
386
|
execute: actorCtx => {
|
|
390
387
|
const sendAction = resolvedAction;
|
|
@@ -414,12 +411,8 @@ function send(eventOrExpr, options) {
|
|
|
414
411
|
* @param options Options to pass into the send event.
|
|
415
412
|
*/
|
|
416
413
|
function sendParent(event, options) {
|
|
417
|
-
return
|
|
418
|
-
...options,
|
|
419
|
-
to: SpecialTargets.Parent
|
|
420
|
-
});
|
|
414
|
+
return sendTo(SpecialTargets.Parent, event, options);
|
|
421
415
|
}
|
|
422
|
-
|
|
423
416
|
/**
|
|
424
417
|
* Forwards (sends) an event to a specified service.
|
|
425
418
|
*
|
|
@@ -437,12 +430,9 @@ function forwardTo(target, options) {
|
|
|
437
430
|
return resolvedTarget;
|
|
438
431
|
};
|
|
439
432
|
}
|
|
440
|
-
return
|
|
433
|
+
return sendTo(target, ({
|
|
441
434
|
event
|
|
442
|
-
}) => event,
|
|
443
|
-
...options,
|
|
444
|
-
to: target
|
|
445
|
-
});
|
|
435
|
+
}) => event, options);
|
|
446
436
|
}
|
|
447
437
|
|
|
448
438
|
/**
|
|
@@ -458,25 +448,7 @@ function escalate(errorData, options) {
|
|
|
458
448
|
type: error$1,
|
|
459
449
|
data: isFunction(errorData) ? errorData(arg) : errorData
|
|
460
450
|
};
|
|
461
|
-
},
|
|
462
|
-
...options,
|
|
463
|
-
to: SpecialTargets.Parent
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
/**
|
|
468
|
-
* Sends an event to an actor.
|
|
469
|
-
*
|
|
470
|
-
* @param actor The `ActorRef` to send the event to.
|
|
471
|
-
* @param event The event to send, or an expression that evaluates to the event to send
|
|
472
|
-
* @param options Send action options
|
|
473
|
-
* @returns An XState send action object
|
|
474
|
-
*/
|
|
475
|
-
function sendTo(actor, event, options) {
|
|
476
|
-
return send(event, {
|
|
477
|
-
...options,
|
|
478
|
-
to: actor
|
|
479
|
-
});
|
|
451
|
+
}, options);
|
|
480
452
|
}
|
|
481
453
|
|
|
482
454
|
const cache = new WeakMap();
|
|
@@ -530,8 +502,6 @@ function cancel(sendId) {
|
|
|
530
502
|
});
|
|
531
503
|
}
|
|
532
504
|
|
|
533
|
-
const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
|
|
534
|
-
|
|
535
505
|
class Mailbox {
|
|
536
506
|
constructor(_process) {
|
|
537
507
|
this._process = _process;
|
|
@@ -599,533 +569,257 @@ class Mailbox {
|
|
|
599
569
|
}
|
|
600
570
|
}
|
|
601
571
|
|
|
602
|
-
function
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
reverseKeyedActors.delete(actorRef);
|
|
619
|
-
}
|
|
572
|
+
const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Returns actor logic from a transition function and its initial state.
|
|
576
|
+
*
|
|
577
|
+
* A transition function is a function that takes the current state and an event and returns the next state.
|
|
578
|
+
*
|
|
579
|
+
* @param transition The transition function that returns the next state given the current state and event.
|
|
580
|
+
* @param initialState The initial state of the transition function.
|
|
581
|
+
* @returns Actor logic
|
|
582
|
+
*/
|
|
583
|
+
function fromTransition(transition, initialState) {
|
|
584
|
+
const logic = {
|
|
585
|
+
config: transition,
|
|
586
|
+
transition: (state, event, actorContext) => {
|
|
587
|
+
return transition(state, event, actorContext);
|
|
620
588
|
},
|
|
621
|
-
|
|
622
|
-
return
|
|
589
|
+
getInitialState: (_, input) => {
|
|
590
|
+
return typeof initialState === 'function' ? initialState({
|
|
591
|
+
input
|
|
592
|
+
}) : initialState;
|
|
623
593
|
},
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
throw new Error(`Actor with system ID '${systemId}' already exists.`);
|
|
628
|
-
}
|
|
629
|
-
keyedActors.set(systemId, actorRef);
|
|
630
|
-
reverseKeyedActors.set(actorRef, systemId);
|
|
631
|
-
}
|
|
594
|
+
getSnapshot: state => state,
|
|
595
|
+
getPersistedState: state => state,
|
|
596
|
+
restoreState: state => state
|
|
632
597
|
};
|
|
633
|
-
return
|
|
598
|
+
return logic;
|
|
634
599
|
}
|
|
635
600
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
601
|
+
const resolveEventType = '$$xstate.resolve';
|
|
602
|
+
const rejectEventType = '$$xstate.reject';
|
|
603
|
+
function fromPromise(
|
|
604
|
+
// TODO: add types
|
|
605
|
+
promiseCreator) {
|
|
606
|
+
// TODO: add event types, consider making the `PromiseEvent` a private type or smth alike
|
|
607
|
+
const logic = {
|
|
608
|
+
config: promiseCreator,
|
|
609
|
+
transition: (state, event) => {
|
|
610
|
+
if (state.status !== 'active') {
|
|
611
|
+
return state;
|
|
612
|
+
}
|
|
613
|
+
switch (event.type) {
|
|
614
|
+
case resolveEventType:
|
|
615
|
+
return {
|
|
616
|
+
...state,
|
|
617
|
+
status: 'done',
|
|
618
|
+
data: event.data,
|
|
619
|
+
input: undefined
|
|
620
|
+
};
|
|
621
|
+
case rejectEventType:
|
|
622
|
+
return {
|
|
623
|
+
...state,
|
|
624
|
+
status: 'error',
|
|
625
|
+
data: event.data,
|
|
626
|
+
input: undefined
|
|
627
|
+
};
|
|
628
|
+
case stopSignalType:
|
|
629
|
+
return {
|
|
630
|
+
...state,
|
|
631
|
+
status: 'canceled',
|
|
632
|
+
input: undefined
|
|
633
|
+
};
|
|
634
|
+
default:
|
|
635
|
+
return state;
|
|
636
|
+
}
|
|
647
637
|
},
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
638
|
+
start: (state, {
|
|
639
|
+
self,
|
|
640
|
+
system
|
|
641
|
+
}) => {
|
|
642
|
+
// TODO: determine how to allow customizing this so that promises
|
|
643
|
+
// can be restarted if necessary
|
|
644
|
+
if (state.status !== 'active') {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
const resolvedPromise = Promise.resolve(promiseCreator({
|
|
648
|
+
input: state.input,
|
|
649
|
+
system
|
|
650
|
+
}));
|
|
651
|
+
resolvedPromise.then(response => {
|
|
652
|
+
// TODO: remove this condition once dead letter queue lands
|
|
653
|
+
if (self._state.status !== 'active') {
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
self.send({
|
|
657
|
+
type: resolveEventType,
|
|
658
|
+
data: response
|
|
659
|
+
});
|
|
660
|
+
}, errorData => {
|
|
661
|
+
// TODO: remove this condition once dead letter queue lands
|
|
662
|
+
if (self._state.status !== 'active') {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
self.send({
|
|
666
|
+
type: rejectEventType,
|
|
667
|
+
data: errorData
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
},
|
|
671
|
+
getInitialState: (_, input) => {
|
|
672
|
+
return {
|
|
673
|
+
status: 'active',
|
|
674
|
+
data: undefined,
|
|
675
|
+
input
|
|
676
|
+
};
|
|
677
|
+
},
|
|
678
|
+
getSnapshot: state => state.data,
|
|
679
|
+
getStatus: state => state,
|
|
680
|
+
getPersistedState: state => state,
|
|
681
|
+
restoreState: state => state
|
|
682
|
+
};
|
|
683
|
+
return logic;
|
|
684
|
+
}
|
|
675
685
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
686
|
+
// TODO: this likely shouldn't accept TEvent, observable actor doesn't accept external events
|
|
687
|
+
function fromObservable(observableCreator) {
|
|
688
|
+
const nextEventType = '$$xstate.next';
|
|
689
|
+
const errorEventType = '$$xstate.error';
|
|
690
|
+
const completeEventType = '$$xstate.complete';
|
|
679
691
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
*/
|
|
686
|
-
constructor(logic, options) {
|
|
687
|
-
this.logic = logic;
|
|
688
|
-
this._state = void 0;
|
|
689
|
-
this.clock = void 0;
|
|
690
|
-
this.options = void 0;
|
|
691
|
-
this.id = void 0;
|
|
692
|
-
this.mailbox = new Mailbox(this._process.bind(this));
|
|
693
|
-
this.delayedEventsMap = {};
|
|
694
|
-
this.observers = new Set();
|
|
695
|
-
this.logger = void 0;
|
|
696
|
-
this.status = ActorStatus.NotStarted;
|
|
697
|
-
this._parent = void 0;
|
|
698
|
-
this.ref = void 0;
|
|
699
|
-
this._actorContext = void 0;
|
|
700
|
-
this._systemId = void 0;
|
|
701
|
-
this.sessionId = void 0;
|
|
702
|
-
this.system = void 0;
|
|
703
|
-
this._doneEvent = void 0;
|
|
704
|
-
this.src = void 0;
|
|
705
|
-
this._deferred = [];
|
|
706
|
-
const resolvedOptions = {
|
|
707
|
-
...defaultOptions,
|
|
708
|
-
...options
|
|
709
|
-
};
|
|
710
|
-
const {
|
|
711
|
-
clock,
|
|
712
|
-
logger,
|
|
713
|
-
parent,
|
|
692
|
+
// TODO: add event types
|
|
693
|
+
const logic = {
|
|
694
|
+
config: observableCreator,
|
|
695
|
+
transition: (state, event, {
|
|
696
|
+
self,
|
|
714
697
|
id,
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
const self = this;
|
|
718
|
-
this.system = parent?.system ?? createSystem();
|
|
719
|
-
if (systemId) {
|
|
720
|
-
this._systemId = systemId;
|
|
721
|
-
this.system._set(systemId, this);
|
|
722
|
-
}
|
|
723
|
-
this.sessionId = this.system._bookId();
|
|
724
|
-
this.id = id ?? this.sessionId;
|
|
725
|
-
this.logger = logger;
|
|
726
|
-
this.clock = clock;
|
|
727
|
-
this._parent = parent;
|
|
728
|
-
this.options = resolvedOptions;
|
|
729
|
-
this.src = resolvedOptions.src;
|
|
730
|
-
this.ref = this;
|
|
731
|
-
this._actorContext = {
|
|
732
|
-
self,
|
|
733
|
-
id: this.id,
|
|
734
|
-
sessionId: this.sessionId,
|
|
735
|
-
logger: this.logger,
|
|
736
|
-
defer: fn => {
|
|
737
|
-
this._deferred.push(fn);
|
|
738
|
-
},
|
|
739
|
-
system: this.system,
|
|
740
|
-
stopChild: child => {
|
|
741
|
-
if (child._parent !== this) {
|
|
742
|
-
throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
|
|
743
|
-
}
|
|
744
|
-
child._stop();
|
|
745
|
-
}
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
// Ensure that the send method is bound to this interpreter instance
|
|
749
|
-
// if destructured
|
|
750
|
-
this.send = this.send.bind(this);
|
|
751
|
-
this._initState();
|
|
752
|
-
}
|
|
753
|
-
_initState() {
|
|
754
|
-
this._state = this.options.state ? this.logic.restoreState ? this.logic.restoreState(this.options.state, this._actorContext) : this.options.state : this.logic.getInitialState(this._actorContext, this.options?.input);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// array of functions to defer
|
|
758
|
-
|
|
759
|
-
update(state) {
|
|
760
|
-
// Update state
|
|
761
|
-
this._state = state;
|
|
762
|
-
const snapshot = this.getSnapshot();
|
|
763
|
-
|
|
764
|
-
// Execute deferred effects
|
|
765
|
-
let deferredFn;
|
|
766
|
-
while (deferredFn = this._deferred.shift()) {
|
|
767
|
-
deferredFn();
|
|
768
|
-
}
|
|
769
|
-
for (const observer of this.observers) {
|
|
770
|
-
observer.next?.(snapshot);
|
|
771
|
-
}
|
|
772
|
-
const status = this.logic.getStatus?.(state);
|
|
773
|
-
switch (status?.status) {
|
|
774
|
-
case 'done':
|
|
775
|
-
this._stopProcedure();
|
|
776
|
-
this._doneEvent = doneInvoke(this.id, status.data);
|
|
777
|
-
this._parent?.send(this._doneEvent);
|
|
778
|
-
this._complete();
|
|
779
|
-
break;
|
|
780
|
-
case 'error':
|
|
781
|
-
this._stopProcedure();
|
|
782
|
-
this._parent?.send(error(this.id, status.data));
|
|
783
|
-
this._error(status.data);
|
|
784
|
-
break;
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
subscribe(nextListenerOrObserver, errorListener, completeListener) {
|
|
788
|
-
const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
|
|
789
|
-
this.observers.add(observer);
|
|
790
|
-
if (this.status === ActorStatus.Stopped) {
|
|
791
|
-
observer.complete?.();
|
|
792
|
-
this.observers.delete(observer);
|
|
793
|
-
}
|
|
794
|
-
return {
|
|
795
|
-
unsubscribe: () => {
|
|
796
|
-
this.observers.delete(observer);
|
|
797
|
-
}
|
|
798
|
-
};
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
/**
|
|
802
|
-
* Starts the interpreter from the initial state
|
|
803
|
-
*/
|
|
804
|
-
start() {
|
|
805
|
-
if (this.status === ActorStatus.Running) {
|
|
806
|
-
// Do not restart the service if it is already started
|
|
807
|
-
return this;
|
|
808
|
-
}
|
|
809
|
-
this.system._register(this.sessionId, this);
|
|
810
|
-
if (this._systemId) {
|
|
811
|
-
this.system._set(this._systemId, this);
|
|
812
|
-
}
|
|
813
|
-
this.status = ActorStatus.Running;
|
|
814
|
-
if (this.logic.start) {
|
|
815
|
-
this.logic.start(this._state, this._actorContext);
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// TODO: this notifies all subscribers but usually this is redundant
|
|
819
|
-
// there is no real change happening here
|
|
820
|
-
// we need to rethink if this needs to be refactored
|
|
821
|
-
this.update(this._state);
|
|
822
|
-
if (this.options.devTools) {
|
|
823
|
-
this.attachDevTools();
|
|
824
|
-
}
|
|
825
|
-
this.mailbox.start();
|
|
826
|
-
return this;
|
|
827
|
-
}
|
|
828
|
-
_process(event) {
|
|
829
|
-
try {
|
|
830
|
-
const nextState = this.logic.transition(this._state, event, this._actorContext);
|
|
831
|
-
this.update(nextState);
|
|
832
|
-
if (event.type === stopSignalType) {
|
|
833
|
-
this._stopProcedure();
|
|
834
|
-
this._complete();
|
|
835
|
-
}
|
|
836
|
-
} catch (err) {
|
|
837
|
-
// TODO: properly handle errors
|
|
838
|
-
if (this.observers.size > 0) {
|
|
839
|
-
this.observers.forEach(observer => {
|
|
840
|
-
observer.error?.(err);
|
|
841
|
-
});
|
|
842
|
-
this.stop();
|
|
843
|
-
} else {
|
|
844
|
-
throw err;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
_stop() {
|
|
849
|
-
if (this.status === ActorStatus.Stopped) {
|
|
850
|
-
return this;
|
|
851
|
-
}
|
|
852
|
-
this.mailbox.clear();
|
|
853
|
-
if (this.status === ActorStatus.NotStarted) {
|
|
854
|
-
this.status = ActorStatus.Stopped;
|
|
855
|
-
return this;
|
|
856
|
-
}
|
|
857
|
-
this.mailbox.enqueue({
|
|
858
|
-
type: stopSignalType
|
|
859
|
-
});
|
|
860
|
-
return this;
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
/**
|
|
864
|
-
* Stops the interpreter and unsubscribe all listeners.
|
|
865
|
-
*/
|
|
866
|
-
stop() {
|
|
867
|
-
if (this._parent) {
|
|
868
|
-
throw new Error('A non-root actor cannot be stopped directly.');
|
|
869
|
-
}
|
|
870
|
-
return this._stop();
|
|
871
|
-
}
|
|
872
|
-
_complete() {
|
|
873
|
-
for (const observer of this.observers) {
|
|
874
|
-
observer.complete?.();
|
|
875
|
-
}
|
|
876
|
-
this.observers.clear();
|
|
877
|
-
}
|
|
878
|
-
_error(data) {
|
|
879
|
-
for (const observer of this.observers) {
|
|
880
|
-
observer.error?.(data);
|
|
881
|
-
}
|
|
882
|
-
this.observers.clear();
|
|
883
|
-
}
|
|
884
|
-
_stopProcedure() {
|
|
885
|
-
if (this.status !== ActorStatus.Running) {
|
|
886
|
-
// Interpreter already stopped; do nothing
|
|
887
|
-
return this;
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
// Cancel all delayed events
|
|
891
|
-
for (const key of Object.keys(this.delayedEventsMap)) {
|
|
892
|
-
this.clock.clearTimeout(this.delayedEventsMap[key]);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
// TODO: mailbox.reset
|
|
896
|
-
this.mailbox.clear();
|
|
897
|
-
// TODO: after `stop` we must prepare ourselves for receiving events again
|
|
898
|
-
// events sent *after* stop signal must be queued
|
|
899
|
-
// it seems like this should be the common behavior for all of our consumers
|
|
900
|
-
// so perhaps this should be unified somehow for all of them
|
|
901
|
-
this.mailbox = new Mailbox(this._process.bind(this));
|
|
902
|
-
this.status = ActorStatus.Stopped;
|
|
903
|
-
this.system._unregister(this);
|
|
904
|
-
return this;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* Sends an event to the running interpreter to trigger a transition.
|
|
909
|
-
*
|
|
910
|
-
* @param event The event to send
|
|
911
|
-
*/
|
|
912
|
-
send(event) {
|
|
913
|
-
if (typeof event === 'string') {
|
|
914
|
-
throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
|
|
915
|
-
}
|
|
916
|
-
if (this.status === ActorStatus.Stopped) {
|
|
917
|
-
// do nothing
|
|
918
|
-
{
|
|
919
|
-
const eventString = JSON.stringify(event);
|
|
920
|
-
console.warn(`Event "${event.type.toString()}" was sent to stopped actor "${this.id} (${this.sessionId})". This actor has already reached its final state, and will not transition.\nEvent: ${eventString}`);
|
|
921
|
-
}
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
|
|
925
|
-
throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
|
|
926
|
-
// tslint:disable-next-line:max-line-length
|
|
927
|
-
}". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
|
|
928
|
-
}
|
|
929
|
-
this.mailbox.enqueue(event);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
// TODO: make private (and figure out a way to do this within the machine)
|
|
933
|
-
delaySend(sendAction) {
|
|
934
|
-
this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
|
|
935
|
-
if ('to' in sendAction.params && sendAction.params.to) {
|
|
936
|
-
sendAction.params.to.send(sendAction.params.event);
|
|
937
|
-
} else {
|
|
938
|
-
this.send(sendAction.params.event);
|
|
939
|
-
}
|
|
940
|
-
}, sendAction.params.delay);
|
|
941
|
-
}
|
|
942
|
-
|
|
943
|
-
// TODO: make private (and figure out a way to do this within the machine)
|
|
944
|
-
cancel(sendId) {
|
|
945
|
-
this.clock.clearTimeout(this.delayedEventsMap[sendId]);
|
|
946
|
-
delete this.delayedEventsMap[sendId];
|
|
947
|
-
}
|
|
948
|
-
attachDevTools() {
|
|
949
|
-
const {
|
|
950
|
-
devTools
|
|
951
|
-
} = this.options;
|
|
952
|
-
if (devTools) {
|
|
953
|
-
const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
|
|
954
|
-
resolvedDevToolsAdapter(this);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
toJSON() {
|
|
958
|
-
return {
|
|
959
|
-
id: this.id
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
getPersistedState() {
|
|
963
|
-
return this.logic.getPersistedState?.(this._state);
|
|
964
|
-
}
|
|
965
|
-
[symbolObservable]() {
|
|
966
|
-
return this;
|
|
967
|
-
}
|
|
968
|
-
getSnapshot() {
|
|
969
|
-
return this.logic.getSnapshot ? this.logic.getSnapshot(this._state) : this._state;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
/**
|
|
974
|
-
* Creates a new Interpreter instance for the given machine with the provided options, if any.
|
|
975
|
-
*
|
|
976
|
-
* @param machine The machine to interpret
|
|
977
|
-
* @param options Interpreter options
|
|
978
|
-
*/
|
|
979
|
-
|
|
980
|
-
function interpret(logic, options) {
|
|
981
|
-
const interpreter = new Interpreter(logic, options);
|
|
982
|
-
return interpreter;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
/**
|
|
986
|
-
* Returns actor logic from a transition function and its initial state.
|
|
987
|
-
*
|
|
988
|
-
* A transition function is a function that takes the current state and an event and returns the next state.
|
|
989
|
-
*
|
|
990
|
-
* @param transition The transition function that returns the next state given the current state and event.
|
|
991
|
-
* @param initialState The initial state of the transition function.
|
|
992
|
-
* @returns Actor logic
|
|
993
|
-
*/
|
|
994
|
-
function fromTransition(transition, initialState) {
|
|
995
|
-
const logic = {
|
|
996
|
-
config: transition,
|
|
997
|
-
transition: (state, event, actorContext) => {
|
|
998
|
-
return transition(state, event, actorContext);
|
|
999
|
-
},
|
|
1000
|
-
getInitialState: (_, input) => {
|
|
1001
|
-
return typeof initialState === 'function' ? initialState({
|
|
1002
|
-
input
|
|
1003
|
-
}) : initialState;
|
|
1004
|
-
},
|
|
1005
|
-
getSnapshot: state => state,
|
|
1006
|
-
getPersistedState: state => state,
|
|
1007
|
-
restoreState: state => state
|
|
1008
|
-
};
|
|
1009
|
-
return logic;
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
function fromPromise(
|
|
1013
|
-
// TODO: add types
|
|
1014
|
-
promiseCreator) {
|
|
1015
|
-
const resolveEventType = '$$xstate.resolve';
|
|
1016
|
-
const rejectEventType = '$$xstate.reject';
|
|
1017
|
-
|
|
1018
|
-
// TODO: add event types
|
|
1019
|
-
const logic = {
|
|
1020
|
-
config: promiseCreator,
|
|
1021
|
-
transition: (state, event) => {
|
|
698
|
+
defer
|
|
699
|
+
}) => {
|
|
1022
700
|
if (state.status !== 'active') {
|
|
1023
701
|
return state;
|
|
1024
702
|
}
|
|
1025
703
|
switch (event.type) {
|
|
1026
|
-
case
|
|
704
|
+
case nextEventType:
|
|
705
|
+
// match the exact timing of events sent by machines
|
|
706
|
+
// send actions are not executed immediately
|
|
707
|
+
defer(() => {
|
|
708
|
+
self._parent?.send({
|
|
709
|
+
type: `xstate.snapshot.${id}`,
|
|
710
|
+
data: event.data
|
|
711
|
+
});
|
|
712
|
+
});
|
|
1027
713
|
return {
|
|
1028
714
|
...state,
|
|
1029
|
-
|
|
1030
|
-
data: event.data,
|
|
1031
|
-
input: undefined
|
|
715
|
+
data: event.data
|
|
1032
716
|
};
|
|
1033
|
-
case
|
|
717
|
+
case errorEventType:
|
|
1034
718
|
return {
|
|
1035
719
|
...state,
|
|
1036
720
|
status: 'error',
|
|
721
|
+
input: undefined,
|
|
1037
722
|
data: event.data,
|
|
1038
|
-
|
|
723
|
+
subscription: undefined
|
|
724
|
+
};
|
|
725
|
+
case completeEventType:
|
|
726
|
+
return {
|
|
727
|
+
...state,
|
|
728
|
+
status: 'done',
|
|
729
|
+
input: undefined,
|
|
730
|
+
subscription: undefined
|
|
1039
731
|
};
|
|
1040
732
|
case stopSignalType:
|
|
733
|
+
state.subscription.unsubscribe();
|
|
1041
734
|
return {
|
|
1042
735
|
...state,
|
|
1043
736
|
status: 'canceled',
|
|
1044
|
-
input: undefined
|
|
737
|
+
input: undefined,
|
|
738
|
+
subscription: undefined
|
|
1045
739
|
};
|
|
1046
740
|
default:
|
|
1047
741
|
return state;
|
|
1048
742
|
}
|
|
1049
743
|
},
|
|
744
|
+
getInitialState: (_, input) => {
|
|
745
|
+
return {
|
|
746
|
+
subscription: undefined,
|
|
747
|
+
status: 'active',
|
|
748
|
+
data: undefined,
|
|
749
|
+
input
|
|
750
|
+
};
|
|
751
|
+
},
|
|
1050
752
|
start: (state, {
|
|
1051
753
|
self,
|
|
1052
754
|
system
|
|
1053
755
|
}) => {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
if (state.status !== 'active') {
|
|
756
|
+
if (state.status === 'done') {
|
|
757
|
+
// Do not restart a completed observable
|
|
1057
758
|
return;
|
|
1058
759
|
}
|
|
1059
|
-
|
|
760
|
+
state.subscription = observableCreator({
|
|
1060
761
|
input: state.input,
|
|
1061
762
|
system
|
|
1062
|
-
})
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
763
|
+
}).subscribe({
|
|
764
|
+
next: value => {
|
|
765
|
+
self.send({
|
|
766
|
+
type: nextEventType,
|
|
767
|
+
data: value
|
|
768
|
+
});
|
|
769
|
+
},
|
|
770
|
+
error: err => {
|
|
771
|
+
self.send({
|
|
772
|
+
type: errorEventType,
|
|
773
|
+
data: err
|
|
774
|
+
});
|
|
775
|
+
},
|
|
776
|
+
complete: () => {
|
|
777
|
+
self.send({
|
|
778
|
+
type: completeEventType
|
|
779
|
+
});
|
|
1076
780
|
}
|
|
1077
|
-
self.send({
|
|
1078
|
-
type: rejectEventType,
|
|
1079
|
-
data: errorData
|
|
1080
|
-
});
|
|
1081
781
|
});
|
|
1082
782
|
},
|
|
1083
|
-
getInitialState: (_, input) => {
|
|
1084
|
-
return {
|
|
1085
|
-
status: 'active',
|
|
1086
|
-
data: undefined,
|
|
1087
|
-
input
|
|
1088
|
-
};
|
|
1089
|
-
},
|
|
1090
783
|
getSnapshot: state => state.data,
|
|
784
|
+
getPersistedState: ({
|
|
785
|
+
status,
|
|
786
|
+
data,
|
|
787
|
+
input
|
|
788
|
+
}) => ({
|
|
789
|
+
status,
|
|
790
|
+
data,
|
|
791
|
+
input
|
|
792
|
+
}),
|
|
1091
793
|
getStatus: state => state,
|
|
1092
|
-
|
|
1093
|
-
|
|
794
|
+
restoreState: state => ({
|
|
795
|
+
...state,
|
|
796
|
+
subscription: undefined
|
|
797
|
+
})
|
|
1094
798
|
};
|
|
1095
799
|
return logic;
|
|
1096
800
|
}
|
|
1097
801
|
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
802
|
+
/**
|
|
803
|
+
* Creates event observable logic that listens to an observable
|
|
804
|
+
* that delivers event objects.
|
|
805
|
+
*
|
|
806
|
+
*
|
|
807
|
+
* @param lazyObservable A function that creates an observable
|
|
808
|
+
* @returns Event observable logic
|
|
809
|
+
*/
|
|
810
|
+
|
|
811
|
+
function fromEventObservable(lazyObservable) {
|
|
1101
812
|
const errorEventType = '$$xstate.error';
|
|
1102
813
|
const completeEventType = '$$xstate.complete';
|
|
1103
814
|
|
|
1104
|
-
// TODO:
|
|
815
|
+
// TODO: event types
|
|
1105
816
|
const logic = {
|
|
1106
|
-
config:
|
|
1107
|
-
transition: (state, event
|
|
1108
|
-
self,
|
|
1109
|
-
id,
|
|
1110
|
-
defer
|
|
1111
|
-
}) => {
|
|
817
|
+
config: lazyObservable,
|
|
818
|
+
transition: (state, event) => {
|
|
1112
819
|
if (state.status !== 'active') {
|
|
1113
820
|
return state;
|
|
1114
821
|
}
|
|
1115
822
|
switch (event.type) {
|
|
1116
|
-
case nextEventType:
|
|
1117
|
-
// match the exact timing of events sent by machines
|
|
1118
|
-
// send actions are not executed immediately
|
|
1119
|
-
defer(() => {
|
|
1120
|
-
self._parent?.send({
|
|
1121
|
-
type: `xstate.snapshot.${id}`,
|
|
1122
|
-
data: event.data
|
|
1123
|
-
});
|
|
1124
|
-
});
|
|
1125
|
-
return {
|
|
1126
|
-
...state,
|
|
1127
|
-
data: event.data
|
|
1128
|
-
};
|
|
1129
823
|
case errorEventType:
|
|
1130
824
|
return {
|
|
1131
825
|
...state,
|
|
@@ -1169,15 +863,12 @@ function fromObservable(observableCreator) {
|
|
|
1169
863
|
// Do not restart a completed observable
|
|
1170
864
|
return;
|
|
1171
865
|
}
|
|
1172
|
-
state.subscription =
|
|
866
|
+
state.subscription = lazyObservable({
|
|
1173
867
|
input: state.input,
|
|
1174
868
|
system
|
|
1175
869
|
}).subscribe({
|
|
1176
870
|
next: value => {
|
|
1177
|
-
self.send(
|
|
1178
|
-
type: nextEventType,
|
|
1179
|
-
data: value
|
|
1180
|
-
});
|
|
871
|
+
self._parent?.send(value);
|
|
1181
872
|
},
|
|
1182
873
|
error: err => {
|
|
1183
874
|
self.send({
|
|
@@ -1192,7 +883,7 @@ function fromObservable(observableCreator) {
|
|
|
1192
883
|
}
|
|
1193
884
|
});
|
|
1194
885
|
},
|
|
1195
|
-
getSnapshot:
|
|
886
|
+
getSnapshot: _ => undefined,
|
|
1196
887
|
getPersistedState: ({
|
|
1197
888
|
status,
|
|
1198
889
|
data,
|
|
@@ -1211,226 +902,504 @@ function fromObservable(observableCreator) {
|
|
|
1211
902
|
return logic;
|
|
1212
903
|
}
|
|
1213
904
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
905
|
+
function fromCallback(invokeCallback) {
|
|
906
|
+
const logic = {
|
|
907
|
+
config: invokeCallback,
|
|
908
|
+
start: (_state, {
|
|
909
|
+
self
|
|
910
|
+
}) => {
|
|
911
|
+
self.send({
|
|
912
|
+
type: startSignalType
|
|
913
|
+
});
|
|
914
|
+
},
|
|
915
|
+
transition: (state, event, {
|
|
916
|
+
self,
|
|
917
|
+
id,
|
|
918
|
+
system
|
|
919
|
+
}) => {
|
|
920
|
+
if (event.type === startSignalType) {
|
|
921
|
+
const sender = eventForParent => {
|
|
922
|
+
if (state.canceled) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
self._parent?.send(eventForParent);
|
|
926
|
+
};
|
|
927
|
+
const receiver = newListener => {
|
|
928
|
+
state.receivers.add(newListener);
|
|
929
|
+
};
|
|
930
|
+
state.dispose = invokeCallback(sender, receiver, {
|
|
931
|
+
input: state.input,
|
|
932
|
+
system
|
|
933
|
+
});
|
|
934
|
+
if (isPromiseLike(state.dispose)) {
|
|
935
|
+
state.dispose.then(resolved => {
|
|
936
|
+
self._parent?.send(doneInvoke(id, resolved));
|
|
937
|
+
state.canceled = true;
|
|
938
|
+
}, errorData => {
|
|
939
|
+
state.canceled = true;
|
|
940
|
+
self._parent?.send(error(id, errorData));
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
return state;
|
|
944
|
+
}
|
|
945
|
+
if (event.type === stopSignalType) {
|
|
946
|
+
state.canceled = true;
|
|
947
|
+
if (isFunction(state.dispose)) {
|
|
948
|
+
state.dispose();
|
|
949
|
+
}
|
|
950
|
+
return state;
|
|
951
|
+
}
|
|
952
|
+
if (isSignal(event)) {
|
|
953
|
+
// TODO: unrecognized signal
|
|
954
|
+
return state;
|
|
955
|
+
}
|
|
956
|
+
state.receivers.forEach(receiver => receiver(event));
|
|
957
|
+
return state;
|
|
958
|
+
},
|
|
959
|
+
getInitialState: (_, input) => {
|
|
960
|
+
return {
|
|
961
|
+
canceled: false,
|
|
962
|
+
receivers: new Set(),
|
|
963
|
+
dispose: undefined,
|
|
964
|
+
input
|
|
965
|
+
};
|
|
966
|
+
},
|
|
967
|
+
getSnapshot: () => undefined,
|
|
968
|
+
getPersistedState: ({
|
|
969
|
+
input
|
|
970
|
+
}) => input
|
|
971
|
+
};
|
|
972
|
+
return logic;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const startSignalType = 'xstate.init';
|
|
976
|
+
const stopSignalType = 'xstate.stop';
|
|
977
|
+
const startSignal = {
|
|
978
|
+
type: 'xstate.init'
|
|
979
|
+
};
|
|
980
|
+
const stopSignal = {
|
|
981
|
+
type: 'xstate.stop'
|
|
982
|
+
};
|
|
983
|
+
/**
|
|
984
|
+
* An object that expresses the actor logic in reaction to received events,
|
|
985
|
+
* as well as an optionally emitted stream of values.
|
|
986
|
+
*
|
|
987
|
+
* @template TReceived The received event
|
|
988
|
+
* @template TSnapshot The emitted value
|
|
989
|
+
*/
|
|
990
|
+
|
|
991
|
+
function isSignal(event) {
|
|
992
|
+
return event.type === startSignalType || event.type === stopSignalType;
|
|
993
|
+
}
|
|
994
|
+
function isActorRef(item) {
|
|
995
|
+
return !!item && typeof item === 'object' && typeof item.send === 'function';
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// TODO: refactor the return type, this could be written in a better way
|
|
999
|
+
// but it's best to avoid unneccessary breaking changes now
|
|
1000
|
+
// @deprecated use `interpret(actorLogic)` instead
|
|
1001
|
+
function toActorRef(actorRefLike) {
|
|
1002
|
+
return {
|
|
1003
|
+
subscribe: () => ({
|
|
1004
|
+
unsubscribe: () => void 0
|
|
1005
|
+
}),
|
|
1006
|
+
id: 'anonymous',
|
|
1007
|
+
sessionId: '',
|
|
1008
|
+
getSnapshot: () => undefined,
|
|
1009
|
+
[symbolObservable]: function () {
|
|
1010
|
+
return this;
|
|
1011
|
+
},
|
|
1012
|
+
status: ActorStatus.Running,
|
|
1013
|
+
stop: () => void 0,
|
|
1014
|
+
...actorRefLike
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
const emptyLogic = fromTransition(_ => undefined, undefined);
|
|
1018
|
+
function createEmptyActor() {
|
|
1019
|
+
return interpret(emptyLogic);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function createSystem() {
|
|
1023
|
+
let sessionIdCounter = 0;
|
|
1024
|
+
const children = new Map();
|
|
1025
|
+
const keyedActors = new Map();
|
|
1026
|
+
const reverseKeyedActors = new WeakMap();
|
|
1027
|
+
const system = {
|
|
1028
|
+
_bookId: () => `x:${sessionIdCounter++}`,
|
|
1029
|
+
_register: (sessionId, actorRef) => {
|
|
1030
|
+
children.set(sessionId, actorRef);
|
|
1031
|
+
return sessionId;
|
|
1032
|
+
},
|
|
1033
|
+
_unregister: actorRef => {
|
|
1034
|
+
children.delete(actorRef.sessionId);
|
|
1035
|
+
const systemId = reverseKeyedActors.get(actorRef);
|
|
1036
|
+
if (systemId !== undefined) {
|
|
1037
|
+
keyedActors.delete(systemId);
|
|
1038
|
+
reverseKeyedActors.delete(actorRef);
|
|
1039
|
+
}
|
|
1040
|
+
},
|
|
1041
|
+
get: systemId => {
|
|
1042
|
+
return keyedActors.get(systemId);
|
|
1043
|
+
},
|
|
1044
|
+
_set: (systemId, actorRef) => {
|
|
1045
|
+
const existing = keyedActors.get(systemId);
|
|
1046
|
+
if (existing && existing !== actorRef) {
|
|
1047
|
+
throw new Error(`Actor with system ID '${systemId}' already exists.`);
|
|
1048
|
+
}
|
|
1049
|
+
keyedActors.set(systemId, actorRef);
|
|
1050
|
+
reverseKeyedActors.set(actorRef, systemId);
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
return system;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
let ActorStatus = /*#__PURE__*/function (ActorStatus) {
|
|
1057
|
+
ActorStatus[ActorStatus["NotStarted"] = 0] = "NotStarted";
|
|
1058
|
+
ActorStatus[ActorStatus["Running"] = 1] = "Running";
|
|
1059
|
+
ActorStatus[ActorStatus["Stopped"] = 2] = "Stopped";
|
|
1060
|
+
return ActorStatus;
|
|
1061
|
+
}({});
|
|
1062
|
+
const defaultOptions = {
|
|
1063
|
+
deferEvents: true,
|
|
1064
|
+
clock: {
|
|
1065
|
+
setTimeout: (fn, ms) => {
|
|
1066
|
+
return setTimeout(fn, ms);
|
|
1067
|
+
},
|
|
1068
|
+
clearTimeout: id => {
|
|
1069
|
+
return clearTimeout(id);
|
|
1070
|
+
}
|
|
1071
|
+
},
|
|
1072
|
+
logger: console.log.bind(console),
|
|
1073
|
+
devTools: false
|
|
1074
|
+
};
|
|
1075
|
+
class Interpreter {
|
|
1076
|
+
/**
|
|
1077
|
+
* The current state of the interpreted logic.
|
|
1078
|
+
*/
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
|
|
1082
|
+
*/
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* The unique identifier for this actor relative to its parent.
|
|
1086
|
+
*/
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Whether the service is started.
|
|
1090
|
+
*/
|
|
1091
|
+
|
|
1092
|
+
// Actor Ref
|
|
1093
|
+
|
|
1094
|
+
// TODO: add typings for system
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* The globally unique process ID for this invocation.
|
|
1098
|
+
*/
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Creates a new Interpreter instance (i.e., service) for the given logic with the provided options, if any.
|
|
1102
|
+
*
|
|
1103
|
+
* @param logic The logic to be interpreted
|
|
1104
|
+
* @param options Interpreter options
|
|
1105
|
+
*/
|
|
1106
|
+
constructor(logic, options) {
|
|
1107
|
+
this.logic = logic;
|
|
1108
|
+
this._state = void 0;
|
|
1109
|
+
this.clock = void 0;
|
|
1110
|
+
this.options = void 0;
|
|
1111
|
+
this.id = void 0;
|
|
1112
|
+
this.mailbox = new Mailbox(this._process.bind(this));
|
|
1113
|
+
this.delayedEventsMap = {};
|
|
1114
|
+
this.observers = new Set();
|
|
1115
|
+
this.logger = void 0;
|
|
1116
|
+
this.status = ActorStatus.NotStarted;
|
|
1117
|
+
this._parent = void 0;
|
|
1118
|
+
this.ref = void 0;
|
|
1119
|
+
this._actorContext = void 0;
|
|
1120
|
+
this._systemId = void 0;
|
|
1121
|
+
this.sessionId = void 0;
|
|
1122
|
+
this.system = void 0;
|
|
1123
|
+
this._doneEvent = void 0;
|
|
1124
|
+
this.src = void 0;
|
|
1125
|
+
this._deferred = [];
|
|
1126
|
+
const resolvedOptions = {
|
|
1127
|
+
...defaultOptions,
|
|
1128
|
+
...options
|
|
1129
|
+
};
|
|
1130
|
+
const {
|
|
1131
|
+
clock,
|
|
1132
|
+
logger,
|
|
1133
|
+
parent,
|
|
1134
|
+
id,
|
|
1135
|
+
systemId
|
|
1136
|
+
} = resolvedOptions;
|
|
1137
|
+
const self = this;
|
|
1138
|
+
this.system = parent?.system ?? createSystem();
|
|
1139
|
+
if (systemId) {
|
|
1140
|
+
this._systemId = systemId;
|
|
1141
|
+
this.system._set(systemId, this);
|
|
1142
|
+
}
|
|
1143
|
+
this.sessionId = this.system._bookId();
|
|
1144
|
+
this.id = id ?? this.sessionId;
|
|
1145
|
+
this.logger = logger;
|
|
1146
|
+
this.clock = clock;
|
|
1147
|
+
this._parent = parent;
|
|
1148
|
+
this.options = resolvedOptions;
|
|
1149
|
+
this.src = resolvedOptions.src;
|
|
1150
|
+
this.ref = this;
|
|
1151
|
+
this._actorContext = {
|
|
1152
|
+
self,
|
|
1153
|
+
id: this.id,
|
|
1154
|
+
sessionId: this.sessionId,
|
|
1155
|
+
logger: this.logger,
|
|
1156
|
+
defer: fn => {
|
|
1157
|
+
this._deferred.push(fn);
|
|
1158
|
+
},
|
|
1159
|
+
system: this.system,
|
|
1160
|
+
stopChild: child => {
|
|
1161
|
+
if (child._parent !== this) {
|
|
1162
|
+
throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
|
|
1163
|
+
}
|
|
1164
|
+
child._stop();
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
// Ensure that the send method is bound to this interpreter instance
|
|
1169
|
+
// if destructured
|
|
1170
|
+
this.send = this.send.bind(this);
|
|
1171
|
+
this._initState();
|
|
1172
|
+
}
|
|
1173
|
+
_initState() {
|
|
1174
|
+
this._state = this.options.state ? this.logic.restoreState ? this.logic.restoreState(this.options.state, this._actorContext) : this.options.state : this.logic.getInitialState(this._actorContext, this.options?.input);
|
|
1175
|
+
}
|
|
1222
1176
|
|
|
1223
|
-
|
|
1224
|
-
const errorEventType = '$$xstate.error';
|
|
1225
|
-
const completeEventType = '$$xstate.complete';
|
|
1177
|
+
// array of functions to defer
|
|
1226
1178
|
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
if (state.status !== 'active') {
|
|
1232
|
-
return state;
|
|
1233
|
-
}
|
|
1234
|
-
switch (event.type) {
|
|
1235
|
-
case errorEventType:
|
|
1236
|
-
return {
|
|
1237
|
-
...state,
|
|
1238
|
-
status: 'error',
|
|
1239
|
-
input: undefined,
|
|
1240
|
-
data: event.data,
|
|
1241
|
-
subscription: undefined
|
|
1242
|
-
};
|
|
1243
|
-
case completeEventType:
|
|
1244
|
-
return {
|
|
1245
|
-
...state,
|
|
1246
|
-
status: 'done',
|
|
1247
|
-
input: undefined,
|
|
1248
|
-
subscription: undefined
|
|
1249
|
-
};
|
|
1250
|
-
case stopSignalType:
|
|
1251
|
-
state.subscription.unsubscribe();
|
|
1252
|
-
return {
|
|
1253
|
-
...state,
|
|
1254
|
-
status: 'canceled',
|
|
1255
|
-
input: undefined,
|
|
1256
|
-
subscription: undefined
|
|
1257
|
-
};
|
|
1258
|
-
default:
|
|
1259
|
-
return state;
|
|
1260
|
-
}
|
|
1261
|
-
},
|
|
1262
|
-
getInitialState: (_, input) => {
|
|
1263
|
-
return {
|
|
1264
|
-
subscription: undefined,
|
|
1265
|
-
status: 'active',
|
|
1266
|
-
data: undefined,
|
|
1267
|
-
input
|
|
1268
|
-
};
|
|
1269
|
-
},
|
|
1270
|
-
start: (state, {
|
|
1271
|
-
self,
|
|
1272
|
-
system
|
|
1273
|
-
}) => {
|
|
1274
|
-
if (state.status === 'done') {
|
|
1275
|
-
// Do not restart a completed observable
|
|
1276
|
-
return;
|
|
1277
|
-
}
|
|
1278
|
-
state.subscription = lazyObservable({
|
|
1279
|
-
input: state.input,
|
|
1280
|
-
system
|
|
1281
|
-
}).subscribe({
|
|
1282
|
-
next: value => {
|
|
1283
|
-
self._parent?.send(value);
|
|
1284
|
-
},
|
|
1285
|
-
error: err => {
|
|
1286
|
-
self.send({
|
|
1287
|
-
type: errorEventType,
|
|
1288
|
-
data: err
|
|
1289
|
-
});
|
|
1290
|
-
},
|
|
1291
|
-
complete: () => {
|
|
1292
|
-
self.send({
|
|
1293
|
-
type: completeEventType
|
|
1294
|
-
});
|
|
1295
|
-
}
|
|
1296
|
-
});
|
|
1297
|
-
},
|
|
1298
|
-
getSnapshot: _ => undefined,
|
|
1299
|
-
getPersistedState: ({
|
|
1300
|
-
status,
|
|
1301
|
-
data,
|
|
1302
|
-
input
|
|
1303
|
-
}) => ({
|
|
1304
|
-
status,
|
|
1305
|
-
data,
|
|
1306
|
-
input
|
|
1307
|
-
}),
|
|
1308
|
-
getStatus: state => state,
|
|
1309
|
-
restoreState: state => ({
|
|
1310
|
-
...state,
|
|
1311
|
-
subscription: undefined
|
|
1312
|
-
})
|
|
1313
|
-
};
|
|
1314
|
-
return logic;
|
|
1315
|
-
}
|
|
1179
|
+
update(state) {
|
|
1180
|
+
// Update state
|
|
1181
|
+
this._state = state;
|
|
1182
|
+
const snapshot = this.getSnapshot();
|
|
1316
1183
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
}, errorData => {
|
|
1351
|
-
state.canceled = true;
|
|
1352
|
-
self._parent?.send(error(id, errorData));
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1355
|
-
return state;
|
|
1184
|
+
// Execute deferred effects
|
|
1185
|
+
let deferredFn;
|
|
1186
|
+
while (deferredFn = this._deferred.shift()) {
|
|
1187
|
+
deferredFn();
|
|
1188
|
+
}
|
|
1189
|
+
for (const observer of this.observers) {
|
|
1190
|
+
observer.next?.(snapshot);
|
|
1191
|
+
}
|
|
1192
|
+
const status = this.logic.getStatus?.(state);
|
|
1193
|
+
switch (status?.status) {
|
|
1194
|
+
case 'done':
|
|
1195
|
+
this._stopProcedure();
|
|
1196
|
+
this._doneEvent = doneInvoke(this.id, status.data);
|
|
1197
|
+
this._parent?.send(this._doneEvent);
|
|
1198
|
+
this._complete();
|
|
1199
|
+
break;
|
|
1200
|
+
case 'error':
|
|
1201
|
+
this._stopProcedure();
|
|
1202
|
+
this._parent?.send(error(this.id, status.data));
|
|
1203
|
+
this._error(status.data);
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
subscribe(nextListenerOrObserver, errorListener, completeListener) {
|
|
1208
|
+
const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
|
|
1209
|
+
this.observers.add(observer);
|
|
1210
|
+
if (this.status === ActorStatus.Stopped) {
|
|
1211
|
+
observer.complete?.();
|
|
1212
|
+
this.observers.delete(observer);
|
|
1213
|
+
}
|
|
1214
|
+
return {
|
|
1215
|
+
unsubscribe: () => {
|
|
1216
|
+
this.observers.delete(observer);
|
|
1356
1217
|
}
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* Starts the interpreter from the initial state
|
|
1223
|
+
*/
|
|
1224
|
+
start() {
|
|
1225
|
+
if (this.status === ActorStatus.Running) {
|
|
1226
|
+
// Do not restart the service if it is already started
|
|
1227
|
+
return this;
|
|
1228
|
+
}
|
|
1229
|
+
this.system._register(this.sessionId, this);
|
|
1230
|
+
if (this._systemId) {
|
|
1231
|
+
this.system._set(this._systemId, this);
|
|
1232
|
+
}
|
|
1233
|
+
this.status = ActorStatus.Running;
|
|
1234
|
+
if (this.logic.start) {
|
|
1235
|
+
this.logic.start(this._state, this._actorContext);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// TODO: this notifies all subscribers but usually this is redundant
|
|
1239
|
+
// there is no real change happening here
|
|
1240
|
+
// we need to rethink if this needs to be refactored
|
|
1241
|
+
this.update(this._state);
|
|
1242
|
+
if (this.options.devTools) {
|
|
1243
|
+
this.attachDevTools();
|
|
1244
|
+
}
|
|
1245
|
+
this.mailbox.start();
|
|
1246
|
+
return this;
|
|
1247
|
+
}
|
|
1248
|
+
_process(event) {
|
|
1249
|
+
try {
|
|
1250
|
+
const nextState = this.logic.transition(this._state, event, this._actorContext);
|
|
1251
|
+
this.update(nextState);
|
|
1357
1252
|
if (event.type === stopSignalType) {
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
state.dispose();
|
|
1361
|
-
}
|
|
1362
|
-
return state;
|
|
1253
|
+
this._stopProcedure();
|
|
1254
|
+
this._complete();
|
|
1363
1255
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1256
|
+
} catch (err) {
|
|
1257
|
+
// TODO: properly handle errors
|
|
1258
|
+
if (this.observers.size > 0) {
|
|
1259
|
+
this.observers.forEach(observer => {
|
|
1260
|
+
observer.error?.(err);
|
|
1261
|
+
});
|
|
1262
|
+
this.stop();
|
|
1263
|
+
} else {
|
|
1264
|
+
throw err;
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
_stop() {
|
|
1269
|
+
if (this.status === ActorStatus.Stopped) {
|
|
1270
|
+
return this;
|
|
1271
|
+
}
|
|
1272
|
+
this.mailbox.clear();
|
|
1273
|
+
if (this.status === ActorStatus.NotStarted) {
|
|
1274
|
+
this.status = ActorStatus.Stopped;
|
|
1275
|
+
return this;
|
|
1276
|
+
}
|
|
1277
|
+
this.mailbox.enqueue({
|
|
1278
|
+
type: stopSignalType
|
|
1279
|
+
});
|
|
1280
|
+
return this;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Stops the interpreter and unsubscribe all listeners.
|
|
1285
|
+
*/
|
|
1286
|
+
stop() {
|
|
1287
|
+
if (this._parent) {
|
|
1288
|
+
throw new Error('A non-root actor cannot be stopped directly.');
|
|
1289
|
+
}
|
|
1290
|
+
return this._stop();
|
|
1291
|
+
}
|
|
1292
|
+
_complete() {
|
|
1293
|
+
for (const observer of this.observers) {
|
|
1294
|
+
observer.complete?.();
|
|
1295
|
+
}
|
|
1296
|
+
this.observers.clear();
|
|
1297
|
+
}
|
|
1298
|
+
_error(data) {
|
|
1299
|
+
for (const observer of this.observers) {
|
|
1300
|
+
observer.error?.(data);
|
|
1301
|
+
}
|
|
1302
|
+
this.observers.clear();
|
|
1303
|
+
}
|
|
1304
|
+
_stopProcedure() {
|
|
1305
|
+
if (this.status !== ActorStatus.Running) {
|
|
1306
|
+
// Interpreter already stopped; do nothing
|
|
1307
|
+
return this;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// Cancel all delayed events
|
|
1311
|
+
for (const key of Object.keys(this.delayedEventsMap)) {
|
|
1312
|
+
this.clock.clearTimeout(this.delayedEventsMap[key]);
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
// TODO: mailbox.reset
|
|
1316
|
+
this.mailbox.clear();
|
|
1317
|
+
// TODO: after `stop` we must prepare ourselves for receiving events again
|
|
1318
|
+
// events sent *after* stop signal must be queued
|
|
1319
|
+
// it seems like this should be the common behavior for all of our consumers
|
|
1320
|
+
// so perhaps this should be unified somehow for all of them
|
|
1321
|
+
this.mailbox = new Mailbox(this._process.bind(this));
|
|
1322
|
+
this.status = ActorStatus.Stopped;
|
|
1323
|
+
this.system._unregister(this);
|
|
1324
|
+
return this;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Sends an event to the running interpreter to trigger a transition.
|
|
1329
|
+
*
|
|
1330
|
+
* @param event The event to send
|
|
1331
|
+
*/
|
|
1332
|
+
send(event) {
|
|
1333
|
+
if (typeof event === 'string') {
|
|
1334
|
+
throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
|
|
1335
|
+
}
|
|
1336
|
+
if (this.status === ActorStatus.Stopped) {
|
|
1337
|
+
// do nothing
|
|
1338
|
+
{
|
|
1339
|
+
const eventString = JSON.stringify(event);
|
|
1340
|
+
console.warn(`Event "${event.type.toString()}" was sent to stopped actor "${this.id} (${this.sessionId})". This actor has already reached its final state, and will not transition.\nEvent: ${eventString}`);
|
|
1367
1341
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
if (this.status !== ActorStatus.Running && !this.options.deferEvents) {
|
|
1345
|
+
throw new Error(`Event "${event.type}" was sent to uninitialized actor "${this.id
|
|
1346
|
+
// tslint:disable-next-line:max-line-length
|
|
1347
|
+
}". Make sure .start() is called for this actor, or set { deferEvents: true } in the actor options.\nEvent: ${JSON.stringify(event)}`);
|
|
1348
|
+
}
|
|
1349
|
+
this.mailbox.enqueue(event);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// TODO: make private (and figure out a way to do this within the machine)
|
|
1353
|
+
delaySend(sendAction) {
|
|
1354
|
+
this.delayedEventsMap[sendAction.params.id] = this.clock.setTimeout(() => {
|
|
1355
|
+
if ('to' in sendAction.params && sendAction.params.to) {
|
|
1356
|
+
sendAction.params.to.send(sendAction.params.event);
|
|
1357
|
+
} else {
|
|
1358
|
+
this.send(sendAction.params.event);
|
|
1370
1359
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1360
|
+
}, sendAction.params.delay);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// TODO: make private (and figure out a way to do this within the machine)
|
|
1364
|
+
cancel(sendId) {
|
|
1365
|
+
this.clock.clearTimeout(this.delayedEventsMap[sendId]);
|
|
1366
|
+
delete this.delayedEventsMap[sendId];
|
|
1367
|
+
}
|
|
1368
|
+
attachDevTools() {
|
|
1369
|
+
const {
|
|
1370
|
+
devTools
|
|
1371
|
+
} = this.options;
|
|
1372
|
+
if (devTools) {
|
|
1373
|
+
const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
|
|
1374
|
+
resolvedDevToolsAdapter(this);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
toJSON() {
|
|
1378
|
+
return {
|
|
1379
|
+
id: this.id
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
getPersistedState() {
|
|
1383
|
+
return this.logic.getPersistedState?.(this._state);
|
|
1384
|
+
}
|
|
1385
|
+
[symbolObservable]() {
|
|
1386
|
+
return this;
|
|
1387
|
+
}
|
|
1388
|
+
getSnapshot() {
|
|
1389
|
+
return this.logic.getSnapshot ? this.logic.getSnapshot(this._state) : this._state;
|
|
1390
|
+
}
|
|
1387
1391
|
}
|
|
1388
1392
|
|
|
1389
|
-
const startSignalType = 'xstate.init';
|
|
1390
|
-
const stopSignalType = 'xstate.stop';
|
|
1391
|
-
const startSignal = {
|
|
1392
|
-
type: 'xstate.init'
|
|
1393
|
-
};
|
|
1394
|
-
const stopSignal = {
|
|
1395
|
-
type: 'xstate.stop'
|
|
1396
|
-
};
|
|
1397
1393
|
/**
|
|
1398
|
-
*
|
|
1399
|
-
* as well as an optionally emitted stream of values.
|
|
1394
|
+
* Creates a new Interpreter instance for the given machine with the provided options, if any.
|
|
1400
1395
|
*
|
|
1401
|
-
* @
|
|
1402
|
-
* @
|
|
1396
|
+
* @param machine The machine to interpret
|
|
1397
|
+
* @param options Interpreter options
|
|
1403
1398
|
*/
|
|
1404
1399
|
|
|
1405
|
-
function
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
function isActorRef(item) {
|
|
1409
|
-
return !!item && typeof item === 'object' && typeof item.send === 'function';
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
// TODO: refactor the return type, this could be written in a better way
|
|
1413
|
-
// but it's best to avoid unneccessary breaking changes now
|
|
1414
|
-
// @deprecated use `interpret(actorLogic)` instead
|
|
1415
|
-
function toActorRef(actorRefLike) {
|
|
1416
|
-
return {
|
|
1417
|
-
subscribe: () => ({
|
|
1418
|
-
unsubscribe: () => void 0
|
|
1419
|
-
}),
|
|
1420
|
-
id: 'anonymous',
|
|
1421
|
-
sessionId: '',
|
|
1422
|
-
getSnapshot: () => undefined,
|
|
1423
|
-
[symbolObservable]: function () {
|
|
1424
|
-
return this;
|
|
1425
|
-
},
|
|
1426
|
-
status: ActorStatus.Running,
|
|
1427
|
-
stop: () => void 0,
|
|
1428
|
-
...actorRefLike
|
|
1429
|
-
};
|
|
1430
|
-
}
|
|
1431
|
-
const emptyLogic = fromTransition(_ => undefined, undefined);
|
|
1432
|
-
function createEmptyActor() {
|
|
1433
|
-
return interpret(emptyLogic);
|
|
1400
|
+
function interpret(logic, options) {
|
|
1401
|
+
const interpreter = new Interpreter(logic, options);
|
|
1402
|
+
return interpreter;
|
|
1434
1403
|
}
|
|
1435
1404
|
|
|
1436
1405
|
function invoke(invokeDef) {
|
|
@@ -1447,42 +1416,32 @@ function invoke(invokeDef) {
|
|
|
1447
1416
|
src
|
|
1448
1417
|
} = invokeDef;
|
|
1449
1418
|
let resolvedInvokeAction;
|
|
1450
|
-
|
|
1419
|
+
const referenced = resolveReferencedActor(state.machine.implementations.actors[src]);
|
|
1420
|
+
if (!referenced) {
|
|
1421
|
+
resolvedInvokeAction = {
|
|
1422
|
+
type,
|
|
1423
|
+
params: invokeDef
|
|
1424
|
+
};
|
|
1425
|
+
} else {
|
|
1426
|
+
const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
|
|
1427
|
+
const ref = interpret(referenced.src, {
|
|
1428
|
+
id,
|
|
1429
|
+
src,
|
|
1430
|
+
parent: actorContext?.self,
|
|
1431
|
+
systemId: invokeDef.systemId,
|
|
1432
|
+
input: typeof input === 'function' ? input({
|
|
1433
|
+
context: state.context,
|
|
1434
|
+
event,
|
|
1435
|
+
self: actorContext?.self
|
|
1436
|
+
}) : input
|
|
1437
|
+
});
|
|
1451
1438
|
resolvedInvokeAction = {
|
|
1452
1439
|
type,
|
|
1453
1440
|
params: {
|
|
1454
1441
|
...invokeDef,
|
|
1455
|
-
ref
|
|
1442
|
+
ref
|
|
1456
1443
|
}
|
|
1457
1444
|
};
|
|
1458
|
-
} else {
|
|
1459
|
-
const referenced = resolveReferencedActor(state.machine.options.actors[src]);
|
|
1460
|
-
if (!referenced) {
|
|
1461
|
-
resolvedInvokeAction = {
|
|
1462
|
-
type,
|
|
1463
|
-
params: invokeDef
|
|
1464
|
-
};
|
|
1465
|
-
} else {
|
|
1466
|
-
const input = 'input' in invokeDef ? invokeDef.input : referenced.input;
|
|
1467
|
-
const ref = interpret(referenced.src, {
|
|
1468
|
-
id,
|
|
1469
|
-
src,
|
|
1470
|
-
parent: actorContext?.self,
|
|
1471
|
-
systemId: invokeDef.systemId,
|
|
1472
|
-
input: typeof input === 'function' ? input({
|
|
1473
|
-
context: state.context,
|
|
1474
|
-
event,
|
|
1475
|
-
self: actorContext?.self
|
|
1476
|
-
}) : input
|
|
1477
|
-
});
|
|
1478
|
-
resolvedInvokeAction = {
|
|
1479
|
-
type,
|
|
1480
|
-
params: {
|
|
1481
|
-
...invokeDef,
|
|
1482
|
-
ref
|
|
1483
|
-
}
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
1445
|
}
|
|
1487
1446
|
const actorRef = resolvedInvokeAction.params.ref;
|
|
1488
1447
|
const invokedState = cloneState(state, {
|
|
@@ -1597,7 +1556,7 @@ function evaluateGuard(guard, context, event, state) {
|
|
|
1597
1556
|
const {
|
|
1598
1557
|
machine
|
|
1599
1558
|
} = state;
|
|
1600
|
-
const predicate = machine?.
|
|
1559
|
+
const predicate = machine?.implementations?.guards?.[guard.type] ?? guard.predicate;
|
|
1601
1560
|
if (!predicate) {
|
|
1602
1561
|
throw new Error(`Guard '${guard.type}' is not implemented.'.`);
|
|
1603
1562
|
}
|
|
@@ -1610,14 +1569,28 @@ function evaluateGuard(guard, context, event, state) {
|
|
|
1610
1569
|
});
|
|
1611
1570
|
}
|
|
1612
1571
|
function toGuardDefinition(guardConfig, getPredicate) {
|
|
1572
|
+
// TODO: check for cycles and consider a refactor to more lazily evaluated guards
|
|
1573
|
+
// TODO: resolve this more recursively: https://github.com/statelyai/xstate/pull/4064#discussion_r1229915724
|
|
1613
1574
|
if (isString(guardConfig)) {
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1575
|
+
const predicateOrDef = getPredicate?.(guardConfig);
|
|
1576
|
+
if (isFunction(predicateOrDef)) {
|
|
1577
|
+
return {
|
|
1578
|
+
type: guardConfig,
|
|
1579
|
+
predicate: predicateOrDef,
|
|
1580
|
+
params: {
|
|
1581
|
+
type: guardConfig
|
|
1582
|
+
}
|
|
1583
|
+
};
|
|
1584
|
+
} else if (predicateOrDef) {
|
|
1585
|
+
return predicateOrDef;
|
|
1586
|
+
} else {
|
|
1587
|
+
return {
|
|
1588
|
+
type: guardConfig,
|
|
1589
|
+
params: {
|
|
1590
|
+
type: guardConfig
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1621
1594
|
}
|
|
1622
1595
|
if (isFunction(guardConfig)) {
|
|
1623
1596
|
return {
|
|
@@ -1629,12 +1602,24 @@ function toGuardDefinition(guardConfig, getPredicate) {
|
|
|
1629
1602
|
}
|
|
1630
1603
|
};
|
|
1631
1604
|
}
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1605
|
+
const predicateOrDef = getPredicate?.(guardConfig.type);
|
|
1606
|
+
if (isFunction(predicateOrDef)) {
|
|
1607
|
+
return {
|
|
1608
|
+
type: guardConfig.type,
|
|
1609
|
+
params: guardConfig.params || guardConfig,
|
|
1610
|
+
children: guardConfig.children?.map(childGuard => toGuardDefinition(childGuard, getPredicate)),
|
|
1611
|
+
predicate: getPredicate?.(guardConfig.type) || guardConfig.predicate
|
|
1612
|
+
};
|
|
1613
|
+
} else if (predicateOrDef) {
|
|
1614
|
+
return predicateOrDef;
|
|
1615
|
+
} else {
|
|
1616
|
+
return {
|
|
1617
|
+
type: guardConfig.type,
|
|
1618
|
+
params: guardConfig.params || guardConfig,
|
|
1619
|
+
children: guardConfig.children?.map(childGuard => toGuardDefinition(childGuard, getPredicate)),
|
|
1620
|
+
predicate: guardConfig.predicate
|
|
1621
|
+
};
|
|
1622
|
+
}
|
|
1638
1623
|
}
|
|
1639
1624
|
|
|
1640
1625
|
function getOutput(configuration, context, event) {
|
|
@@ -1839,7 +1824,7 @@ function formatTransition(stateNode, transitionConfig) {
|
|
|
1839
1824
|
const reenter = transitionConfig.reenter ?? false;
|
|
1840
1825
|
const {
|
|
1841
1826
|
guards
|
|
1842
|
-
} = stateNode.machine.
|
|
1827
|
+
} = stateNode.machine.implementations;
|
|
1843
1828
|
const target = resolveTarget(stateNode, normalizedTarget);
|
|
1844
1829
|
|
|
1845
1830
|
// TODO: should this be part of a lint rule instead?
|
|
@@ -1935,7 +1920,7 @@ function formatInitialTransition(stateNode, _target) {
|
|
|
1935
1920
|
return formatTransition(stateNode, {
|
|
1936
1921
|
target: toArray(_target.target).map(t => {
|
|
1937
1922
|
if (isString(t)) {
|
|
1938
|
-
return isStateId(t) ? t : `${
|
|
1923
|
+
return isStateId(t) ? t : `${STATE_DELIMITER}${t}`;
|
|
1939
1924
|
}
|
|
1940
1925
|
return t;
|
|
1941
1926
|
}),
|
|
@@ -1955,7 +1940,7 @@ function resolveTarget(stateNode, targets) {
|
|
|
1955
1940
|
if (isStateId(target)) {
|
|
1956
1941
|
return stateNode.machine.getStateNodeById(target);
|
|
1957
1942
|
}
|
|
1958
|
-
const isInternalTarget = target[0] ===
|
|
1943
|
+
const isInternalTarget = target[0] === STATE_DELIMITER;
|
|
1959
1944
|
// If internal target is defined on machine,
|
|
1960
1945
|
// do not include machine key on target
|
|
1961
1946
|
if (isInternalTarget && !stateNode.parent) {
|
|
@@ -2038,7 +2023,7 @@ function getStateNodeByPath(stateNode, statePath) {
|
|
|
2038
2023
|
// throw e;
|
|
2039
2024
|
}
|
|
2040
2025
|
}
|
|
2041
|
-
const arrayStatePath = toStatePath(statePath
|
|
2026
|
+
const arrayStatePath = toStatePath(statePath).slice();
|
|
2042
2027
|
let currentStateNode = stateNode;
|
|
2043
2028
|
while (arrayStatePath.length) {
|
|
2044
2029
|
const key = arrayStatePath.shift();
|
|
@@ -2056,7 +2041,7 @@ function getStateNodeByPath(stateNode, statePath) {
|
|
|
2056
2041
|
* @param state The state value or State instance
|
|
2057
2042
|
*/
|
|
2058
2043
|
function getStateNodes(stateNode, state) {
|
|
2059
|
-
const stateValue = state instanceof State ? state.value : toStateValue(state
|
|
2044
|
+
const stateValue = state instanceof State ? state.value : toStateValue(state);
|
|
2060
2045
|
if (isString(stateValue)) {
|
|
2061
2046
|
return [stateNode, stateNode.states[stateValue]];
|
|
2062
2047
|
}
|
|
@@ -2249,40 +2234,18 @@ function computeExitSet(transitions, configuration, historyValue) {
|
|
|
2249
2234
|
* @param mutConfiguration
|
|
2250
2235
|
*/
|
|
2251
2236
|
|
|
2252
|
-
function microstep(transitions, currentState, actorCtx, event) {
|
|
2253
|
-
const {
|
|
2254
|
-
machine
|
|
2255
|
-
} = currentState;
|
|
2256
|
-
// Transition will "apply" if:
|
|
2257
|
-
// - the state node is the initial state (there is no current state)
|
|
2258
|
-
// - OR there are transitions
|
|
2259
|
-
const willTransition = currentState._initial || transitions.length > 0;
|
|
2237
|
+
function microstep(transitions, currentState, actorCtx, event, isInitial) {
|
|
2260
2238
|
const mutConfiguration = new Set(currentState.configuration);
|
|
2261
|
-
if (!
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
target: [...currentState.configuration].filter(isAtomicStateNode),
|
|
2268
|
-
source: machine.root,
|
|
2269
|
-
reenter: true,
|
|
2270
|
-
actions: [],
|
|
2271
|
-
eventType: null,
|
|
2272
|
-
toJSON: null // TODO: fix
|
|
2273
|
-
}] : transitions, currentState, mutConfiguration, event, actorCtx);
|
|
2274
|
-
const {
|
|
2275
|
-
context
|
|
2276
|
-
} = microstate;
|
|
2277
|
-
const nextState = cloneState(microstate, {
|
|
2278
|
-
value: {},
|
|
2279
|
-
// TODO: make optional
|
|
2280
|
-
transitions
|
|
2239
|
+
if (!transitions.length) {
|
|
2240
|
+
return currentState;
|
|
2241
|
+
}
|
|
2242
|
+
const microstate = microstepProcedure(transitions, currentState, mutConfiguration, event, actorCtx, isInitial);
|
|
2243
|
+
return cloneState(microstate, {
|
|
2244
|
+
value: {} // TODO: make optional
|
|
2281
2245
|
});
|
|
2282
|
-
nextState.changed = currentState._initial ? undefined : !stateValuesEqual(nextState.value, currentState.value) || actions.length > 0 || context !== currentState.context;
|
|
2283
|
-
return nextState;
|
|
2284
2246
|
}
|
|
2285
|
-
|
|
2247
|
+
|
|
2248
|
+
function microstepProcedure(transitions, currentState, mutConfiguration, event, actorCtx, isInitial) {
|
|
2286
2249
|
const actions = [];
|
|
2287
2250
|
const historyValue = {
|
|
2288
2251
|
...currentState.historyValue
|
|
@@ -2291,7 +2254,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
|
|
|
2291
2254
|
const internalQueue = [...currentState._internalQueue];
|
|
2292
2255
|
|
|
2293
2256
|
// Exit states
|
|
2294
|
-
if (!
|
|
2257
|
+
if (!isInitial) {
|
|
2295
2258
|
exitStates(filteredTransitions, mutConfiguration, historyValue, actions);
|
|
2296
2259
|
}
|
|
2297
2260
|
|
|
@@ -2299,7 +2262,7 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
|
|
|
2299
2262
|
actions.push(...filteredTransitions.flatMap(t => t.actions));
|
|
2300
2263
|
|
|
2301
2264
|
// Enter states
|
|
2302
|
-
enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue);
|
|
2265
|
+
enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial);
|
|
2303
2266
|
const nextConfiguration = [...mutConfiguration];
|
|
2304
2267
|
const done = isInFinalState(nextConfiguration);
|
|
2305
2268
|
if (done) {
|
|
@@ -2307,10 +2270,10 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
|
|
|
2307
2270
|
actions.push(...finalActions);
|
|
2308
2271
|
}
|
|
2309
2272
|
try {
|
|
2310
|
-
const
|
|
2273
|
+
const nextState = resolveActionsAndContext(actions, event, currentState, actorCtx);
|
|
2311
2274
|
const output = done ? getOutput(nextConfiguration, nextState.context, event) : undefined;
|
|
2312
2275
|
internalQueue.push(...nextState._internalQueue);
|
|
2313
|
-
return
|
|
2276
|
+
return cloneState(currentState, {
|
|
2314
2277
|
configuration: nextConfiguration,
|
|
2315
2278
|
historyValue,
|
|
2316
2279
|
_internalQueue: internalQueue,
|
|
@@ -2318,20 +2281,20 @@ function microstepProcedure(transitions, currentState, mutConfiguration, event,
|
|
|
2318
2281
|
done,
|
|
2319
2282
|
output,
|
|
2320
2283
|
children: nextState.children
|
|
2321
|
-
})
|
|
2284
|
+
});
|
|
2322
2285
|
} catch (e) {
|
|
2323
2286
|
// TODO: Refactor this once proper error handling is implemented.
|
|
2324
2287
|
// See https://github.com/statelyai/rfcs/pull/4
|
|
2325
2288
|
throw e;
|
|
2326
2289
|
}
|
|
2327
2290
|
}
|
|
2328
|
-
function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue) {
|
|
2291
|
+
function enterStates(event, filteredTransitions, mutConfiguration, actions, internalQueue, currentState, historyValue, isInitial) {
|
|
2329
2292
|
const statesToEnter = new Set();
|
|
2330
2293
|
const statesForDefaultEntry = new Set();
|
|
2331
2294
|
computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
|
|
2332
2295
|
|
|
2333
2296
|
// In the initial state, the root state node is "entered".
|
|
2334
|
-
if (
|
|
2297
|
+
if (isInitial) {
|
|
2335
2298
|
statesForDefaultEntry.add(currentState.machine.root);
|
|
2336
2299
|
}
|
|
2337
2300
|
for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
|
|
@@ -2456,7 +2419,7 @@ function exitStates(transitions, mutConfiguration, historyValue, actions) {
|
|
|
2456
2419
|
}
|
|
2457
2420
|
}
|
|
2458
2421
|
for (const s of statesToExit) {
|
|
2459
|
-
actions.push(...s.exit
|
|
2422
|
+
actions.push(...s.exit, ...s.invoke.map(def => stop(def.id)));
|
|
2460
2423
|
mutConfiguration.delete(s);
|
|
2461
2424
|
}
|
|
2462
2425
|
}
|
|
@@ -2464,11 +2427,9 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
|
|
|
2464
2427
|
const {
|
|
2465
2428
|
machine
|
|
2466
2429
|
} = currentState;
|
|
2467
|
-
const resolvedActions = [];
|
|
2468
2430
|
const raiseActions = [];
|
|
2469
2431
|
let intermediateState = currentState;
|
|
2470
2432
|
function handleAction(action) {
|
|
2471
|
-
resolvedActions.push(action);
|
|
2472
2433
|
if (actorCtx?.self.status === ActorStatus.Running) {
|
|
2473
2434
|
action.execute?.(actorCtx);
|
|
2474
2435
|
} else {
|
|
@@ -2476,7 +2437,7 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
|
|
|
2476
2437
|
}
|
|
2477
2438
|
}
|
|
2478
2439
|
function resolveAction(actionObject) {
|
|
2479
|
-
const executableActionObject = resolveActionObject(actionObject, machine.
|
|
2440
|
+
const executableActionObject = resolveActionObject(actionObject, machine.implementations.actions);
|
|
2480
2441
|
if (isDynamicAction(executableActionObject)) {
|
|
2481
2442
|
const [nextState, resolvedAction] = executableActionObject.resolve(event, {
|
|
2482
2443
|
state: intermediateState,
|
|
@@ -2485,7 +2446,7 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
|
|
|
2485
2446
|
});
|
|
2486
2447
|
const matchedActions = resolvedAction.params?.actions;
|
|
2487
2448
|
intermediateState = nextState;
|
|
2488
|
-
if (
|
|
2449
|
+
if (resolvedAction.type === raise$1 && typeof resolvedAction.params.delay !== 'number') {
|
|
2489
2450
|
raiseActions.push(resolvedAction);
|
|
2490
2451
|
}
|
|
2491
2452
|
|
|
@@ -2501,9 +2462,9 @@ function resolveActionsAndContext(actions, event, currentState, actorCtx) {
|
|
|
2501
2462
|
for (const actionObject of actions) {
|
|
2502
2463
|
resolveAction(actionObject);
|
|
2503
2464
|
}
|
|
2504
|
-
return
|
|
2465
|
+
return cloneState(intermediateState, {
|
|
2505
2466
|
_internalQueue: raiseActions.map(a => a.params.event)
|
|
2506
|
-
})
|
|
2467
|
+
});
|
|
2507
2468
|
}
|
|
2508
2469
|
function macrostep(state, event, actorCtx) {
|
|
2509
2470
|
if (event.type === WILDCARD) {
|
|
@@ -2514,7 +2475,7 @@ function macrostep(state, event, actorCtx) {
|
|
|
2514
2475
|
|
|
2515
2476
|
// Handle stop event
|
|
2516
2477
|
if (event.type === stopSignalType) {
|
|
2517
|
-
nextState = stopStep(event, nextState, actorCtx)
|
|
2478
|
+
nextState = stopStep(event, nextState, actorCtx);
|
|
2518
2479
|
states.push(nextState);
|
|
2519
2480
|
return {
|
|
2520
2481
|
state: nextState,
|
|
@@ -2527,7 +2488,7 @@ function macrostep(state, event, actorCtx) {
|
|
|
2527
2488
|
// Determine the next state based on the next microstep
|
|
2528
2489
|
if (nextEvent.type !== init) {
|
|
2529
2490
|
const transitions = selectTransitions(nextEvent, nextState);
|
|
2530
|
-
nextState = microstep(transitions, state, actorCtx, nextEvent);
|
|
2491
|
+
nextState = microstep(transitions, state, actorCtx, nextEvent, false);
|
|
2531
2492
|
states.push(nextState);
|
|
2532
2493
|
}
|
|
2533
2494
|
while (!nextState.done) {
|
|
@@ -2538,12 +2499,12 @@ function macrostep(state, event, actorCtx) {
|
|
|
2538
2499
|
} else {
|
|
2539
2500
|
nextEvent = nextState._internalQueue[0];
|
|
2540
2501
|
const transitions = selectTransitions(nextEvent, nextState);
|
|
2541
|
-
nextState = microstep(transitions, nextState, actorCtx, nextEvent);
|
|
2502
|
+
nextState = microstep(transitions, nextState, actorCtx, nextEvent, false);
|
|
2542
2503
|
nextState._internalQueue.shift();
|
|
2543
2504
|
states.push(nextState);
|
|
2544
2505
|
}
|
|
2545
2506
|
} else {
|
|
2546
|
-
nextState = microstep(enabledTransitions, nextState, actorCtx, nextEvent);
|
|
2507
|
+
nextState = microstep(enabledTransitions, nextState, actorCtx, nextEvent, false);
|
|
2547
2508
|
states.push(nextState);
|
|
2548
2509
|
}
|
|
2549
2510
|
}
|
|
@@ -2597,20 +2558,6 @@ function resolveStateValue(rootNode, stateValue) {
|
|
|
2597
2558
|
const configuration = getConfiguration(getStateNodes(rootNode, stateValue));
|
|
2598
2559
|
return getStateValue(rootNode, [...configuration]);
|
|
2599
2560
|
}
|
|
2600
|
-
function stateValuesEqual(a, b) {
|
|
2601
|
-
if (a === b) {
|
|
2602
|
-
return true;
|
|
2603
|
-
}
|
|
2604
|
-
if (a === undefined || b === undefined) {
|
|
2605
|
-
return false;
|
|
2606
|
-
}
|
|
2607
|
-
if (isString(a) || isString(b)) {
|
|
2608
|
-
return a === b;
|
|
2609
|
-
}
|
|
2610
|
-
const aKeys = Object.keys(a);
|
|
2611
|
-
const bKeys = Object.keys(b);
|
|
2612
|
-
return aKeys.length === bKeys.length && aKeys.every(key => stateValuesEqual(a[key], b[key]));
|
|
2613
|
-
}
|
|
2614
2561
|
function getInitialConfiguration(rootNode) {
|
|
2615
2562
|
const configuration = [];
|
|
2616
2563
|
const initialTransition = rootNode.initial;
|
|
@@ -2633,15 +2580,6 @@ class State {
|
|
|
2633
2580
|
*/
|
|
2634
2581
|
// TODO: add an explicit type for `output`
|
|
2635
2582
|
|
|
2636
|
-
/**
|
|
2637
|
-
* Indicates whether the state has changed from the previous state. A state is considered "changed" if:
|
|
2638
|
-
*
|
|
2639
|
-
* - Its value is not equal to its previous value, or:
|
|
2640
|
-
* - It has any new actions (side-effects) to execute.
|
|
2641
|
-
*
|
|
2642
|
-
* An initial state (with no history) will return `undefined`.
|
|
2643
|
-
*/
|
|
2644
|
-
|
|
2645
2583
|
/**
|
|
2646
2584
|
* The enabled state nodes representative of the state value.
|
|
2647
2585
|
*/
|
|
@@ -2664,7 +2602,6 @@ class State {
|
|
|
2664
2602
|
meta: {},
|
|
2665
2603
|
configuration: [],
|
|
2666
2604
|
// TODO: fix,
|
|
2667
|
-
transitions: [],
|
|
2668
2605
|
children: {}
|
|
2669
2606
|
}, machine);
|
|
2670
2607
|
}
|
|
@@ -2694,8 +2631,6 @@ class State {
|
|
|
2694
2631
|
this.context = void 0;
|
|
2695
2632
|
this.historyValue = {};
|
|
2696
2633
|
this._internalQueue = void 0;
|
|
2697
|
-
this._initial = false;
|
|
2698
|
-
this.changed = void 0;
|
|
2699
2634
|
this.configuration = void 0;
|
|
2700
2635
|
this.children = void 0;
|
|
2701
2636
|
this.context = config.context;
|
|
@@ -2716,12 +2651,12 @@ class State {
|
|
|
2716
2651
|
* @param stateValue
|
|
2717
2652
|
* @param delimiter The character(s) that separate each subpath in the string state node path.
|
|
2718
2653
|
*/
|
|
2719
|
-
toStrings(stateValue = this.value
|
|
2654
|
+
toStrings(stateValue = this.value) {
|
|
2720
2655
|
if (isString(stateValue)) {
|
|
2721
2656
|
return [stateValue];
|
|
2722
2657
|
}
|
|
2723
2658
|
const valueKeys = Object.keys(stateValue);
|
|
2724
|
-
return valueKeys.concat(...valueKeys.map(key => this.toStrings(stateValue[key]
|
|
2659
|
+
return valueKeys.concat(...valueKeys.map(key => this.toStrings(stateValue[key]).map(s => key + STATE_DELIMITER + s)));
|
|
2725
2660
|
}
|
|
2726
2661
|
toJSON() {
|
|
2727
2662
|
const {
|
|
@@ -2829,11 +2764,14 @@ function stop(actorRef) {
|
|
|
2829
2764
|
actor
|
|
2830
2765
|
}
|
|
2831
2766
|
}, (event, {
|
|
2832
|
-
state
|
|
2767
|
+
state,
|
|
2768
|
+
actorContext
|
|
2833
2769
|
}) => {
|
|
2834
2770
|
const actorRefOrString = isFunction(actor) ? actor({
|
|
2835
2771
|
context: state.context,
|
|
2836
|
-
event
|
|
2772
|
+
event,
|
|
2773
|
+
self: actorContext?.self ?? {},
|
|
2774
|
+
system: actorContext?.system
|
|
2837
2775
|
}) : actor;
|
|
2838
2776
|
const actorRef = typeof actorRefOrString === 'string' ? state.children[actorRefOrString] : actorRefOrString;
|
|
2839
2777
|
let children = state.children;
|
|
@@ -2917,61 +2855,60 @@ function log(expr = defaultLogExpr, label) {
|
|
|
2917
2855
|
});
|
|
2918
2856
|
}
|
|
2919
2857
|
|
|
2920
|
-
function createSpawner(
|
|
2921
|
-
|
|
2858
|
+
function createSpawner(actorContext, {
|
|
2859
|
+
machine,
|
|
2860
|
+
context
|
|
2861
|
+
}, event, spawnedChildren) {
|
|
2862
|
+
const spawn = (src, options = {}) => {
|
|
2922
2863
|
const {
|
|
2923
2864
|
systemId
|
|
2924
2865
|
} = options;
|
|
2925
|
-
if (
|
|
2926
|
-
const referenced = resolveReferencedActor(machine.
|
|
2927
|
-
if (referenced) {
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
})
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
// TODO
|
|
2946
|
-
ref: actorRef,
|
|
2947
|
-
meta: undefined,
|
|
2948
|
-
input,
|
|
2949
|
-
systemId
|
|
2950
|
-
}));
|
|
2951
|
-
return actorRef; // TODO: fix types
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
throw new Error(`Actor logic '${src}' not implemented in machine '${machine.id}'`);
|
|
2866
|
+
if (typeof src === 'string') {
|
|
2867
|
+
const referenced = resolveReferencedActor(machine.implementations.actors[src]);
|
|
2868
|
+
if (!referenced) {
|
|
2869
|
+
throw new Error(`Actor logic '${src}' not implemented in machine '${machine.id}'`);
|
|
2870
|
+
}
|
|
2871
|
+
const input = 'input' in options ? options.input : referenced.input;
|
|
2872
|
+
|
|
2873
|
+
// TODO: this should also receive `src`
|
|
2874
|
+
const actor = interpret(referenced.src, {
|
|
2875
|
+
id: options.id,
|
|
2876
|
+
parent: actorContext.self,
|
|
2877
|
+
input: typeof input === 'function' ? input({
|
|
2878
|
+
context,
|
|
2879
|
+
event,
|
|
2880
|
+
self: actorContext.self
|
|
2881
|
+
}) : input,
|
|
2882
|
+
systemId
|
|
2883
|
+
});
|
|
2884
|
+
spawnedChildren[actor.id] = actor;
|
|
2885
|
+
return actor;
|
|
2955
2886
|
} else {
|
|
2956
2887
|
// TODO: this should also receive `src`
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
parent: self,
|
|
2888
|
+
return interpret(src, {
|
|
2889
|
+
id: options.id,
|
|
2890
|
+
parent: actorContext.self,
|
|
2961
2891
|
input: options.input,
|
|
2962
2892
|
systemId
|
|
2963
2893
|
});
|
|
2964
|
-
mutCapturedActions.push(invoke({
|
|
2965
|
-
// @ts-ignore TODO: fix types
|
|
2966
|
-
src: actorRef,
|
|
2967
|
-
ref: actorRef,
|
|
2968
|
-
id: actorRef.id,
|
|
2969
|
-
meta: undefined,
|
|
2970
|
-
input: options.input
|
|
2971
|
-
}));
|
|
2972
|
-
return actorRef; // TODO: fix types
|
|
2973
2894
|
}
|
|
2974
2895
|
};
|
|
2896
|
+
return (src, options) => {
|
|
2897
|
+
const actorRef = spawn(src, options);
|
|
2898
|
+
spawnedChildren[actorRef.id] = actorRef;
|
|
2899
|
+
actorContext.defer(() => {
|
|
2900
|
+
if (actorRef.status === ActorStatus.Stopped) {
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
try {
|
|
2904
|
+
actorRef.start?.();
|
|
2905
|
+
} catch (err) {
|
|
2906
|
+
actorContext.self.send(error(actorRef.id, err));
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
});
|
|
2910
|
+
return actorRef;
|
|
2911
|
+
};
|
|
2975
2912
|
}
|
|
2976
2913
|
|
|
2977
2914
|
/**
|
|
@@ -2990,15 +2927,15 @@ function assign(assignment) {
|
|
|
2990
2927
|
action,
|
|
2991
2928
|
actorContext
|
|
2992
2929
|
}) => {
|
|
2993
|
-
const capturedActions = [];
|
|
2994
2930
|
if (!state.context) {
|
|
2995
2931
|
throw new Error('Cannot assign to undefined `context`. Ensure that `context` is defined in the machine config.');
|
|
2996
2932
|
}
|
|
2933
|
+
const spawnedChildren = {};
|
|
2997
2934
|
const args = {
|
|
2998
2935
|
context: state.context,
|
|
2999
2936
|
event,
|
|
3000
2937
|
action,
|
|
3001
|
-
spawn: createSpawner(actorContext
|
|
2938
|
+
spawn: createSpawner(actorContext, state, event, spawnedChildren),
|
|
3002
2939
|
self: actorContext?.self ?? {},
|
|
3003
2940
|
system: actorContext?.system
|
|
3004
2941
|
};
|
|
@@ -3013,12 +2950,15 @@ function assign(assignment) {
|
|
|
3013
2950
|
}
|
|
3014
2951
|
const updatedContext = Object.assign({}, state.context, partialUpdate);
|
|
3015
2952
|
return [cloneState(state, {
|
|
3016
|
-
context: updatedContext
|
|
2953
|
+
context: updatedContext,
|
|
2954
|
+
children: Object.keys(spawnedChildren).length ? {
|
|
2955
|
+
...state.children,
|
|
2956
|
+
...spawnedChildren
|
|
2957
|
+
} : state.children
|
|
3017
2958
|
}), {
|
|
3018
2959
|
type: assign$1,
|
|
3019
2960
|
params: {
|
|
3020
|
-
context: updatedContext
|
|
3021
|
-
actions: capturedActions
|
|
2961
|
+
context: updatedContext
|
|
3022
2962
|
}
|
|
3023
2963
|
}];
|
|
3024
2964
|
});
|
|
@@ -3054,7 +2994,7 @@ function raise(eventOrExpr, options) {
|
|
|
3054
2994
|
self: actorContext?.self ?? {},
|
|
3055
2995
|
system: actorContext?.system
|
|
3056
2996
|
};
|
|
3057
|
-
const delaysMap = state.machine.
|
|
2997
|
+
const delaysMap = state.machine.implementations.delays;
|
|
3058
2998
|
|
|
3059
2999
|
// TODO: helper function for resolving Expr
|
|
3060
3000
|
if (typeof eventOrExpr === 'string') {
|
|
@@ -3096,7 +3036,7 @@ function choose(guards) {
|
|
|
3096
3036
|
state
|
|
3097
3037
|
}) => {
|
|
3098
3038
|
const matchedActions = guards.find(condition => {
|
|
3099
|
-
const guard = condition.guard && toGuardDefinition(condition.guard, guardType => state.machine.
|
|
3039
|
+
const guard = condition.guard && toGuardDefinition(condition.guard, guardType => state.machine.implementations.guards[guardType]);
|
|
3100
3040
|
return !guard || evaluateGuard(guard, state.context, event, state);
|
|
3101
3041
|
})?.actions;
|
|
3102
3042
|
return [state, {
|
|
@@ -3129,9 +3069,6 @@ function pure(getActions) {
|
|
|
3129
3069
|
});
|
|
3130
3070
|
}
|
|
3131
3071
|
|
|
3132
|
-
const initEvent = {
|
|
3133
|
-
type: init
|
|
3134
|
-
};
|
|
3135
3072
|
function resolveActionObject(actionObject, actionFunctionMap) {
|
|
3136
3073
|
if (isDynamicAction(actionObject)) {
|
|
3137
3074
|
return actionObject;
|
|
@@ -3292,7 +3229,6 @@ exports.choose = choose;
|
|
|
3292
3229
|
exports.createEmptyActor = createEmptyActor;
|
|
3293
3230
|
exports.createInitEvent = createInitEvent;
|
|
3294
3231
|
exports.createInvokeId = createInvokeId;
|
|
3295
|
-
exports.createSpawner = createSpawner;
|
|
3296
3232
|
exports.done = done;
|
|
3297
3233
|
exports.doneInvoke = doneInvoke;
|
|
3298
3234
|
exports.error = error;
|
|
@@ -3315,10 +3251,10 @@ exports.getInitialConfiguration = getInitialConfiguration;
|
|
|
3315
3251
|
exports.getPersistedState = getPersistedState;
|
|
3316
3252
|
exports.getStateNodeByPath = getStateNodeByPath;
|
|
3317
3253
|
exports.getStateNodes = getStateNodes;
|
|
3318
|
-
exports.initEvent = initEvent;
|
|
3319
3254
|
exports.interpret = interpret;
|
|
3320
3255
|
exports.invoke = invoke$1;
|
|
3321
3256
|
exports.isActorRef = isActorRef;
|
|
3257
|
+
exports.isAtomicStateNode = isAtomicStateNode;
|
|
3322
3258
|
exports.isErrorEvent = isErrorEvent;
|
|
3323
3259
|
exports.isInFinalState = isInFinalState;
|
|
3324
3260
|
exports.isSignal = isSignal;
|
|
@@ -3339,7 +3275,6 @@ exports.resolveActionObject = resolveActionObject;
|
|
|
3339
3275
|
exports.resolveActionsAndContext = resolveActionsAndContext;
|
|
3340
3276
|
exports.resolveReferencedActor = resolveReferencedActor;
|
|
3341
3277
|
exports.resolveStateValue = resolveStateValue;
|
|
3342
|
-
exports.send = send;
|
|
3343
3278
|
exports.sendParent = sendParent;
|
|
3344
3279
|
exports.sendTo = sendTo;
|
|
3345
3280
|
exports.startSignal = startSignal;
|