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