xstate 5.0.0-beta.9 → 5.0.0
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/README.md +10 -8
- package/actions/dist/xstate-actions.cjs.js +19 -5
- package/actions/dist/xstate-actions.cjs.mjs +3 -15
- package/actions/dist/xstate-actions.development.cjs.js +21 -0
- package/actions/dist/xstate-actions.development.cjs.mjs +13 -0
- package/actions/dist/xstate-actions.development.esm.js +3 -0
- package/actions/dist/xstate-actions.esm.js +3 -2
- 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 +621 -4
- package/actors/dist/xstate-actors.cjs.mjs +1 -8
- package/actors/dist/xstate-actors.development.cjs.js +624 -0
- package/actors/dist/xstate-actors.development.cjs.mjs +8 -0
- package/actors/dist/xstate-actors.development.esm.js +615 -0
- package/actors/dist/xstate-actors.esm.js +615 -2
- package/actors/dist/xstate-actors.umd.min.js +1 -1
- package/actors/dist/xstate-actors.umd.min.js.map +1 -1
- package/dev/dist/xstate-dev.cjs.js +45 -4
- package/{dist/index-0f3fdf0c.cjs.prod.js → dev/dist/xstate-dev.development.cjs.js} +6 -7
- package/dev/dist/xstate-dev.development.cjs.mjs +5 -0
- package/{dist/index-50bd0aff.esm.js → dev/dist/xstate-dev.development.esm.js} +6 -8
- package/dev/dist/xstate-dev.esm.js +42 -1
- package/dev/dist/xstate-dev.umd.min.js +1 -1
- package/dev/dist/xstate-dev.umd.min.js.map +1 -1
- package/dist/declarations/src/SimulatedClock.d.ts +1 -1
- package/dist/declarations/src/State.d.ts +47 -73
- package/dist/declarations/src/StateMachine.d.ts +29 -57
- package/dist/declarations/src/StateNode.d.ts +36 -33
- package/dist/declarations/src/actions/assign.d.ts +11 -2
- package/dist/declarations/src/actions/cancel.d.ts +7 -3
- package/dist/declarations/src/actions/enqueueActions.d.ts +32 -0
- package/dist/declarations/src/actions/log.d.ts +7 -3
- package/dist/declarations/src/actions/raise.d.ts +7 -2
- package/dist/declarations/src/actions/send.d.ts +14 -36
- package/dist/declarations/src/actions/spawnChild.d.ts +29 -0
- package/dist/declarations/src/actions/stopChild.d.ts +18 -0
- package/dist/declarations/src/actions.d.ts +8 -48
- package/dist/declarations/src/actors/callback.d.ts +91 -8
- package/dist/declarations/src/actors/index.d.ts +6 -28
- package/dist/declarations/src/actors/observable.d.ts +101 -18
- package/dist/declarations/src/actors/promise.d.ts +80 -10
- package/dist/declarations/src/actors/transition.d.ts +64 -9
- package/dist/declarations/src/constants.d.ts +3 -0
- package/dist/declarations/src/createMachine.d.ts +20 -0
- package/dist/declarations/src/dev/index.d.ts +6 -6
- package/dist/declarations/src/guards.d.ts +41 -8
- package/dist/declarations/src/index.d.ts +18 -23
- package/dist/declarations/src/interpreter.d.ts +149 -41
- package/dist/declarations/src/setup.d.ts +51 -0
- package/dist/declarations/src/spawn.d.ts +23 -2
- package/dist/declarations/src/stateUtils.d.ts +30 -45
- package/dist/declarations/src/system.d.ts +26 -2
- package/dist/declarations/src/typegenTypes.d.ts +34 -22
- package/dist/declarations/src/types.d.ts +527 -669
- package/dist/declarations/src/utils.d.ts +15 -52
- package/dist/declarations/src/waitFor.d.ts +2 -2
- package/dist/log-22e678c5.esm.js +364 -0
- package/dist/log-5e226275.cjs.js +372 -0
- package/dist/log-641cd926.development.cjs.js +394 -0
- package/dist/log-f196f85f.development.esm.js +386 -0
- package/dist/raise-34e25c2c.cjs.js +2368 -0
- package/dist/raise-62704519.development.cjs.js +2422 -0
- package/dist/raise-89c581c4.development.esm.js +2371 -0
- package/dist/raise-8bc422d1.esm.js +2317 -0
- package/dist/xstate.cjs.js +728 -4
- package/dist/xstate.cjs.mjs +9 -9
- package/dist/xstate.development.cjs.js +737 -0
- package/dist/xstate.development.cjs.mjs +39 -0
- package/dist/xstate.development.esm.js +699 -0
- package/dist/xstate.esm.js +561 -770
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/guards/dist/xstate-guards.cjs.js +12 -5
- package/guards/dist/xstate-guards.cjs.mjs +1 -2
- package/guards/dist/xstate-guards.development.cjs.js +14 -0
- package/guards/dist/xstate-guards.development.cjs.mjs +7 -0
- package/guards/dist/xstate-guards.development.esm.js +2 -0
- package/guards/dist/xstate-guards.esm.js +2 -2
- package/guards/dist/xstate-guards.umd.min.js +1 -1
- package/guards/dist/xstate-guards.umd.min.js.map +1 -1
- package/package.json +53 -1
- package/actions/dist/xstate-actions.cjs.dev.js +0 -32
- package/actions/dist/xstate-actions.cjs.prod.js +0 -32
- package/actions/dynamicAction.ts +0 -42
- package/actors/dist/xstate-actors.cjs.dev.js +0 -22
- package/actors/dist/xstate-actors.cjs.prod.js +0 -22
- package/dev/dist/xstate-dev.cjs.dev.js +0 -11
- package/dev/dist/xstate-dev.cjs.prod.js +0 -11
- package/dist/actions-b6357569.cjs.dev.js +0 -4437
- package/dist/actions-bd4a184d.cjs.prod.js +0 -4423
- package/dist/actions-de434a04.esm.js +0 -4348
- package/dist/declarations/actions/dynamicAction.d.ts +0 -5
- package/dist/declarations/src/Machine.d.ts +0 -4
- package/dist/declarations/src/Mailbox.d.ts +0 -12
- package/dist/declarations/src/actionTypes.d.ts +0 -16
- package/dist/declarations/src/actions/choose.d.ts +0 -3
- package/dist/declarations/src/actions/invoke.d.ts +0 -3
- package/dist/declarations/src/actions/pure.d.ts +0 -6
- package/dist/declarations/src/actions/stop.d.ts +0 -7
- package/dist/declarations/src/environment.d.ts +0 -1
- package/dist/declarations/src/mapState.d.ts +0 -3
- package/dist/declarations/src/memo.d.ts +0 -2
- package/dist/index-ebaab3c9.cjs.dev.js +0 -52
- package/dist/xstate.cjs.dev.js +0 -950
- package/dist/xstate.cjs.prod.js +0 -947
- package/guards/dist/xstate-guards.cjs.dev.js +0 -15
- package/guards/dist/xstate-guards.cjs.prod.js +0 -15
|
@@ -0,0 +1,2368 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var dev_dist_xstateDev = require('../dev/dist/xstate-dev.cjs.js');
|
|
4
|
+
|
|
5
|
+
class Mailbox {
|
|
6
|
+
constructor(_process) {
|
|
7
|
+
this._process = _process;
|
|
8
|
+
this._active = false;
|
|
9
|
+
this._current = null;
|
|
10
|
+
this._last = null;
|
|
11
|
+
}
|
|
12
|
+
start() {
|
|
13
|
+
this._active = true;
|
|
14
|
+
this.flush();
|
|
15
|
+
}
|
|
16
|
+
clear() {
|
|
17
|
+
// we can't set _current to null because we might be currently processing
|
|
18
|
+
// and enqueue following clear shouldnt start processing the enqueued item immediately
|
|
19
|
+
if (this._current) {
|
|
20
|
+
this._current.next = null;
|
|
21
|
+
this._last = this._current;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
enqueue(event) {
|
|
25
|
+
const enqueued = {
|
|
26
|
+
value: event,
|
|
27
|
+
next: null
|
|
28
|
+
};
|
|
29
|
+
if (this._current) {
|
|
30
|
+
this._last.next = enqueued;
|
|
31
|
+
this._last = enqueued;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this._current = enqueued;
|
|
35
|
+
this._last = enqueued;
|
|
36
|
+
if (this._active) {
|
|
37
|
+
this.flush();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
flush() {
|
|
41
|
+
while (this._current) {
|
|
42
|
+
// atm the given _process is responsible for implementing proper try/catch handling
|
|
43
|
+
// we assume here that this won't throw in a way that can affect this mailbox
|
|
44
|
+
const consumed = this._current;
|
|
45
|
+
this._process(consumed.value);
|
|
46
|
+
this._current = consumed.next;
|
|
47
|
+
}
|
|
48
|
+
this._last = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const STATE_DELIMITER = '.';
|
|
53
|
+
const TARGETLESS_KEY = '';
|
|
54
|
+
const NULL_EVENT = '';
|
|
55
|
+
const STATE_IDENTIFIER = '#';
|
|
56
|
+
const WILDCARD = '*';
|
|
57
|
+
const XSTATE_INIT = 'xstate.init';
|
|
58
|
+
const XSTATE_ERROR = 'xstate.error';
|
|
59
|
+
const XSTATE_STOP = 'xstate.stop';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns an event that represents an implicit event that
|
|
63
|
+
* is sent after the specified `delay`.
|
|
64
|
+
*
|
|
65
|
+
* @param delayRef The delay in milliseconds
|
|
66
|
+
* @param id The state node ID where this event is handled
|
|
67
|
+
*/
|
|
68
|
+
function createAfterEvent(delayRef, id) {
|
|
69
|
+
return {
|
|
70
|
+
type: `xstate.after.${delayRef}.${id}`
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Returns an event that represents that a final state node
|
|
76
|
+
* has been reached in the parent state node.
|
|
77
|
+
*
|
|
78
|
+
* @param id The final state node's parent state node `id`
|
|
79
|
+
* @param output The data to pass into the event
|
|
80
|
+
*/
|
|
81
|
+
function createDoneStateEvent(id, output) {
|
|
82
|
+
return {
|
|
83
|
+
type: `xstate.done.state.${id}`,
|
|
84
|
+
output
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Returns an event that represents that an invoked service has terminated.
|
|
90
|
+
*
|
|
91
|
+
* An invoked service is terminated when it has reached a top-level final state node,
|
|
92
|
+
* but not when it is canceled.
|
|
93
|
+
*
|
|
94
|
+
* @param invokeId The invoked service ID
|
|
95
|
+
* @param output The data to pass into the event
|
|
96
|
+
*/
|
|
97
|
+
function createDoneActorEvent(invokeId, output) {
|
|
98
|
+
return {
|
|
99
|
+
type: `xstate.done.actor.${invokeId}`,
|
|
100
|
+
output
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function createErrorActorEvent(id, error) {
|
|
104
|
+
return {
|
|
105
|
+
type: `xstate.error.actor.${id}`,
|
|
106
|
+
error
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function createInitEvent(input) {
|
|
110
|
+
return {
|
|
111
|
+
type: XSTATE_INIT,
|
|
112
|
+
input
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* This function makes sure that unhandled errors are thrown in a separate macrotask.
|
|
118
|
+
* It allows those errors to be detected by global error handlers and reported to bug tracking services
|
|
119
|
+
* without interrupting our own stack of execution.
|
|
120
|
+
*
|
|
121
|
+
* @param err error to be thrown
|
|
122
|
+
*/
|
|
123
|
+
function reportUnhandledError(err) {
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
throw err;
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
|
|
130
|
+
|
|
131
|
+
let idCounter = 0;
|
|
132
|
+
function createSystem(rootActor) {
|
|
133
|
+
const children = new Map();
|
|
134
|
+
const keyedActors = new Map();
|
|
135
|
+
const reverseKeyedActors = new WeakMap();
|
|
136
|
+
const observers = new Set();
|
|
137
|
+
const system = {
|
|
138
|
+
_bookId: () => `x:${idCounter++}`,
|
|
139
|
+
_register: (sessionId, actorRef) => {
|
|
140
|
+
children.set(sessionId, actorRef);
|
|
141
|
+
return sessionId;
|
|
142
|
+
},
|
|
143
|
+
_unregister: actorRef => {
|
|
144
|
+
children.delete(actorRef.sessionId);
|
|
145
|
+
const systemId = reverseKeyedActors.get(actorRef);
|
|
146
|
+
if (systemId !== undefined) {
|
|
147
|
+
keyedActors.delete(systemId);
|
|
148
|
+
reverseKeyedActors.delete(actorRef);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
get: systemId => {
|
|
152
|
+
return keyedActors.get(systemId);
|
|
153
|
+
},
|
|
154
|
+
_set: (systemId, actorRef) => {
|
|
155
|
+
const existing = keyedActors.get(systemId);
|
|
156
|
+
if (existing && existing !== actorRef) {
|
|
157
|
+
throw new Error(`Actor with system ID '${systemId}' already exists.`);
|
|
158
|
+
}
|
|
159
|
+
keyedActors.set(systemId, actorRef);
|
|
160
|
+
reverseKeyedActors.set(actorRef, systemId);
|
|
161
|
+
},
|
|
162
|
+
inspect: observer => {
|
|
163
|
+
observers.add(observer);
|
|
164
|
+
},
|
|
165
|
+
_sendInspectionEvent: event => {
|
|
166
|
+
const resolvedInspectionEvent = {
|
|
167
|
+
...event,
|
|
168
|
+
rootId: rootActor.sessionId
|
|
169
|
+
};
|
|
170
|
+
observers.forEach(observer => observer.next?.(resolvedInspectionEvent));
|
|
171
|
+
},
|
|
172
|
+
_relay: (source, target, event) => {
|
|
173
|
+
system._sendInspectionEvent({
|
|
174
|
+
type: '@xstate.event',
|
|
175
|
+
sourceRef: source,
|
|
176
|
+
actorRef: target,
|
|
177
|
+
event
|
|
178
|
+
});
|
|
179
|
+
target._send(event);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
return system;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function matchesState(parentStateId, childStateId) {
|
|
186
|
+
const parentStateValue = toStateValue(parentStateId);
|
|
187
|
+
const childStateValue = toStateValue(childStateId);
|
|
188
|
+
if (typeof childStateValue === 'string') {
|
|
189
|
+
if (typeof parentStateValue === 'string') {
|
|
190
|
+
return childStateValue === parentStateValue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Parent more specific than child
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
if (typeof parentStateValue === 'string') {
|
|
197
|
+
return parentStateValue in childStateValue;
|
|
198
|
+
}
|
|
199
|
+
return Object.keys(parentStateValue).every(key => {
|
|
200
|
+
if (!(key in childStateValue)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
return matchesState(parentStateValue[key], childStateValue[key]);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function toStatePath(stateId) {
|
|
207
|
+
if (isArray(stateId)) {
|
|
208
|
+
return stateId;
|
|
209
|
+
}
|
|
210
|
+
return stateId.split(STATE_DELIMITER);
|
|
211
|
+
}
|
|
212
|
+
function toStateValue(stateValue) {
|
|
213
|
+
if (isMachineSnapshot(stateValue)) {
|
|
214
|
+
return stateValue.value;
|
|
215
|
+
}
|
|
216
|
+
if (typeof stateValue !== 'string') {
|
|
217
|
+
return stateValue;
|
|
218
|
+
}
|
|
219
|
+
const statePath = toStatePath(stateValue);
|
|
220
|
+
return pathToStateValue(statePath);
|
|
221
|
+
}
|
|
222
|
+
function pathToStateValue(statePath) {
|
|
223
|
+
if (statePath.length === 1) {
|
|
224
|
+
return statePath[0];
|
|
225
|
+
}
|
|
226
|
+
const value = {};
|
|
227
|
+
let marker = value;
|
|
228
|
+
for (let i = 0; i < statePath.length - 1; i++) {
|
|
229
|
+
if (i === statePath.length - 2) {
|
|
230
|
+
marker[statePath[i]] = statePath[i + 1];
|
|
231
|
+
} else {
|
|
232
|
+
const previous = marker;
|
|
233
|
+
marker = {};
|
|
234
|
+
previous[statePath[i]] = marker;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
239
|
+
function mapValues(collection, iteratee) {
|
|
240
|
+
const result = {};
|
|
241
|
+
const collectionKeys = Object.keys(collection);
|
|
242
|
+
for (let i = 0; i < collectionKeys.length; i++) {
|
|
243
|
+
const key = collectionKeys[i];
|
|
244
|
+
result[key] = iteratee(collection[key], key, collection, i);
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
function toArrayStrict(value) {
|
|
249
|
+
if (isArray(value)) {
|
|
250
|
+
return value;
|
|
251
|
+
}
|
|
252
|
+
return [value];
|
|
253
|
+
}
|
|
254
|
+
function toArray(value) {
|
|
255
|
+
if (value === undefined) {
|
|
256
|
+
return [];
|
|
257
|
+
}
|
|
258
|
+
return toArrayStrict(value);
|
|
259
|
+
}
|
|
260
|
+
function resolveOutput(mapper, context, event, self) {
|
|
261
|
+
if (typeof mapper === 'function') {
|
|
262
|
+
return mapper({
|
|
263
|
+
context,
|
|
264
|
+
event,
|
|
265
|
+
self
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return mapper;
|
|
269
|
+
}
|
|
270
|
+
function isArray(value) {
|
|
271
|
+
return Array.isArray(value);
|
|
272
|
+
}
|
|
273
|
+
function isErrorActorEvent(event) {
|
|
274
|
+
return event.type.startsWith('xstate.error.actor');
|
|
275
|
+
}
|
|
276
|
+
function toTransitionConfigArray(configLike) {
|
|
277
|
+
return toArrayStrict(configLike).map(transitionLike => {
|
|
278
|
+
if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
|
|
279
|
+
return {
|
|
280
|
+
target: transitionLike
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return transitionLike;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
function normalizeTarget(target) {
|
|
287
|
+
if (target === undefined || target === TARGETLESS_KEY) {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
return toArray(target);
|
|
291
|
+
}
|
|
292
|
+
function toObserver(nextHandler, errorHandler, completionHandler) {
|
|
293
|
+
const isObserver = typeof nextHandler === 'object';
|
|
294
|
+
const self = isObserver ? nextHandler : undefined;
|
|
295
|
+
return {
|
|
296
|
+
next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
|
|
297
|
+
error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
|
|
298
|
+
complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function createInvokeId(stateNodeId, index) {
|
|
302
|
+
return `${index}.${stateNodeId}`;
|
|
303
|
+
}
|
|
304
|
+
function resolveReferencedActor(machine, src) {
|
|
305
|
+
const match = src.match(/^xstate\.invoke\.(\d+)\.(.*)/);
|
|
306
|
+
if (!match) {
|
|
307
|
+
return machine.implementations.actors[src];
|
|
308
|
+
}
|
|
309
|
+
const [, indexStr, nodeId] = match;
|
|
310
|
+
const node = machine.getStateNodeById(nodeId);
|
|
311
|
+
const invokeConfig = node.config.invoke;
|
|
312
|
+
return (Array.isArray(invokeConfig) ? invokeConfig[indexStr] : invokeConfig).src;
|
|
313
|
+
}
|
|
314
|
+
function getAllOwnEventDescriptors(snapshot) {
|
|
315
|
+
return [...new Set([...snapshot._nodes.flatMap(sn => sn.ownEvents)])];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const $$ACTOR_TYPE = 1;
|
|
319
|
+
// those values are currently used by @xstate/react directly so it's important to keep the assigned values in sync
|
|
320
|
+
let ProcessingStatus = /*#__PURE__*/function (ProcessingStatus) {
|
|
321
|
+
ProcessingStatus[ProcessingStatus["NotStarted"] = 0] = "NotStarted";
|
|
322
|
+
ProcessingStatus[ProcessingStatus["Running"] = 1] = "Running";
|
|
323
|
+
ProcessingStatus[ProcessingStatus["Stopped"] = 2] = "Stopped";
|
|
324
|
+
return ProcessingStatus;
|
|
325
|
+
}({});
|
|
326
|
+
const defaultOptions = {
|
|
327
|
+
clock: {
|
|
328
|
+
setTimeout: (fn, ms) => {
|
|
329
|
+
return setTimeout(fn, ms);
|
|
330
|
+
},
|
|
331
|
+
clearTimeout: id => {
|
|
332
|
+
return clearTimeout(id);
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
logger: console.log.bind(console),
|
|
336
|
+
devTools: false
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* An Actor is a running process that can receive events, send events and change its behavior based on the events it receives, which can cause effects outside of the actor. When you run a state machine, it becomes an actor.
|
|
341
|
+
*/
|
|
342
|
+
class Actor {
|
|
343
|
+
/**
|
|
344
|
+
* Creates a new actor instance for the given logic with the provided options, if any.
|
|
345
|
+
*
|
|
346
|
+
* @param logic The logic to create an actor from
|
|
347
|
+
* @param options Actor options
|
|
348
|
+
*/
|
|
349
|
+
constructor(logic, options) {
|
|
350
|
+
this.logic = logic;
|
|
351
|
+
/**
|
|
352
|
+
* The current internal state of the actor.
|
|
353
|
+
*/
|
|
354
|
+
this._snapshot = void 0;
|
|
355
|
+
/**
|
|
356
|
+
* The clock that is responsible for setting and clearing timeouts, such as delayed events and transitions.
|
|
357
|
+
*/
|
|
358
|
+
this.clock = void 0;
|
|
359
|
+
this.options = void 0;
|
|
360
|
+
/**
|
|
361
|
+
* The unique identifier for this actor relative to its parent.
|
|
362
|
+
*/
|
|
363
|
+
this.id = void 0;
|
|
364
|
+
this.mailbox = new Mailbox(this._process.bind(this));
|
|
365
|
+
this.delayedEventsMap = {};
|
|
366
|
+
this.observers = new Set();
|
|
367
|
+
this.logger = void 0;
|
|
368
|
+
/** @internal */
|
|
369
|
+
this._processingStatus = ProcessingStatus.NotStarted;
|
|
370
|
+
// Actor Ref
|
|
371
|
+
this._parent = void 0;
|
|
372
|
+
this._syncSnapshot = void 0;
|
|
373
|
+
this.ref = void 0;
|
|
374
|
+
// TODO: add typings for system
|
|
375
|
+
this._actorScope = void 0;
|
|
376
|
+
this._systemId = void 0;
|
|
377
|
+
/**
|
|
378
|
+
* The globally unique process ID for this invocation.
|
|
379
|
+
*/
|
|
380
|
+
this.sessionId = void 0;
|
|
381
|
+
/**
|
|
382
|
+
* The system to which this actor belongs.
|
|
383
|
+
*/
|
|
384
|
+
this.system = void 0;
|
|
385
|
+
this._doneEvent = void 0;
|
|
386
|
+
this.src = void 0;
|
|
387
|
+
// array of functions to defer
|
|
388
|
+
this._deferred = [];
|
|
389
|
+
const resolvedOptions = {
|
|
390
|
+
...defaultOptions,
|
|
391
|
+
...options
|
|
392
|
+
};
|
|
393
|
+
const {
|
|
394
|
+
clock,
|
|
395
|
+
logger,
|
|
396
|
+
parent,
|
|
397
|
+
syncSnapshot,
|
|
398
|
+
id,
|
|
399
|
+
systemId,
|
|
400
|
+
inspect
|
|
401
|
+
} = resolvedOptions;
|
|
402
|
+
this.system = parent?.system ?? createSystem(this);
|
|
403
|
+
if (inspect && !parent) {
|
|
404
|
+
// Always inspect at the system-level
|
|
405
|
+
this.system.inspect(toObserver(inspect));
|
|
406
|
+
}
|
|
407
|
+
this.sessionId = this.system._bookId();
|
|
408
|
+
this.id = id ?? this.sessionId;
|
|
409
|
+
this.logger = logger;
|
|
410
|
+
this.clock = clock;
|
|
411
|
+
this._parent = parent;
|
|
412
|
+
this._syncSnapshot = syncSnapshot;
|
|
413
|
+
this.options = resolvedOptions;
|
|
414
|
+
this.src = resolvedOptions.src ?? logic;
|
|
415
|
+
this.ref = this;
|
|
416
|
+
this._actorScope = {
|
|
417
|
+
self: this,
|
|
418
|
+
id: this.id,
|
|
419
|
+
sessionId: this.sessionId,
|
|
420
|
+
logger: this.logger,
|
|
421
|
+
defer: fn => {
|
|
422
|
+
this._deferred.push(fn);
|
|
423
|
+
},
|
|
424
|
+
system: this.system,
|
|
425
|
+
stopChild: child => {
|
|
426
|
+
if (child._parent !== this) {
|
|
427
|
+
throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
|
|
428
|
+
}
|
|
429
|
+
child._stop();
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// Ensure that the send method is bound to this Actor instance
|
|
434
|
+
// if destructured
|
|
435
|
+
this.send = this.send.bind(this);
|
|
436
|
+
this.system._sendInspectionEvent({
|
|
437
|
+
type: '@xstate.actor',
|
|
438
|
+
actorRef: this
|
|
439
|
+
});
|
|
440
|
+
if (systemId) {
|
|
441
|
+
this._systemId = systemId;
|
|
442
|
+
this.system._set(systemId, this);
|
|
443
|
+
}
|
|
444
|
+
this._initState(options?.snapshot ?? options?.state);
|
|
445
|
+
if (systemId && this._snapshot.status !== 'active') {
|
|
446
|
+
this.system._unregister(this);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
_initState(persistedState) {
|
|
450
|
+
try {
|
|
451
|
+
this._snapshot = persistedState ? this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState : this.logic.getInitialSnapshot(this._actorScope, this.options?.input);
|
|
452
|
+
} catch (err) {
|
|
453
|
+
// if we get here then it means that we assign a value to this._snapshot that is not of the correct type
|
|
454
|
+
// we can't get the true `TSnapshot & { status: 'error'; }`, it's impossible
|
|
455
|
+
// so right now this is a lie of sorts
|
|
456
|
+
this._snapshot = {
|
|
457
|
+
status: 'error',
|
|
458
|
+
output: undefined,
|
|
459
|
+
error: err
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
update(snapshot, event) {
|
|
464
|
+
// Update state
|
|
465
|
+
this._snapshot = snapshot;
|
|
466
|
+
|
|
467
|
+
// Execute deferred effects
|
|
468
|
+
let deferredFn;
|
|
469
|
+
while (deferredFn = this._deferred.shift()) {
|
|
470
|
+
try {
|
|
471
|
+
deferredFn();
|
|
472
|
+
} catch (err) {
|
|
473
|
+
// this error can only be caught when executing *initial* actions
|
|
474
|
+
// it's the only time when we call actions provided by the user through those deferreds
|
|
475
|
+
// when the actor is already running we always execute them synchronously while transitioning
|
|
476
|
+
// no "builtin deferred" should actually throw an error since they are either safe
|
|
477
|
+
// or the control flow is passed through the mailbox and errors should be caught by the `_process` used by the mailbox
|
|
478
|
+
this._deferred.length = 0;
|
|
479
|
+
this._snapshot = {
|
|
480
|
+
...snapshot,
|
|
481
|
+
status: 'error',
|
|
482
|
+
error: err
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
switch (this._snapshot.status) {
|
|
487
|
+
case 'active':
|
|
488
|
+
for (const observer of this.observers) {
|
|
489
|
+
try {
|
|
490
|
+
observer.next?.(snapshot);
|
|
491
|
+
} catch (err) {
|
|
492
|
+
reportUnhandledError(err);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
break;
|
|
496
|
+
case 'done':
|
|
497
|
+
// next observers are meant to be notified about done snapshots
|
|
498
|
+
// this can be seen as something that is different from how observable work
|
|
499
|
+
// but with observables `complete` callback is called without any arguments
|
|
500
|
+
// it's more ergonomic for XState to treat a done snapshot as a "next" value
|
|
501
|
+
// and the completion event as something that is separate,
|
|
502
|
+
// something that merely follows emitting that done snapshot
|
|
503
|
+
for (const observer of this.observers) {
|
|
504
|
+
try {
|
|
505
|
+
observer.next?.(snapshot);
|
|
506
|
+
} catch (err) {
|
|
507
|
+
reportUnhandledError(err);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
this._stopProcedure();
|
|
511
|
+
this._complete();
|
|
512
|
+
this._doneEvent = createDoneActorEvent(this.id, this._snapshot.output);
|
|
513
|
+
if (this._parent) {
|
|
514
|
+
this.system._relay(this, this._parent, this._doneEvent);
|
|
515
|
+
}
|
|
516
|
+
break;
|
|
517
|
+
case 'error':
|
|
518
|
+
this._error(this._snapshot.error);
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
this.system._sendInspectionEvent({
|
|
522
|
+
type: '@xstate.snapshot',
|
|
523
|
+
actorRef: this,
|
|
524
|
+
event,
|
|
525
|
+
snapshot
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Subscribe an observer to an actor’s snapshot values.
|
|
531
|
+
*
|
|
532
|
+
* @remarks
|
|
533
|
+
* The observer will receive the actor’s snapshot value when it is emitted. The observer can be:
|
|
534
|
+
* - A plain function that receives the latest snapshot, or
|
|
535
|
+
* - An observer object whose `.next(snapshot)` method receives the latest snapshot
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```ts
|
|
539
|
+
* // Observer as a plain function
|
|
540
|
+
* const subscription = actor.subscribe((snapshot) => {
|
|
541
|
+
* console.log(snapshot);
|
|
542
|
+
* });
|
|
543
|
+
* ```
|
|
544
|
+
*
|
|
545
|
+
* @example
|
|
546
|
+
* ```ts
|
|
547
|
+
* // Observer as an object
|
|
548
|
+
* const subscription = actor.subscribe({
|
|
549
|
+
* next(snapshot) {
|
|
550
|
+
* console.log(snapshot);
|
|
551
|
+
* },
|
|
552
|
+
* error(err) {
|
|
553
|
+
* // ...
|
|
554
|
+
* },
|
|
555
|
+
* complete() {
|
|
556
|
+
* // ...
|
|
557
|
+
* },
|
|
558
|
+
* });
|
|
559
|
+
* ```
|
|
560
|
+
*
|
|
561
|
+
* The return value of `actor.subscribe(observer)` is a subscription object that has an `.unsubscribe()` method. You can call `subscription.unsubscribe()` to unsubscribe the observer:
|
|
562
|
+
*
|
|
563
|
+
* @example
|
|
564
|
+
* ```ts
|
|
565
|
+
* const subscription = actor.subscribe((snapshot) => {
|
|
566
|
+
* // ...
|
|
567
|
+
* });
|
|
568
|
+
*
|
|
569
|
+
* // Unsubscribe the observer
|
|
570
|
+
* subscription.unsubscribe();
|
|
571
|
+
* ```
|
|
572
|
+
*
|
|
573
|
+
* When the actor is stopped, all of its observers will automatically be unsubscribed.
|
|
574
|
+
*
|
|
575
|
+
* @param observer - Either a plain function that receives the latest snapshot, or an observer object whose `.next(snapshot)` method receives the latest snapshot
|
|
576
|
+
*/
|
|
577
|
+
|
|
578
|
+
subscribe(nextListenerOrObserver, errorListener, completeListener) {
|
|
579
|
+
const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
|
|
580
|
+
if (this._processingStatus !== ProcessingStatus.Stopped) {
|
|
581
|
+
this.observers.add(observer);
|
|
582
|
+
} else {
|
|
583
|
+
try {
|
|
584
|
+
observer.complete?.();
|
|
585
|
+
} catch (err) {
|
|
586
|
+
reportUnhandledError(err);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
unsubscribe: () => {
|
|
591
|
+
this.observers.delete(observer);
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Starts the Actor from the initial state
|
|
598
|
+
*/
|
|
599
|
+
start() {
|
|
600
|
+
if (this._processingStatus === ProcessingStatus.Running) {
|
|
601
|
+
// Do not restart the service if it is already started
|
|
602
|
+
return this;
|
|
603
|
+
}
|
|
604
|
+
if (this._syncSnapshot) {
|
|
605
|
+
this.subscribe({
|
|
606
|
+
next: snapshot => {
|
|
607
|
+
if (snapshot.status === 'active') {
|
|
608
|
+
this.system._relay(this, this._parent, {
|
|
609
|
+
type: `xstate.snapshot.${this.id}`,
|
|
610
|
+
snapshot
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
error: () => {}
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
this.system._register(this.sessionId, this);
|
|
618
|
+
if (this._systemId) {
|
|
619
|
+
this.system._set(this._systemId, this);
|
|
620
|
+
}
|
|
621
|
+
this._processingStatus = ProcessingStatus.Running;
|
|
622
|
+
|
|
623
|
+
// TODO: this isn't correct when rehydrating
|
|
624
|
+
const initEvent = createInitEvent(this.options.input);
|
|
625
|
+
this.system._sendInspectionEvent({
|
|
626
|
+
type: '@xstate.event',
|
|
627
|
+
sourceRef: this._parent,
|
|
628
|
+
actorRef: this,
|
|
629
|
+
event: initEvent
|
|
630
|
+
});
|
|
631
|
+
const status = this._snapshot.status;
|
|
632
|
+
switch (status) {
|
|
633
|
+
case 'done':
|
|
634
|
+
// a state machine can be "done" upon initialization (it could reach a final state using initial microsteps)
|
|
635
|
+
// we still need to complete observers, flush deferreds etc
|
|
636
|
+
this.update(this._snapshot, initEvent);
|
|
637
|
+
// TODO: rethink cleanup of observers, mailbox, etc
|
|
638
|
+
return this;
|
|
639
|
+
case 'error':
|
|
640
|
+
this._error(this._snapshot.error);
|
|
641
|
+
return this;
|
|
642
|
+
}
|
|
643
|
+
if (this.logic.start) {
|
|
644
|
+
try {
|
|
645
|
+
this.logic.start(this._snapshot, this._actorScope);
|
|
646
|
+
} catch (err) {
|
|
647
|
+
this._snapshot = {
|
|
648
|
+
...this._snapshot,
|
|
649
|
+
status: 'error',
|
|
650
|
+
error: err
|
|
651
|
+
};
|
|
652
|
+
this._error(err);
|
|
653
|
+
return this;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// TODO: this notifies all subscribers but usually this is redundant
|
|
658
|
+
// there is no real change happening here
|
|
659
|
+
// we need to rethink if this needs to be refactored
|
|
660
|
+
this.update(this._snapshot, initEvent);
|
|
661
|
+
if (this.options.devTools) {
|
|
662
|
+
this.attachDevTools();
|
|
663
|
+
}
|
|
664
|
+
this.mailbox.start();
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
_process(event) {
|
|
668
|
+
let nextState;
|
|
669
|
+
let caughtError;
|
|
670
|
+
try {
|
|
671
|
+
nextState = this.logic.transition(this._snapshot, event, this._actorScope);
|
|
672
|
+
} catch (err) {
|
|
673
|
+
// we wrap it in a box so we can rethrow it later even if falsy value gets caught here
|
|
674
|
+
caughtError = {
|
|
675
|
+
err
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (caughtError) {
|
|
679
|
+
const {
|
|
680
|
+
err
|
|
681
|
+
} = caughtError;
|
|
682
|
+
this._snapshot = {
|
|
683
|
+
...this._snapshot,
|
|
684
|
+
status: 'error',
|
|
685
|
+
error: err
|
|
686
|
+
};
|
|
687
|
+
this._error(err);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
this.update(nextState, event);
|
|
691
|
+
if (event.type === XSTATE_STOP) {
|
|
692
|
+
this._stopProcedure();
|
|
693
|
+
this._complete();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
_stop() {
|
|
697
|
+
if (this._processingStatus === ProcessingStatus.Stopped) {
|
|
698
|
+
return this;
|
|
699
|
+
}
|
|
700
|
+
this.mailbox.clear();
|
|
701
|
+
if (this._processingStatus === ProcessingStatus.NotStarted) {
|
|
702
|
+
this._processingStatus = ProcessingStatus.Stopped;
|
|
703
|
+
return this;
|
|
704
|
+
}
|
|
705
|
+
this.mailbox.enqueue({
|
|
706
|
+
type: XSTATE_STOP
|
|
707
|
+
});
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Stops the Actor and unsubscribe all listeners.
|
|
713
|
+
*/
|
|
714
|
+
stop() {
|
|
715
|
+
if (this._parent) {
|
|
716
|
+
throw new Error('A non-root actor cannot be stopped directly.');
|
|
717
|
+
}
|
|
718
|
+
return this._stop();
|
|
719
|
+
}
|
|
720
|
+
_complete() {
|
|
721
|
+
for (const observer of this.observers) {
|
|
722
|
+
try {
|
|
723
|
+
observer.complete?.();
|
|
724
|
+
} catch (err) {
|
|
725
|
+
reportUnhandledError(err);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
this.observers.clear();
|
|
729
|
+
}
|
|
730
|
+
_reportError(err) {
|
|
731
|
+
if (!this.observers.size) {
|
|
732
|
+
if (!this._parent) {
|
|
733
|
+
reportUnhandledError(err);
|
|
734
|
+
}
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
let reportError = false;
|
|
738
|
+
for (const observer of this.observers) {
|
|
739
|
+
const errorListener = observer.error;
|
|
740
|
+
reportError ||= !errorListener;
|
|
741
|
+
try {
|
|
742
|
+
errorListener?.(err);
|
|
743
|
+
} catch (err2) {
|
|
744
|
+
reportUnhandledError(err2);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
this.observers.clear();
|
|
748
|
+
if (reportError) {
|
|
749
|
+
reportUnhandledError(err);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
_error(err) {
|
|
753
|
+
this._stopProcedure();
|
|
754
|
+
this._reportError(err);
|
|
755
|
+
if (this._parent) {
|
|
756
|
+
this.system._relay(this, this._parent, createErrorActorEvent(this.id, err));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
// TODO: atm children don't belong entirely to the actor so
|
|
760
|
+
// in a way - it's not even super aware of them
|
|
761
|
+
// so we can't stop them from here but we really should!
|
|
762
|
+
// right now, they are being stopped within the machine's transition
|
|
763
|
+
// but that could throw and leave us with "orphaned" active actors
|
|
764
|
+
_stopProcedure() {
|
|
765
|
+
if (this._processingStatus !== ProcessingStatus.Running) {
|
|
766
|
+
// Actor already stopped; do nothing
|
|
767
|
+
return this;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Cancel all delayed events
|
|
771
|
+
for (const key of Object.keys(this.delayedEventsMap)) {
|
|
772
|
+
this.clock.clearTimeout(this.delayedEventsMap[key]);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// TODO: mailbox.reset
|
|
776
|
+
this.mailbox.clear();
|
|
777
|
+
// TODO: after `stop` we must prepare ourselves for receiving events again
|
|
778
|
+
// events sent *after* stop signal must be queued
|
|
779
|
+
// it seems like this should be the common behavior for all of our consumers
|
|
780
|
+
// so perhaps this should be unified somehow for all of them
|
|
781
|
+
this.mailbox = new Mailbox(this._process.bind(this));
|
|
782
|
+
this._processingStatus = ProcessingStatus.Stopped;
|
|
783
|
+
this.system._unregister(this);
|
|
784
|
+
return this;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* @internal
|
|
789
|
+
*/
|
|
790
|
+
_send(event) {
|
|
791
|
+
if (this._processingStatus === ProcessingStatus.Stopped) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
this.mailbox.enqueue(event);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Sends an event to the running Actor to trigger a transition.
|
|
799
|
+
*
|
|
800
|
+
* @param event The event to send
|
|
801
|
+
*/
|
|
802
|
+
send(event) {
|
|
803
|
+
this.system._relay(undefined, this, event);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* TODO: figure out a way to do this within the machine
|
|
808
|
+
* @internal
|
|
809
|
+
*/
|
|
810
|
+
delaySend(params) {
|
|
811
|
+
const {
|
|
812
|
+
event,
|
|
813
|
+
id,
|
|
814
|
+
delay
|
|
815
|
+
} = params;
|
|
816
|
+
const timerId = this.clock.setTimeout(() => {
|
|
817
|
+
this.system._relay(this, params.to ?? this, event);
|
|
818
|
+
}, delay);
|
|
819
|
+
|
|
820
|
+
// TODO: consider the rehydration story here
|
|
821
|
+
if (id) {
|
|
822
|
+
this.delayedEventsMap[id] = timerId;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* TODO: figure out a way to do this within the machine
|
|
828
|
+
* @internal
|
|
829
|
+
*/
|
|
830
|
+
cancel(sendId) {
|
|
831
|
+
this.clock.clearTimeout(this.delayedEventsMap[sendId]);
|
|
832
|
+
delete this.delayedEventsMap[sendId];
|
|
833
|
+
}
|
|
834
|
+
attachDevTools() {
|
|
835
|
+
const {
|
|
836
|
+
devTools
|
|
837
|
+
} = this.options;
|
|
838
|
+
if (devTools) {
|
|
839
|
+
const resolvedDevToolsAdapter = typeof devTools === 'function' ? devTools : dev_dist_xstateDev.devToolsAdapter;
|
|
840
|
+
resolvedDevToolsAdapter(this);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
toJSON() {
|
|
844
|
+
return {
|
|
845
|
+
xstate$$type: $$ACTOR_TYPE,
|
|
846
|
+
id: this.id
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Obtain the internal state of the actor, which can be persisted.
|
|
852
|
+
*
|
|
853
|
+
* @remarks
|
|
854
|
+
* The internal state can be persisted from any actor, not only machines.
|
|
855
|
+
*
|
|
856
|
+
* Note that the persisted state is not the same as the snapshot from {@link Actor.getSnapshot}. Persisted state represents the internal state of the actor, while snapshots represent the actor's last emitted value.
|
|
857
|
+
*
|
|
858
|
+
* Can be restored with {@link ActorOptions.state}
|
|
859
|
+
*
|
|
860
|
+
* @see https://stately.ai/docs/persistence
|
|
861
|
+
*/
|
|
862
|
+
|
|
863
|
+
getPersistedSnapshot(options) {
|
|
864
|
+
return this.logic.getPersistedSnapshot(this._snapshot, options);
|
|
865
|
+
}
|
|
866
|
+
[symbolObservable]() {
|
|
867
|
+
return this;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Read an actor’s snapshot synchronously.
|
|
872
|
+
*
|
|
873
|
+
* @remarks
|
|
874
|
+
* The snapshot represent an actor's last emitted value.
|
|
875
|
+
*
|
|
876
|
+
* When an actor receives an event, its internal state may change.
|
|
877
|
+
* An actor may emit a snapshot when a state transition occurs.
|
|
878
|
+
*
|
|
879
|
+
* Note that some actors, such as callback actors generated with `fromCallback`, will not emit snapshots.
|
|
880
|
+
*
|
|
881
|
+
* @see {@link Actor.subscribe} to subscribe to an actor’s snapshot values.
|
|
882
|
+
* @see {@link Actor.getPersistedSnapshot} to persist the internal state of an actor (which is more than just a snapshot).
|
|
883
|
+
*/
|
|
884
|
+
getSnapshot() {
|
|
885
|
+
return this._snapshot;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Creates a new actor instance for the given actor logic with the provided options, if any.
|
|
891
|
+
*
|
|
892
|
+
* @remarks
|
|
893
|
+
* When you create an actor from actor logic via `createActor(logic)`, you implicitly create an actor system where the created actor is the root actor.
|
|
894
|
+
* Any actors spawned from this root actor and its descendants are part of that actor system.
|
|
895
|
+
*
|
|
896
|
+
* @example
|
|
897
|
+
* ```ts
|
|
898
|
+
* import { createActor } from 'xstate';
|
|
899
|
+
* import { someActorLogic } from './someActorLogic.ts';
|
|
900
|
+
*
|
|
901
|
+
* // Creating the actor, which implicitly creates an actor system with itself as the root actor
|
|
902
|
+
* const actor = createActor(someActorLogic);
|
|
903
|
+
*
|
|
904
|
+
* actor.subscribe((snapshot) => {
|
|
905
|
+
* console.log(snapshot);
|
|
906
|
+
* });
|
|
907
|
+
*
|
|
908
|
+
* // Actors must be started by calling `actor.start()`, which will also start the actor system.
|
|
909
|
+
* actor.start();
|
|
910
|
+
*
|
|
911
|
+
* // Actors can receive events
|
|
912
|
+
* actor.send({ type: 'someEvent' });
|
|
913
|
+
*
|
|
914
|
+
* // You can stop root actors by calling `actor.stop()`, which will also stop the actor system and all actors in that system.
|
|
915
|
+
* actor.stop();
|
|
916
|
+
* ```
|
|
917
|
+
*
|
|
918
|
+
* @param logic - The actor logic to create an actor from. For a state machine actor logic creator, see {@link createMachine}. Other actor logic creators include {@link fromCallback}, {@link fromEventObservable}, {@link fromObservable}, {@link fromPromise}, and {@link fromTransition}.
|
|
919
|
+
* @param options - Actor options
|
|
920
|
+
*/
|
|
921
|
+
|
|
922
|
+
function createActor(logic, options) {
|
|
923
|
+
const interpreter = new Actor(logic, options);
|
|
924
|
+
return interpreter;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Creates a new Interpreter instance for the given machine with the provided options, if any.
|
|
929
|
+
*
|
|
930
|
+
* @deprecated Use `createActor` instead
|
|
931
|
+
*/
|
|
932
|
+
const interpret = createActor;
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* @deprecated Use `Actor` instead.
|
|
936
|
+
*/
|
|
937
|
+
|
|
938
|
+
function resolveCancel(_, snapshot, actionArgs, actionParams, {
|
|
939
|
+
sendId
|
|
940
|
+
}) {
|
|
941
|
+
const resolvedSendId = typeof sendId === 'function' ? sendId(actionArgs, actionParams) : sendId;
|
|
942
|
+
return [snapshot, resolvedSendId];
|
|
943
|
+
}
|
|
944
|
+
function executeCancel(actorScope, resolvedSendId) {
|
|
945
|
+
actorScope.self.cancel(resolvedSendId);
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Cancels an in-flight `send(...)` action. A canceled sent action will not
|
|
949
|
+
* be executed, nor will its event be sent, unless it has already been sent
|
|
950
|
+
* (e.g., if `cancel(...)` is called after the `send(...)` action's `delay`).
|
|
951
|
+
*
|
|
952
|
+
* @param sendId The `id` of the `send(...)` action to cancel.
|
|
953
|
+
*/
|
|
954
|
+
function cancel(sendId) {
|
|
955
|
+
function cancel(args, params) {
|
|
956
|
+
}
|
|
957
|
+
cancel.type = 'xstate.cancel';
|
|
958
|
+
cancel.sendId = sendId;
|
|
959
|
+
cancel.resolve = resolveCancel;
|
|
960
|
+
cancel.execute = executeCancel;
|
|
961
|
+
return cancel;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
function resolveSpawn(actorScope, snapshot, actionArgs, _actionParams, {
|
|
965
|
+
id,
|
|
966
|
+
systemId,
|
|
967
|
+
src,
|
|
968
|
+
input,
|
|
969
|
+
syncSnapshot
|
|
970
|
+
}) {
|
|
971
|
+
const logic = typeof src === 'string' ? resolveReferencedActor(snapshot.machine, src) : src;
|
|
972
|
+
const resolvedId = typeof id === 'function' ? id(actionArgs) : id;
|
|
973
|
+
let actorRef;
|
|
974
|
+
if (logic) {
|
|
975
|
+
actorRef = createActor(logic, {
|
|
976
|
+
id: resolvedId,
|
|
977
|
+
src,
|
|
978
|
+
parent: actorScope?.self,
|
|
979
|
+
syncSnapshot,
|
|
980
|
+
systemId,
|
|
981
|
+
input: typeof input === 'function' ? input({
|
|
982
|
+
context: snapshot.context,
|
|
983
|
+
event: actionArgs.event,
|
|
984
|
+
self: actorScope?.self
|
|
985
|
+
}) : input
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
return [cloneMachineSnapshot(snapshot, {
|
|
989
|
+
children: {
|
|
990
|
+
...snapshot.children,
|
|
991
|
+
[resolvedId]: actorRef
|
|
992
|
+
}
|
|
993
|
+
}), {
|
|
994
|
+
id,
|
|
995
|
+
actorRef
|
|
996
|
+
}];
|
|
997
|
+
}
|
|
998
|
+
function executeSpawn(actorScope, {
|
|
999
|
+
id,
|
|
1000
|
+
actorRef
|
|
1001
|
+
}) {
|
|
1002
|
+
if (!actorRef) {
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
actorScope.defer(() => {
|
|
1006
|
+
if (actorRef._processingStatus === ProcessingStatus.Stopped) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
actorRef.start();
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
function spawnChild(...[src, {
|
|
1013
|
+
id,
|
|
1014
|
+
systemId,
|
|
1015
|
+
input,
|
|
1016
|
+
syncSnapshot = false
|
|
1017
|
+
} = {}]) {
|
|
1018
|
+
function spawnChild(args, params) {
|
|
1019
|
+
}
|
|
1020
|
+
spawnChild.type = 'snapshot.spawnChild';
|
|
1021
|
+
spawnChild.id = id;
|
|
1022
|
+
spawnChild.systemId = systemId;
|
|
1023
|
+
spawnChild.src = src;
|
|
1024
|
+
spawnChild.input = input;
|
|
1025
|
+
spawnChild.syncSnapshot = syncSnapshot;
|
|
1026
|
+
spawnChild.resolve = resolveSpawn;
|
|
1027
|
+
spawnChild.execute = executeSpawn;
|
|
1028
|
+
return spawnChild;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
function resolveStop(_, snapshot, args, actionParams, {
|
|
1032
|
+
actorRef
|
|
1033
|
+
}) {
|
|
1034
|
+
const actorRefOrString = typeof actorRef === 'function' ? actorRef(args, actionParams) : actorRef;
|
|
1035
|
+
const resolvedActorRef = typeof actorRefOrString === 'string' ? snapshot.children[actorRefOrString] : actorRefOrString;
|
|
1036
|
+
let children = snapshot.children;
|
|
1037
|
+
if (resolvedActorRef) {
|
|
1038
|
+
children = {
|
|
1039
|
+
...children
|
|
1040
|
+
};
|
|
1041
|
+
delete children[resolvedActorRef.id];
|
|
1042
|
+
}
|
|
1043
|
+
return [cloneMachineSnapshot(snapshot, {
|
|
1044
|
+
children
|
|
1045
|
+
}), resolvedActorRef];
|
|
1046
|
+
}
|
|
1047
|
+
function executeStop(actorScope, actorRef) {
|
|
1048
|
+
if (!actorRef) {
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// we need to eagerly unregister it here so a new actor with the same systemId can be registered immediately
|
|
1053
|
+
// since we defer actual stopping of the actor but we don't defer actor creations (and we can't do that)
|
|
1054
|
+
// this could throw on `systemId` collision, for example, when dealing with reentering transitions
|
|
1055
|
+
actorScope.system._unregister(actorRef);
|
|
1056
|
+
|
|
1057
|
+
// this allows us to prevent an actor from being started if it gets stopped within the same macrostep
|
|
1058
|
+
// this can happen, for example, when the invoking state is being exited immediately by an always transition
|
|
1059
|
+
if (actorRef._processingStatus !== ProcessingStatus.Running) {
|
|
1060
|
+
actorScope.stopChild(actorRef);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
// stopping a child enqueues a stop event in the child actor's mailbox
|
|
1064
|
+
// we need for all of the already enqueued events to be processed before we stop the child
|
|
1065
|
+
// the parent itself might want to send some events to a child (for example from exit actions on the invoking state)
|
|
1066
|
+
// and we don't want to ignore those events
|
|
1067
|
+
actorScope.defer(() => {
|
|
1068
|
+
actorScope.stopChild(actorRef);
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Stops a child actor.
|
|
1073
|
+
*
|
|
1074
|
+
* @param actorRef The actor to stop.
|
|
1075
|
+
*/
|
|
1076
|
+
function stopChild(actorRef) {
|
|
1077
|
+
function stop(args, params) {
|
|
1078
|
+
}
|
|
1079
|
+
stop.type = 'xstate.stopChild';
|
|
1080
|
+
stop.actorRef = actorRef;
|
|
1081
|
+
stop.resolve = resolveStop;
|
|
1082
|
+
stop.execute = executeStop;
|
|
1083
|
+
return stop;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* Stops a child actor.
|
|
1088
|
+
*
|
|
1089
|
+
* @deprecated Use `stopChild(...)` instead
|
|
1090
|
+
*/
|
|
1091
|
+
const stop = stopChild;
|
|
1092
|
+
|
|
1093
|
+
function checkStateIn(snapshot, _, {
|
|
1094
|
+
stateValue
|
|
1095
|
+
}) {
|
|
1096
|
+
if (typeof stateValue === 'string' && isStateId(stateValue)) {
|
|
1097
|
+
const target = snapshot.machine.getStateNodeById(stateValue);
|
|
1098
|
+
return snapshot._nodes.some(sn => sn === target);
|
|
1099
|
+
}
|
|
1100
|
+
return snapshot.matches(stateValue);
|
|
1101
|
+
}
|
|
1102
|
+
function stateIn(stateValue) {
|
|
1103
|
+
function stateIn(args, params) {
|
|
1104
|
+
return false;
|
|
1105
|
+
}
|
|
1106
|
+
stateIn.check = checkStateIn;
|
|
1107
|
+
stateIn.stateValue = stateValue;
|
|
1108
|
+
return stateIn;
|
|
1109
|
+
}
|
|
1110
|
+
function checkNot(snapshot, {
|
|
1111
|
+
context,
|
|
1112
|
+
event
|
|
1113
|
+
}, {
|
|
1114
|
+
guards
|
|
1115
|
+
}) {
|
|
1116
|
+
return !evaluateGuard(guards[0], context, event, snapshot);
|
|
1117
|
+
}
|
|
1118
|
+
function not(guard) {
|
|
1119
|
+
function not(args, params) {
|
|
1120
|
+
return false;
|
|
1121
|
+
}
|
|
1122
|
+
not.check = checkNot;
|
|
1123
|
+
not.guards = [guard];
|
|
1124
|
+
return not;
|
|
1125
|
+
}
|
|
1126
|
+
function checkAnd(snapshot, {
|
|
1127
|
+
context,
|
|
1128
|
+
event
|
|
1129
|
+
}, {
|
|
1130
|
+
guards
|
|
1131
|
+
}) {
|
|
1132
|
+
return guards.every(guard => evaluateGuard(guard, context, event, snapshot));
|
|
1133
|
+
}
|
|
1134
|
+
function and(guards) {
|
|
1135
|
+
function and(args, params) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
and.check = checkAnd;
|
|
1139
|
+
and.guards = guards;
|
|
1140
|
+
return and;
|
|
1141
|
+
}
|
|
1142
|
+
function checkOr(snapshot, {
|
|
1143
|
+
context,
|
|
1144
|
+
event
|
|
1145
|
+
}, {
|
|
1146
|
+
guards
|
|
1147
|
+
}) {
|
|
1148
|
+
return guards.some(guard => evaluateGuard(guard, context, event, snapshot));
|
|
1149
|
+
}
|
|
1150
|
+
function or(guards) {
|
|
1151
|
+
function or(args, params) {
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1154
|
+
or.check = checkOr;
|
|
1155
|
+
or.guards = guards;
|
|
1156
|
+
return or;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// TODO: throw on cycles (depth check should be enough)
|
|
1160
|
+
function evaluateGuard(guard, context, event, snapshot) {
|
|
1161
|
+
const {
|
|
1162
|
+
machine
|
|
1163
|
+
} = snapshot;
|
|
1164
|
+
const isInline = typeof guard === 'function';
|
|
1165
|
+
const resolved = isInline ? guard : machine.implementations.guards[typeof guard === 'string' ? guard : guard.type];
|
|
1166
|
+
if (!isInline && !resolved) {
|
|
1167
|
+
throw new Error(`Guard '${typeof guard === 'string' ? guard : guard.type}' is not implemented.'.`);
|
|
1168
|
+
}
|
|
1169
|
+
if (typeof resolved !== 'function') {
|
|
1170
|
+
return evaluateGuard(resolved, context, event, snapshot);
|
|
1171
|
+
}
|
|
1172
|
+
const guardArgs = {
|
|
1173
|
+
context,
|
|
1174
|
+
event
|
|
1175
|
+
};
|
|
1176
|
+
const guardParams = isInline || typeof guard === 'string' ? undefined : 'params' in guard ? typeof guard.params === 'function' ? guard.params({
|
|
1177
|
+
context,
|
|
1178
|
+
event
|
|
1179
|
+
}) : guard.params : undefined;
|
|
1180
|
+
if (!('check' in resolved)) {
|
|
1181
|
+
// the existing type of `.guards` assumes non-nullable `TExpressionGuard`
|
|
1182
|
+
// inline guards expect `TExpressionGuard` to be set to `undefined`
|
|
1183
|
+
// it's fine to cast this here, our logic makes sure that we call those 2 "variants" correctly
|
|
1184
|
+
return resolved(guardArgs, guardParams);
|
|
1185
|
+
}
|
|
1186
|
+
const builtinGuard = resolved;
|
|
1187
|
+
return builtinGuard.check(snapshot, guardArgs, resolved // this holds all params
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
const isAtomicStateNode = stateNode => stateNode.type === 'atomic' || stateNode.type === 'final';
|
|
1192
|
+
function getChildren(stateNode) {
|
|
1193
|
+
return Object.values(stateNode.states).filter(sn => sn.type !== 'history');
|
|
1194
|
+
}
|
|
1195
|
+
function getProperAncestors(stateNode, toStateNode) {
|
|
1196
|
+
const ancestors = [];
|
|
1197
|
+
if (toStateNode === stateNode) {
|
|
1198
|
+
return ancestors;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// add all ancestors
|
|
1202
|
+
let m = stateNode.parent;
|
|
1203
|
+
while (m && m !== toStateNode) {
|
|
1204
|
+
ancestors.push(m);
|
|
1205
|
+
m = m.parent;
|
|
1206
|
+
}
|
|
1207
|
+
return ancestors;
|
|
1208
|
+
}
|
|
1209
|
+
function getAllStateNodes(stateNodes) {
|
|
1210
|
+
const nodeSet = new Set(stateNodes);
|
|
1211
|
+
const adjList = getAdjList(nodeSet);
|
|
1212
|
+
|
|
1213
|
+
// add descendants
|
|
1214
|
+
for (const s of nodeSet) {
|
|
1215
|
+
// if previously active, add existing child nodes
|
|
1216
|
+
if (s.type === 'compound' && (!adjList.get(s) || !adjList.get(s).length)) {
|
|
1217
|
+
getInitialStateNodesWithTheirAncestors(s).forEach(sn => nodeSet.add(sn));
|
|
1218
|
+
} else {
|
|
1219
|
+
if (s.type === 'parallel') {
|
|
1220
|
+
for (const child of getChildren(s)) {
|
|
1221
|
+
if (child.type === 'history') {
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
if (!nodeSet.has(child)) {
|
|
1225
|
+
const initialStates = getInitialStateNodesWithTheirAncestors(child);
|
|
1226
|
+
for (const initialStateNode of initialStates) {
|
|
1227
|
+
nodeSet.add(initialStateNode);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
// add all ancestors
|
|
1236
|
+
for (const s of nodeSet) {
|
|
1237
|
+
let m = s.parent;
|
|
1238
|
+
while (m) {
|
|
1239
|
+
nodeSet.add(m);
|
|
1240
|
+
m = m.parent;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return nodeSet;
|
|
1244
|
+
}
|
|
1245
|
+
function getValueFromAdj(baseNode, adjList) {
|
|
1246
|
+
const childStateNodes = adjList.get(baseNode);
|
|
1247
|
+
if (!childStateNodes) {
|
|
1248
|
+
return {}; // todo: fix?
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
if (baseNode.type === 'compound') {
|
|
1252
|
+
const childStateNode = childStateNodes[0];
|
|
1253
|
+
if (childStateNode) {
|
|
1254
|
+
if (isAtomicStateNode(childStateNode)) {
|
|
1255
|
+
return childStateNode.key;
|
|
1256
|
+
}
|
|
1257
|
+
} else {
|
|
1258
|
+
return {};
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const stateValue = {};
|
|
1262
|
+
for (const childStateNode of childStateNodes) {
|
|
1263
|
+
stateValue[childStateNode.key] = getValueFromAdj(childStateNode, adjList);
|
|
1264
|
+
}
|
|
1265
|
+
return stateValue;
|
|
1266
|
+
}
|
|
1267
|
+
function getAdjList(stateNodes) {
|
|
1268
|
+
const adjList = new Map();
|
|
1269
|
+
for (const s of stateNodes) {
|
|
1270
|
+
if (!adjList.has(s)) {
|
|
1271
|
+
adjList.set(s, []);
|
|
1272
|
+
}
|
|
1273
|
+
if (s.parent) {
|
|
1274
|
+
if (!adjList.has(s.parent)) {
|
|
1275
|
+
adjList.set(s.parent, []);
|
|
1276
|
+
}
|
|
1277
|
+
adjList.get(s.parent).push(s);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return adjList;
|
|
1281
|
+
}
|
|
1282
|
+
function getStateValue(rootNode, stateNodes) {
|
|
1283
|
+
const config = getAllStateNodes(stateNodes);
|
|
1284
|
+
return getValueFromAdj(rootNode, getAdjList(config));
|
|
1285
|
+
}
|
|
1286
|
+
function isInFinalState(stateNodeSet, stateNode) {
|
|
1287
|
+
if (stateNode.type === 'compound') {
|
|
1288
|
+
return getChildren(stateNode).some(s => s.type === 'final' && stateNodeSet.has(s));
|
|
1289
|
+
}
|
|
1290
|
+
if (stateNode.type === 'parallel') {
|
|
1291
|
+
return getChildren(stateNode).every(sn => isInFinalState(stateNodeSet, sn));
|
|
1292
|
+
}
|
|
1293
|
+
return stateNode.type === 'final';
|
|
1294
|
+
}
|
|
1295
|
+
const isStateId = str => str[0] === STATE_IDENTIFIER;
|
|
1296
|
+
function getCandidates(stateNode, receivedEventType) {
|
|
1297
|
+
const candidates = stateNode.transitions.get(receivedEventType) || [...stateNode.transitions.keys()].filter(descriptor => {
|
|
1298
|
+
// check if transition is a wildcard transition,
|
|
1299
|
+
// which matches any non-transient events
|
|
1300
|
+
if (descriptor === WILDCARD) {
|
|
1301
|
+
return true;
|
|
1302
|
+
}
|
|
1303
|
+
if (!descriptor.endsWith('.*')) {
|
|
1304
|
+
return false;
|
|
1305
|
+
}
|
|
1306
|
+
const partialEventTokens = descriptor.split('.');
|
|
1307
|
+
const eventTokens = receivedEventType.split('.');
|
|
1308
|
+
for (let tokenIndex = 0; tokenIndex < partialEventTokens.length; tokenIndex++) {
|
|
1309
|
+
const partialEventToken = partialEventTokens[tokenIndex];
|
|
1310
|
+
const eventToken = eventTokens[tokenIndex];
|
|
1311
|
+
if (partialEventToken === '*') {
|
|
1312
|
+
const isLastToken = tokenIndex === partialEventTokens.length - 1;
|
|
1313
|
+
return isLastToken;
|
|
1314
|
+
}
|
|
1315
|
+
if (partialEventToken !== eventToken) {
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return true;
|
|
1320
|
+
}).sort((a, b) => b.length - a.length).flatMap(key => stateNode.transitions.get(key));
|
|
1321
|
+
return candidates;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* All delayed transitions from the config.
|
|
1326
|
+
*/
|
|
1327
|
+
function getDelayedTransitions(stateNode) {
|
|
1328
|
+
const afterConfig = stateNode.config.after;
|
|
1329
|
+
if (!afterConfig) {
|
|
1330
|
+
return [];
|
|
1331
|
+
}
|
|
1332
|
+
const mutateEntryExit = (delay, i) => {
|
|
1333
|
+
const afterEvent = createAfterEvent(delay, stateNode.id);
|
|
1334
|
+
const eventType = afterEvent.type;
|
|
1335
|
+
stateNode.entry.push(raise(afterEvent, {
|
|
1336
|
+
id: eventType,
|
|
1337
|
+
delay
|
|
1338
|
+
}));
|
|
1339
|
+
stateNode.exit.push(cancel(eventType));
|
|
1340
|
+
return eventType;
|
|
1341
|
+
};
|
|
1342
|
+
const delayedTransitions = Object.keys(afterConfig).flatMap((delay, i) => {
|
|
1343
|
+
const configTransition = afterConfig[delay];
|
|
1344
|
+
const resolvedTransition = typeof configTransition === 'string' ? {
|
|
1345
|
+
target: configTransition
|
|
1346
|
+
} : configTransition;
|
|
1347
|
+
const resolvedDelay = Number.isNaN(+delay) ? delay : +delay;
|
|
1348
|
+
const eventType = mutateEntryExit(resolvedDelay);
|
|
1349
|
+
return toArray(resolvedTransition).map(transition => ({
|
|
1350
|
+
...transition,
|
|
1351
|
+
event: eventType,
|
|
1352
|
+
delay: resolvedDelay
|
|
1353
|
+
}));
|
|
1354
|
+
});
|
|
1355
|
+
return delayedTransitions.map(delayedTransition => {
|
|
1356
|
+
const {
|
|
1357
|
+
delay
|
|
1358
|
+
} = delayedTransition;
|
|
1359
|
+
return {
|
|
1360
|
+
...formatTransition(stateNode, delayedTransition.event, delayedTransition),
|
|
1361
|
+
delay
|
|
1362
|
+
};
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
function formatTransition(stateNode, descriptor, transitionConfig) {
|
|
1366
|
+
const normalizedTarget = normalizeTarget(transitionConfig.target);
|
|
1367
|
+
const reenter = transitionConfig.reenter ?? false;
|
|
1368
|
+
const target = resolveTarget(stateNode, normalizedTarget);
|
|
1369
|
+
const transition = {
|
|
1370
|
+
...transitionConfig,
|
|
1371
|
+
actions: toArray(transitionConfig.actions),
|
|
1372
|
+
guard: transitionConfig.guard,
|
|
1373
|
+
target,
|
|
1374
|
+
source: stateNode,
|
|
1375
|
+
reenter,
|
|
1376
|
+
eventType: descriptor,
|
|
1377
|
+
toJSON: () => ({
|
|
1378
|
+
...transition,
|
|
1379
|
+
source: `#${stateNode.id}`,
|
|
1380
|
+
target: target ? target.map(t => `#${t.id}`) : undefined
|
|
1381
|
+
})
|
|
1382
|
+
};
|
|
1383
|
+
return transition;
|
|
1384
|
+
}
|
|
1385
|
+
function formatTransitions(stateNode) {
|
|
1386
|
+
const transitions = new Map();
|
|
1387
|
+
if (stateNode.config.on) {
|
|
1388
|
+
for (const descriptor of Object.keys(stateNode.config.on)) {
|
|
1389
|
+
if (descriptor === NULL_EVENT) {
|
|
1390
|
+
throw new Error('Null events ("") cannot be specified as a transition key. Use `always: { ... }` instead.');
|
|
1391
|
+
}
|
|
1392
|
+
const transitionsConfig = stateNode.config.on[descriptor];
|
|
1393
|
+
transitions.set(descriptor, toTransitionConfigArray(transitionsConfig).map(t => formatTransition(stateNode, descriptor, t)));
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
if (stateNode.config.onDone) {
|
|
1397
|
+
const descriptor = `xstate.done.state.${stateNode.id}`;
|
|
1398
|
+
transitions.set(descriptor, toTransitionConfigArray(stateNode.config.onDone).map(t => formatTransition(stateNode, descriptor, t)));
|
|
1399
|
+
}
|
|
1400
|
+
for (const invokeDef of stateNode.invoke) {
|
|
1401
|
+
if (invokeDef.onDone) {
|
|
1402
|
+
const descriptor = `xstate.done.actor.${invokeDef.id}`;
|
|
1403
|
+
transitions.set(descriptor, toTransitionConfigArray(invokeDef.onDone).map(t => formatTransition(stateNode, descriptor, t)));
|
|
1404
|
+
}
|
|
1405
|
+
if (invokeDef.onError) {
|
|
1406
|
+
const descriptor = `xstate.error.actor.${invokeDef.id}`;
|
|
1407
|
+
transitions.set(descriptor, toTransitionConfigArray(invokeDef.onError).map(t => formatTransition(stateNode, descriptor, t)));
|
|
1408
|
+
}
|
|
1409
|
+
if (invokeDef.onSnapshot) {
|
|
1410
|
+
const descriptor = `xstate.snapshot.${invokeDef.id}`;
|
|
1411
|
+
transitions.set(descriptor, toTransitionConfigArray(invokeDef.onSnapshot).map(t => formatTransition(stateNode, descriptor, t)));
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
for (const delayedTransition of stateNode.after) {
|
|
1415
|
+
let existing = transitions.get(delayedTransition.eventType);
|
|
1416
|
+
if (!existing) {
|
|
1417
|
+
existing = [];
|
|
1418
|
+
transitions.set(delayedTransition.eventType, existing);
|
|
1419
|
+
}
|
|
1420
|
+
existing.push(delayedTransition);
|
|
1421
|
+
}
|
|
1422
|
+
return transitions;
|
|
1423
|
+
}
|
|
1424
|
+
function formatInitialTransition(stateNode, _target) {
|
|
1425
|
+
const resolvedTarget = typeof _target === 'string' ? stateNode.states[_target] : _target ? stateNode.states[_target.target] : undefined;
|
|
1426
|
+
if (!resolvedTarget && _target) {
|
|
1427
|
+
throw new Error(`Initial state node "${_target}" not found on parent state node #${stateNode.id}`);
|
|
1428
|
+
}
|
|
1429
|
+
const transition = {
|
|
1430
|
+
source: stateNode,
|
|
1431
|
+
actions: !_target || typeof _target === 'string' ? [] : toArray(_target.actions),
|
|
1432
|
+
eventType: null,
|
|
1433
|
+
reenter: false,
|
|
1434
|
+
target: resolvedTarget ? [resolvedTarget] : [],
|
|
1435
|
+
toJSON: () => ({
|
|
1436
|
+
...transition,
|
|
1437
|
+
source: `#${stateNode.id}`,
|
|
1438
|
+
target: resolvedTarget ? [`#${resolvedTarget.id}`] : []
|
|
1439
|
+
})
|
|
1440
|
+
};
|
|
1441
|
+
return transition;
|
|
1442
|
+
}
|
|
1443
|
+
function resolveTarget(stateNode, targets) {
|
|
1444
|
+
if (targets === undefined) {
|
|
1445
|
+
// an undefined target signals that the state node should not transition from that state when receiving that event
|
|
1446
|
+
return undefined;
|
|
1447
|
+
}
|
|
1448
|
+
return targets.map(target => {
|
|
1449
|
+
if (typeof target !== 'string') {
|
|
1450
|
+
return target;
|
|
1451
|
+
}
|
|
1452
|
+
if (isStateId(target)) {
|
|
1453
|
+
return stateNode.machine.getStateNodeById(target);
|
|
1454
|
+
}
|
|
1455
|
+
const isInternalTarget = target[0] === STATE_DELIMITER;
|
|
1456
|
+
// If internal target is defined on machine,
|
|
1457
|
+
// do not include machine key on target
|
|
1458
|
+
if (isInternalTarget && !stateNode.parent) {
|
|
1459
|
+
return getStateNodeByPath(stateNode, target.slice(1));
|
|
1460
|
+
}
|
|
1461
|
+
const resolvedTarget = isInternalTarget ? stateNode.key + target : target;
|
|
1462
|
+
if (stateNode.parent) {
|
|
1463
|
+
try {
|
|
1464
|
+
const targetStateNode = getStateNodeByPath(stateNode.parent, resolvedTarget);
|
|
1465
|
+
return targetStateNode;
|
|
1466
|
+
} catch (err) {
|
|
1467
|
+
throw new Error(`Invalid transition definition for state node '${stateNode.id}':\n${err.message}`);
|
|
1468
|
+
}
|
|
1469
|
+
} else {
|
|
1470
|
+
throw new Error(`Invalid target: "${target}" is not a valid target from the root node. Did you mean ".${target}"?`);
|
|
1471
|
+
}
|
|
1472
|
+
});
|
|
1473
|
+
}
|
|
1474
|
+
function resolveHistoryDefaultTransition(stateNode) {
|
|
1475
|
+
const normalizedTarget = normalizeTarget(stateNode.config.target);
|
|
1476
|
+
if (!normalizedTarget) {
|
|
1477
|
+
return stateNode.parent.initial;
|
|
1478
|
+
}
|
|
1479
|
+
return {
|
|
1480
|
+
target: normalizedTarget.map(t => typeof t === 'string' ? getStateNodeByPath(stateNode.parent, t) : t)
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
function isHistoryNode(stateNode) {
|
|
1484
|
+
return stateNode.type === 'history';
|
|
1485
|
+
}
|
|
1486
|
+
function getInitialStateNodesWithTheirAncestors(stateNode) {
|
|
1487
|
+
const states = getInitialStateNodes(stateNode);
|
|
1488
|
+
for (const initialState of states) {
|
|
1489
|
+
for (const ancestor of getProperAncestors(initialState, stateNode)) {
|
|
1490
|
+
states.add(ancestor);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
return states;
|
|
1494
|
+
}
|
|
1495
|
+
function getInitialStateNodes(stateNode) {
|
|
1496
|
+
const set = new Set();
|
|
1497
|
+
function iter(descStateNode) {
|
|
1498
|
+
if (set.has(descStateNode)) {
|
|
1499
|
+
return;
|
|
1500
|
+
}
|
|
1501
|
+
set.add(descStateNode);
|
|
1502
|
+
if (descStateNode.type === 'compound') {
|
|
1503
|
+
iter(descStateNode.initial.target[0]);
|
|
1504
|
+
} else if (descStateNode.type === 'parallel') {
|
|
1505
|
+
for (const child of getChildren(descStateNode)) {
|
|
1506
|
+
iter(child);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
iter(stateNode);
|
|
1511
|
+
return set;
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Returns the child state node from its relative `stateKey`, or throws.
|
|
1515
|
+
*/
|
|
1516
|
+
function getStateNode(stateNode, stateKey) {
|
|
1517
|
+
if (isStateId(stateKey)) {
|
|
1518
|
+
return stateNode.machine.getStateNodeById(stateKey);
|
|
1519
|
+
}
|
|
1520
|
+
if (!stateNode.states) {
|
|
1521
|
+
throw new Error(`Unable to retrieve child state '${stateKey}' from '${stateNode.id}'; no child states exist.`);
|
|
1522
|
+
}
|
|
1523
|
+
const result = stateNode.states[stateKey];
|
|
1524
|
+
if (!result) {
|
|
1525
|
+
throw new Error(`Child state '${stateKey}' does not exist on '${stateNode.id}'`);
|
|
1526
|
+
}
|
|
1527
|
+
return result;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
/**
|
|
1531
|
+
* Returns the relative state node from the given `statePath`, or throws.
|
|
1532
|
+
*
|
|
1533
|
+
* @param statePath The string or string array relative path to the state node.
|
|
1534
|
+
*/
|
|
1535
|
+
function getStateNodeByPath(stateNode, statePath) {
|
|
1536
|
+
if (typeof statePath === 'string' && isStateId(statePath)) {
|
|
1537
|
+
try {
|
|
1538
|
+
return stateNode.machine.getStateNodeById(statePath);
|
|
1539
|
+
} catch (e) {
|
|
1540
|
+
// try individual paths
|
|
1541
|
+
// throw e;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
const arrayStatePath = toStatePath(statePath).slice();
|
|
1545
|
+
let currentStateNode = stateNode;
|
|
1546
|
+
while (arrayStatePath.length) {
|
|
1547
|
+
const key = arrayStatePath.shift();
|
|
1548
|
+
if (!key.length) {
|
|
1549
|
+
break;
|
|
1550
|
+
}
|
|
1551
|
+
currentStateNode = getStateNode(currentStateNode, key);
|
|
1552
|
+
}
|
|
1553
|
+
return currentStateNode;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
/**
|
|
1557
|
+
* Returns the state nodes represented by the current state value.
|
|
1558
|
+
*
|
|
1559
|
+
* @param stateValue The state value or State instance
|
|
1560
|
+
*/
|
|
1561
|
+
function getStateNodes(stateNode, stateValue) {
|
|
1562
|
+
if (typeof stateValue === 'string') {
|
|
1563
|
+
return [stateNode, stateNode.states[stateValue]];
|
|
1564
|
+
}
|
|
1565
|
+
const childStateKeys = Object.keys(stateValue);
|
|
1566
|
+
const childStateNodes = childStateKeys.map(subStateKey => getStateNode(stateNode, subStateKey)).filter(Boolean);
|
|
1567
|
+
return [stateNode.machine.root, stateNode].concat(childStateNodes, childStateKeys.reduce((allSubStateNodes, subStateKey) => {
|
|
1568
|
+
const subStateNode = getStateNode(stateNode, subStateKey);
|
|
1569
|
+
if (!subStateNode) {
|
|
1570
|
+
return allSubStateNodes;
|
|
1571
|
+
}
|
|
1572
|
+
const subStateNodes = getStateNodes(subStateNode, stateValue[subStateKey]);
|
|
1573
|
+
return allSubStateNodes.concat(subStateNodes);
|
|
1574
|
+
}, []));
|
|
1575
|
+
}
|
|
1576
|
+
function transitionAtomicNode(stateNode, stateValue, snapshot, event) {
|
|
1577
|
+
const childStateNode = getStateNode(stateNode, stateValue);
|
|
1578
|
+
const next = childStateNode.next(snapshot, event);
|
|
1579
|
+
if (!next || !next.length) {
|
|
1580
|
+
return stateNode.next(snapshot, event);
|
|
1581
|
+
}
|
|
1582
|
+
return next;
|
|
1583
|
+
}
|
|
1584
|
+
function transitionCompoundNode(stateNode, stateValue, snapshot, event) {
|
|
1585
|
+
const subStateKeys = Object.keys(stateValue);
|
|
1586
|
+
const childStateNode = getStateNode(stateNode, subStateKeys[0]);
|
|
1587
|
+
const next = transitionNode(childStateNode, stateValue[subStateKeys[0]], snapshot, event);
|
|
1588
|
+
if (!next || !next.length) {
|
|
1589
|
+
return stateNode.next(snapshot, event);
|
|
1590
|
+
}
|
|
1591
|
+
return next;
|
|
1592
|
+
}
|
|
1593
|
+
function transitionParallelNode(stateNode, stateValue, snapshot, event) {
|
|
1594
|
+
const allInnerTransitions = [];
|
|
1595
|
+
for (const subStateKey of Object.keys(stateValue)) {
|
|
1596
|
+
const subStateValue = stateValue[subStateKey];
|
|
1597
|
+
if (!subStateValue) {
|
|
1598
|
+
continue;
|
|
1599
|
+
}
|
|
1600
|
+
const subStateNode = getStateNode(stateNode, subStateKey);
|
|
1601
|
+
const innerTransitions = transitionNode(subStateNode, subStateValue, snapshot, event);
|
|
1602
|
+
if (innerTransitions) {
|
|
1603
|
+
allInnerTransitions.push(...innerTransitions);
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
if (!allInnerTransitions.length) {
|
|
1607
|
+
return stateNode.next(snapshot, event);
|
|
1608
|
+
}
|
|
1609
|
+
return allInnerTransitions;
|
|
1610
|
+
}
|
|
1611
|
+
function transitionNode(stateNode, stateValue, snapshot, event) {
|
|
1612
|
+
// leaf node
|
|
1613
|
+
if (typeof stateValue === 'string') {
|
|
1614
|
+
return transitionAtomicNode(stateNode, stateValue, snapshot, event);
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// compound node
|
|
1618
|
+
if (Object.keys(stateValue).length === 1) {
|
|
1619
|
+
return transitionCompoundNode(stateNode, stateValue, snapshot, event);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
// parallel node
|
|
1623
|
+
return transitionParallelNode(stateNode, stateValue, snapshot, event);
|
|
1624
|
+
}
|
|
1625
|
+
function getHistoryNodes(stateNode) {
|
|
1626
|
+
return Object.keys(stateNode.states).map(key => stateNode.states[key]).filter(sn => sn.type === 'history');
|
|
1627
|
+
}
|
|
1628
|
+
function isDescendant(childStateNode, parentStateNode) {
|
|
1629
|
+
let marker = childStateNode;
|
|
1630
|
+
while (marker.parent && marker.parent !== parentStateNode) {
|
|
1631
|
+
marker = marker.parent;
|
|
1632
|
+
}
|
|
1633
|
+
return marker.parent === parentStateNode;
|
|
1634
|
+
}
|
|
1635
|
+
function hasIntersection(s1, s2) {
|
|
1636
|
+
const set1 = new Set(s1);
|
|
1637
|
+
const set2 = new Set(s2);
|
|
1638
|
+
for (const item of set1) {
|
|
1639
|
+
if (set2.has(item)) {
|
|
1640
|
+
return true;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
for (const item of set2) {
|
|
1644
|
+
if (set1.has(item)) {
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
return false;
|
|
1649
|
+
}
|
|
1650
|
+
function removeConflictingTransitions(enabledTransitions, stateNodeSet, historyValue) {
|
|
1651
|
+
const filteredTransitions = new Set();
|
|
1652
|
+
for (const t1 of enabledTransitions) {
|
|
1653
|
+
let t1Preempted = false;
|
|
1654
|
+
const transitionsToRemove = new Set();
|
|
1655
|
+
for (const t2 of filteredTransitions) {
|
|
1656
|
+
if (hasIntersection(computeExitSet([t1], stateNodeSet, historyValue), computeExitSet([t2], stateNodeSet, historyValue))) {
|
|
1657
|
+
if (isDescendant(t1.source, t2.source)) {
|
|
1658
|
+
transitionsToRemove.add(t2);
|
|
1659
|
+
} else {
|
|
1660
|
+
t1Preempted = true;
|
|
1661
|
+
break;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
if (!t1Preempted) {
|
|
1666
|
+
for (const t3 of transitionsToRemove) {
|
|
1667
|
+
filteredTransitions.delete(t3);
|
|
1668
|
+
}
|
|
1669
|
+
filteredTransitions.add(t1);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
return Array.from(filteredTransitions);
|
|
1673
|
+
}
|
|
1674
|
+
function findLeastCommonAncestor(stateNodes) {
|
|
1675
|
+
const [head, ...tail] = stateNodes;
|
|
1676
|
+
for (const ancestor of getProperAncestors(head, undefined)) {
|
|
1677
|
+
if (tail.every(sn => isDescendant(sn, ancestor))) {
|
|
1678
|
+
return ancestor;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
function getEffectiveTargetStates(transition, historyValue) {
|
|
1683
|
+
if (!transition.target) {
|
|
1684
|
+
return [];
|
|
1685
|
+
}
|
|
1686
|
+
const targets = new Set();
|
|
1687
|
+
for (const targetNode of transition.target) {
|
|
1688
|
+
if (isHistoryNode(targetNode)) {
|
|
1689
|
+
if (historyValue[targetNode.id]) {
|
|
1690
|
+
for (const node of historyValue[targetNode.id]) {
|
|
1691
|
+
targets.add(node);
|
|
1692
|
+
}
|
|
1693
|
+
} else {
|
|
1694
|
+
for (const node of getEffectiveTargetStates(resolveHistoryDefaultTransition(targetNode), historyValue)) {
|
|
1695
|
+
targets.add(node);
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
} else {
|
|
1699
|
+
targets.add(targetNode);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
return [...targets];
|
|
1703
|
+
}
|
|
1704
|
+
function getTransitionDomain(transition, historyValue) {
|
|
1705
|
+
const targetStates = getEffectiveTargetStates(transition, historyValue);
|
|
1706
|
+
if (!targetStates) {
|
|
1707
|
+
return;
|
|
1708
|
+
}
|
|
1709
|
+
if (!transition.reenter && targetStates.every(target => target === transition.source || isDescendant(target, transition.source))) {
|
|
1710
|
+
return transition.source;
|
|
1711
|
+
}
|
|
1712
|
+
const lca = findLeastCommonAncestor(targetStates.concat(transition.source));
|
|
1713
|
+
if (lca) {
|
|
1714
|
+
return lca;
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1717
|
+
// at this point we know that it's a root transition since LCA couldn't be found
|
|
1718
|
+
if (transition.reenter) {
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
return transition.source.machine.root;
|
|
1722
|
+
}
|
|
1723
|
+
function computeExitSet(transitions, stateNodeSet, historyValue) {
|
|
1724
|
+
const statesToExit = new Set();
|
|
1725
|
+
for (const t of transitions) {
|
|
1726
|
+
if (t.target?.length) {
|
|
1727
|
+
const domain = getTransitionDomain(t, historyValue);
|
|
1728
|
+
if (t.reenter && t.source === domain) {
|
|
1729
|
+
statesToExit.add(domain);
|
|
1730
|
+
}
|
|
1731
|
+
for (const stateNode of stateNodeSet) {
|
|
1732
|
+
if (isDescendant(stateNode, domain)) {
|
|
1733
|
+
statesToExit.add(stateNode);
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return [...statesToExit];
|
|
1739
|
+
}
|
|
1740
|
+
function areStateNodeCollectionsEqual(prevStateNodes, nextStateNodeSet) {
|
|
1741
|
+
if (prevStateNodes.length !== nextStateNodeSet.size) {
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
for (const node of prevStateNodes) {
|
|
1745
|
+
if (!nextStateNodeSet.has(node)) {
|
|
1746
|
+
return false;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
return true;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
/**
|
|
1753
|
+
* https://www.w3.org/TR/scxml/#microstepProcedure
|
|
1754
|
+
*/
|
|
1755
|
+
function microstep(transitions, currentSnapshot, actorScope, event, isInitial, internalQueue) {
|
|
1756
|
+
if (!transitions.length) {
|
|
1757
|
+
return currentSnapshot;
|
|
1758
|
+
}
|
|
1759
|
+
const mutStateNodeSet = new Set(currentSnapshot._nodes);
|
|
1760
|
+
let historyValue = currentSnapshot.historyValue;
|
|
1761
|
+
const filteredTransitions = removeConflictingTransitions(transitions, mutStateNodeSet, historyValue);
|
|
1762
|
+
let nextState = currentSnapshot;
|
|
1763
|
+
|
|
1764
|
+
// Exit states
|
|
1765
|
+
if (!isInitial) {
|
|
1766
|
+
[nextState, historyValue] = exitStates(nextState, event, actorScope, filteredTransitions, mutStateNodeSet, historyValue, internalQueue);
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
// Execute transition content
|
|
1770
|
+
nextState = resolveActionsAndContext(nextState, event, actorScope, filteredTransitions.flatMap(t => t.actions), internalQueue);
|
|
1771
|
+
|
|
1772
|
+
// Enter states
|
|
1773
|
+
nextState = enterStates(nextState, event, actorScope, filteredTransitions, mutStateNodeSet, internalQueue, historyValue, isInitial);
|
|
1774
|
+
const nextStateNodes = [...mutStateNodeSet];
|
|
1775
|
+
if (nextState.status === 'done') {
|
|
1776
|
+
nextState = resolveActionsAndContext(nextState, event, actorScope, nextStateNodes.sort((a, b) => b.order - a.order).flatMap(state => state.exit), internalQueue);
|
|
1777
|
+
}
|
|
1778
|
+
try {
|
|
1779
|
+
if (historyValue === currentSnapshot.historyValue && areStateNodeCollectionsEqual(currentSnapshot._nodes, mutStateNodeSet)) {
|
|
1780
|
+
return nextState;
|
|
1781
|
+
}
|
|
1782
|
+
return cloneMachineSnapshot(nextState, {
|
|
1783
|
+
_nodes: nextStateNodes,
|
|
1784
|
+
historyValue
|
|
1785
|
+
});
|
|
1786
|
+
} catch (e) {
|
|
1787
|
+
// TODO: Refactor this once proper error handling is implemented.
|
|
1788
|
+
// See https://github.com/statelyai/rfcs/pull/4
|
|
1789
|
+
throw e;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function getMachineOutput(snapshot, event, actorScope, rootNode, rootCompletionNode) {
|
|
1793
|
+
if (!rootNode.output) {
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
const doneStateEvent = createDoneStateEvent(rootCompletionNode.id, rootCompletionNode.output && rootCompletionNode.parent ? resolveOutput(rootCompletionNode.output, snapshot.context, event, actorScope.self) : undefined);
|
|
1797
|
+
return resolveOutput(rootNode.output, snapshot.context, doneStateEvent, actorScope.self);
|
|
1798
|
+
}
|
|
1799
|
+
function enterStates(currentSnapshot, event, actorScope, filteredTransitions, mutStateNodeSet, internalQueue, historyValue, isInitial) {
|
|
1800
|
+
let nextSnapshot = currentSnapshot;
|
|
1801
|
+
const statesToEnter = new Set();
|
|
1802
|
+
// those are states that were directly targeted or indirectly targeted by the explicit target
|
|
1803
|
+
// in other words, those are states for which initial actions should be executed
|
|
1804
|
+
// when we target `#deep_child` initial actions of its ancestors shouldn't be executed
|
|
1805
|
+
const statesForDefaultEntry = new Set();
|
|
1806
|
+
computeEntrySet(filteredTransitions, historyValue, statesForDefaultEntry, statesToEnter);
|
|
1807
|
+
|
|
1808
|
+
// In the initial state, the root state node is "entered".
|
|
1809
|
+
if (isInitial) {
|
|
1810
|
+
statesForDefaultEntry.add(currentSnapshot.machine.root);
|
|
1811
|
+
}
|
|
1812
|
+
const completedNodes = new Set();
|
|
1813
|
+
for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
|
|
1814
|
+
mutStateNodeSet.add(stateNodeToEnter);
|
|
1815
|
+
const actions = [];
|
|
1816
|
+
|
|
1817
|
+
// Add entry actions
|
|
1818
|
+
actions.push(...stateNodeToEnter.entry);
|
|
1819
|
+
for (const invokeDef of stateNodeToEnter.invoke) {
|
|
1820
|
+
actions.push(spawnChild(invokeDef.src, {
|
|
1821
|
+
...invokeDef,
|
|
1822
|
+
syncSnapshot: !!invokeDef.onSnapshot
|
|
1823
|
+
}));
|
|
1824
|
+
}
|
|
1825
|
+
if (statesForDefaultEntry.has(stateNodeToEnter)) {
|
|
1826
|
+
const initialActions = stateNodeToEnter.initial.actions;
|
|
1827
|
+
actions.push(...initialActions);
|
|
1828
|
+
}
|
|
1829
|
+
nextSnapshot = resolveActionsAndContext(nextSnapshot, event, actorScope, actions, internalQueue, stateNodeToEnter.invoke.map(invokeDef => invokeDef.id));
|
|
1830
|
+
if (stateNodeToEnter.type === 'final') {
|
|
1831
|
+
const parent = stateNodeToEnter.parent;
|
|
1832
|
+
let ancestorMarker = parent?.type === 'parallel' ? parent : parent?.parent;
|
|
1833
|
+
let rootCompletionNode = ancestorMarker || stateNodeToEnter;
|
|
1834
|
+
if (parent?.type === 'compound') {
|
|
1835
|
+
internalQueue.push(createDoneStateEvent(parent.id, stateNodeToEnter.output ? resolveOutput(stateNodeToEnter.output, nextSnapshot.context, event, actorScope.self) : undefined));
|
|
1836
|
+
}
|
|
1837
|
+
while (ancestorMarker?.type === 'parallel' && !completedNodes.has(ancestorMarker) && isInFinalState(mutStateNodeSet, ancestorMarker)) {
|
|
1838
|
+
completedNodes.add(ancestorMarker);
|
|
1839
|
+
internalQueue.push(createDoneStateEvent(ancestorMarker.id));
|
|
1840
|
+
rootCompletionNode = ancestorMarker;
|
|
1841
|
+
ancestorMarker = ancestorMarker.parent;
|
|
1842
|
+
}
|
|
1843
|
+
if (ancestorMarker) {
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
nextSnapshot = cloneMachineSnapshot(nextSnapshot, {
|
|
1847
|
+
status: 'done',
|
|
1848
|
+
output: getMachineOutput(nextSnapshot, event, actorScope, nextSnapshot.machine.root, rootCompletionNode)
|
|
1849
|
+
});
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
return nextSnapshot;
|
|
1853
|
+
}
|
|
1854
|
+
function computeEntrySet(transitions, historyValue, statesForDefaultEntry, statesToEnter) {
|
|
1855
|
+
for (const t of transitions) {
|
|
1856
|
+
const domain = getTransitionDomain(t, historyValue);
|
|
1857
|
+
for (const s of t.target || []) {
|
|
1858
|
+
if (!isHistoryNode(s) && (
|
|
1859
|
+
// if the target is different than the source then it will *definitely* be entered
|
|
1860
|
+
t.source !== s ||
|
|
1861
|
+
// we know that the domain can't lie within the source
|
|
1862
|
+
// if it's different than the source then it's outside of it and it means that the target has to be entered as well
|
|
1863
|
+
t.source !== domain ||
|
|
1864
|
+
// reentering transitions always enter the target, even if it's the source itself
|
|
1865
|
+
t.reenter)) {
|
|
1866
|
+
statesToEnter.add(s);
|
|
1867
|
+
statesForDefaultEntry.add(s);
|
|
1868
|
+
}
|
|
1869
|
+
addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
|
|
1870
|
+
}
|
|
1871
|
+
const targetStates = getEffectiveTargetStates(t, historyValue);
|
|
1872
|
+
for (const s of targetStates) {
|
|
1873
|
+
const ancestors = getProperAncestors(s, domain);
|
|
1874
|
+
if (domain?.type === 'parallel') {
|
|
1875
|
+
ancestors.push(domain);
|
|
1876
|
+
}
|
|
1877
|
+
addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, ancestors, !t.source.parent && t.reenter ? undefined : domain);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
function addDescendantStatesToEnter(stateNode, historyValue, statesForDefaultEntry, statesToEnter) {
|
|
1882
|
+
if (isHistoryNode(stateNode)) {
|
|
1883
|
+
if (historyValue[stateNode.id]) {
|
|
1884
|
+
const historyStateNodes = historyValue[stateNode.id];
|
|
1885
|
+
for (const s of historyStateNodes) {
|
|
1886
|
+
statesToEnter.add(s);
|
|
1887
|
+
addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
|
|
1888
|
+
}
|
|
1889
|
+
for (const s of historyStateNodes) {
|
|
1890
|
+
addProperAncestorStatesToEnter(s, stateNode.parent, statesToEnter, historyValue, statesForDefaultEntry);
|
|
1891
|
+
}
|
|
1892
|
+
} else {
|
|
1893
|
+
const historyDefaultTransition = resolveHistoryDefaultTransition(stateNode);
|
|
1894
|
+
for (const s of historyDefaultTransition.target) {
|
|
1895
|
+
statesToEnter.add(s);
|
|
1896
|
+
if (historyDefaultTransition === stateNode.parent?.initial) {
|
|
1897
|
+
statesForDefaultEntry.add(stateNode.parent);
|
|
1898
|
+
}
|
|
1899
|
+
addDescendantStatesToEnter(s, historyValue, statesForDefaultEntry, statesToEnter);
|
|
1900
|
+
}
|
|
1901
|
+
for (const s of historyDefaultTransition.target) {
|
|
1902
|
+
addProperAncestorStatesToEnter(s, stateNode, statesToEnter, historyValue, statesForDefaultEntry);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
} else {
|
|
1906
|
+
if (stateNode.type === 'compound') {
|
|
1907
|
+
const [initialState] = stateNode.initial.target;
|
|
1908
|
+
if (!isHistoryNode(initialState)) {
|
|
1909
|
+
statesToEnter.add(initialState);
|
|
1910
|
+
statesForDefaultEntry.add(initialState);
|
|
1911
|
+
}
|
|
1912
|
+
addDescendantStatesToEnter(initialState, historyValue, statesForDefaultEntry, statesToEnter);
|
|
1913
|
+
addProperAncestorStatesToEnter(initialState, stateNode, statesToEnter, historyValue, statesForDefaultEntry);
|
|
1914
|
+
} else {
|
|
1915
|
+
if (stateNode.type === 'parallel') {
|
|
1916
|
+
for (const child of getChildren(stateNode).filter(sn => !isHistoryNode(sn))) {
|
|
1917
|
+
if (![...statesToEnter].some(s => isDescendant(s, child))) {
|
|
1918
|
+
if (!isHistoryNode(child)) {
|
|
1919
|
+
statesToEnter.add(child);
|
|
1920
|
+
statesForDefaultEntry.add(child);
|
|
1921
|
+
}
|
|
1922
|
+
addDescendantStatesToEnter(child, historyValue, statesForDefaultEntry, statesToEnter);
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
function addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, ancestors, reentrancyDomain) {
|
|
1930
|
+
for (const anc of ancestors) {
|
|
1931
|
+
if (!reentrancyDomain || isDescendant(anc, reentrancyDomain)) {
|
|
1932
|
+
statesToEnter.add(anc);
|
|
1933
|
+
}
|
|
1934
|
+
if (anc.type === 'parallel') {
|
|
1935
|
+
for (const child of getChildren(anc).filter(sn => !isHistoryNode(sn))) {
|
|
1936
|
+
if (![...statesToEnter].some(s => isDescendant(s, child))) {
|
|
1937
|
+
statesToEnter.add(child);
|
|
1938
|
+
addDescendantStatesToEnter(child, historyValue, statesForDefaultEntry, statesToEnter);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
function addProperAncestorStatesToEnter(stateNode, toStateNode, statesToEnter, historyValue, statesForDefaultEntry) {
|
|
1945
|
+
addAncestorStatesToEnter(statesToEnter, historyValue, statesForDefaultEntry, getProperAncestors(stateNode, toStateNode));
|
|
1946
|
+
}
|
|
1947
|
+
function exitStates(currentSnapshot, event, actorScope, transitions, mutStateNodeSet, historyValue, internalQueue) {
|
|
1948
|
+
let nextSnapshot = currentSnapshot;
|
|
1949
|
+
const statesToExit = computeExitSet(transitions, mutStateNodeSet, historyValue);
|
|
1950
|
+
statesToExit.sort((a, b) => b.order - a.order);
|
|
1951
|
+
let changedHistory;
|
|
1952
|
+
|
|
1953
|
+
// From SCXML algorithm: https://www.w3.org/TR/scxml/#exitStates
|
|
1954
|
+
for (const exitStateNode of statesToExit) {
|
|
1955
|
+
for (const historyNode of getHistoryNodes(exitStateNode)) {
|
|
1956
|
+
let predicate;
|
|
1957
|
+
if (historyNode.history === 'deep') {
|
|
1958
|
+
predicate = sn => isAtomicStateNode(sn) && isDescendant(sn, exitStateNode);
|
|
1959
|
+
} else {
|
|
1960
|
+
predicate = sn => {
|
|
1961
|
+
return sn.parent === exitStateNode;
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
changedHistory ??= {
|
|
1965
|
+
...historyValue
|
|
1966
|
+
};
|
|
1967
|
+
changedHistory[historyNode.id] = Array.from(mutStateNodeSet).filter(predicate);
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
for (const s of statesToExit) {
|
|
1971
|
+
nextSnapshot = resolveActionsAndContext(nextSnapshot, event, actorScope, [...s.exit, ...s.invoke.map(def => stopChild(def.id))], internalQueue);
|
|
1972
|
+
mutStateNodeSet.delete(s);
|
|
1973
|
+
}
|
|
1974
|
+
return [nextSnapshot, changedHistory || historyValue];
|
|
1975
|
+
}
|
|
1976
|
+
function resolveActionsAndContextWorker(currentSnapshot, event, actorScope, actions, extra, retries) {
|
|
1977
|
+
const {
|
|
1978
|
+
machine
|
|
1979
|
+
} = currentSnapshot;
|
|
1980
|
+
let intermediateSnapshot = currentSnapshot;
|
|
1981
|
+
for (const action of actions) {
|
|
1982
|
+
const isInline = typeof action === 'function';
|
|
1983
|
+
const resolvedAction = isInline ? action :
|
|
1984
|
+
// the existing type of `.actions` assumes non-nullable `TExpressionAction`
|
|
1985
|
+
// it's fine to cast this here to get a common type and lack of errors in the rest of the code
|
|
1986
|
+
// our logic below makes sure that we call those 2 "variants" correctly
|
|
1987
|
+
machine.implementations.actions[typeof action === 'string' ? action : action.type];
|
|
1988
|
+
if (!resolvedAction) {
|
|
1989
|
+
continue;
|
|
1990
|
+
}
|
|
1991
|
+
const actionArgs = {
|
|
1992
|
+
context: intermediateSnapshot.context,
|
|
1993
|
+
event,
|
|
1994
|
+
self: actorScope?.self,
|
|
1995
|
+
system: actorScope?.system
|
|
1996
|
+
};
|
|
1997
|
+
const actionParams = isInline || typeof action === 'string' ? undefined : 'params' in action ? typeof action.params === 'function' ? action.params({
|
|
1998
|
+
context: intermediateSnapshot.context,
|
|
1999
|
+
event
|
|
2000
|
+
}) : action.params : undefined;
|
|
2001
|
+
if (!('resolve' in resolvedAction)) {
|
|
2002
|
+
if (actorScope?.self._processingStatus === ProcessingStatus.Running) {
|
|
2003
|
+
resolvedAction(actionArgs, actionParams);
|
|
2004
|
+
} else {
|
|
2005
|
+
actorScope?.defer(() => {
|
|
2006
|
+
resolvedAction(actionArgs, actionParams);
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
const builtinAction = resolvedAction;
|
|
2012
|
+
const [nextState, params, actions] = builtinAction.resolve(actorScope, intermediateSnapshot, actionArgs, actionParams, resolvedAction,
|
|
2013
|
+
// this holds all params
|
|
2014
|
+
extra);
|
|
2015
|
+
intermediateSnapshot = nextState;
|
|
2016
|
+
if ('retryResolve' in builtinAction) {
|
|
2017
|
+
retries?.push([builtinAction, params]);
|
|
2018
|
+
}
|
|
2019
|
+
if ('execute' in builtinAction) {
|
|
2020
|
+
if (actorScope?.self._processingStatus === ProcessingStatus.Running) {
|
|
2021
|
+
builtinAction.execute(actorScope, params);
|
|
2022
|
+
} else {
|
|
2023
|
+
actorScope?.defer(builtinAction.execute.bind(null, actorScope, params));
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (actions) {
|
|
2027
|
+
intermediateSnapshot = resolveActionsAndContextWorker(intermediateSnapshot, event, actorScope, actions, extra, retries);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
return intermediateSnapshot;
|
|
2031
|
+
}
|
|
2032
|
+
function resolveActionsAndContext(currentSnapshot, event, actorScope, actions, internalQueue, deferredActorIds) {
|
|
2033
|
+
const retries = deferredActorIds ? [] : undefined;
|
|
2034
|
+
const nextState = resolveActionsAndContextWorker(currentSnapshot, event, actorScope, actions, {
|
|
2035
|
+
internalQueue,
|
|
2036
|
+
deferredActorIds
|
|
2037
|
+
}, retries);
|
|
2038
|
+
retries?.forEach(([builtinAction, params]) => {
|
|
2039
|
+
builtinAction.retryResolve(actorScope, nextState, params);
|
|
2040
|
+
});
|
|
2041
|
+
return nextState;
|
|
2042
|
+
}
|
|
2043
|
+
function macrostep(snapshot, event, actorScope, internalQueue = []) {
|
|
2044
|
+
let nextSnapshot = snapshot;
|
|
2045
|
+
const states = [];
|
|
2046
|
+
|
|
2047
|
+
// Handle stop event
|
|
2048
|
+
if (event.type === XSTATE_STOP) {
|
|
2049
|
+
nextSnapshot = cloneMachineSnapshot(stopChildren(nextSnapshot, event, actorScope), {
|
|
2050
|
+
status: 'stopped'
|
|
2051
|
+
});
|
|
2052
|
+
states.push(nextSnapshot);
|
|
2053
|
+
return {
|
|
2054
|
+
snapshot: nextSnapshot,
|
|
2055
|
+
microstates: states
|
|
2056
|
+
};
|
|
2057
|
+
}
|
|
2058
|
+
let nextEvent = event;
|
|
2059
|
+
|
|
2060
|
+
// Assume the state is at rest (no raised events)
|
|
2061
|
+
// Determine the next state based on the next microstep
|
|
2062
|
+
if (nextEvent.type !== XSTATE_INIT) {
|
|
2063
|
+
const currentEvent = nextEvent;
|
|
2064
|
+
const isErr = isErrorActorEvent(currentEvent);
|
|
2065
|
+
const transitions = selectTransitions(currentEvent, nextSnapshot);
|
|
2066
|
+
if (isErr && !transitions.length) {
|
|
2067
|
+
// TODO: we should likely only allow transitions selected by very explicit descriptors
|
|
2068
|
+
// `*` shouldn't be matched, likely `xstate.error.*` shouldnt be either
|
|
2069
|
+
// similarly `xstate.error.actor.*` and `xstate.error.actor.todo.*` have to be considered too
|
|
2070
|
+
nextSnapshot = cloneMachineSnapshot(snapshot, {
|
|
2071
|
+
status: 'error',
|
|
2072
|
+
error: currentEvent.error
|
|
2073
|
+
});
|
|
2074
|
+
states.push(nextSnapshot);
|
|
2075
|
+
return {
|
|
2076
|
+
snapshot: nextSnapshot,
|
|
2077
|
+
microstates: states
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
nextSnapshot = microstep(transitions, snapshot, actorScope, nextEvent, false, internalQueue);
|
|
2081
|
+
states.push(nextSnapshot);
|
|
2082
|
+
}
|
|
2083
|
+
let shouldSelectEventlessTransitions = true;
|
|
2084
|
+
while (nextSnapshot.status === 'active') {
|
|
2085
|
+
let enabledTransitions = shouldSelectEventlessTransitions ? selectEventlessTransitions(nextSnapshot, nextEvent) : [];
|
|
2086
|
+
|
|
2087
|
+
// eventless transitions should always be selected after selecting *regular* transitions
|
|
2088
|
+
// by assigning `undefined` to `previousState` we ensure that `shouldSelectEventlessTransitions` gets always computed to true in such a case
|
|
2089
|
+
const previousState = enabledTransitions.length ? nextSnapshot : undefined;
|
|
2090
|
+
if (!enabledTransitions.length) {
|
|
2091
|
+
if (!internalQueue.length) {
|
|
2092
|
+
break;
|
|
2093
|
+
}
|
|
2094
|
+
nextEvent = internalQueue.shift();
|
|
2095
|
+
enabledTransitions = selectTransitions(nextEvent, nextSnapshot);
|
|
2096
|
+
}
|
|
2097
|
+
nextSnapshot = microstep(enabledTransitions, nextSnapshot, actorScope, nextEvent, false, internalQueue);
|
|
2098
|
+
shouldSelectEventlessTransitions = nextSnapshot !== previousState;
|
|
2099
|
+
states.push(nextSnapshot);
|
|
2100
|
+
}
|
|
2101
|
+
if (nextSnapshot.status !== 'active') {
|
|
2102
|
+
stopChildren(nextSnapshot, nextEvent, actorScope);
|
|
2103
|
+
}
|
|
2104
|
+
return {
|
|
2105
|
+
snapshot: nextSnapshot,
|
|
2106
|
+
microstates: states
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
function stopChildren(nextState, event, actorScope) {
|
|
2110
|
+
return resolveActionsAndContext(nextState, event, actorScope, Object.values(nextState.children).map(child => stopChild(child)), []);
|
|
2111
|
+
}
|
|
2112
|
+
function selectTransitions(event, nextState) {
|
|
2113
|
+
return nextState.machine.getTransitionData(nextState, event);
|
|
2114
|
+
}
|
|
2115
|
+
function selectEventlessTransitions(nextState, event) {
|
|
2116
|
+
const enabledTransitionSet = new Set();
|
|
2117
|
+
const atomicStates = nextState._nodes.filter(isAtomicStateNode);
|
|
2118
|
+
for (const stateNode of atomicStates) {
|
|
2119
|
+
loop: for (const s of [stateNode].concat(getProperAncestors(stateNode, undefined))) {
|
|
2120
|
+
if (!s.always) {
|
|
2121
|
+
continue;
|
|
2122
|
+
}
|
|
2123
|
+
for (const transition of s.always) {
|
|
2124
|
+
if (transition.guard === undefined || evaluateGuard(transition.guard, nextState.context, event, nextState)) {
|
|
2125
|
+
enabledTransitionSet.add(transition);
|
|
2126
|
+
break loop;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return removeConflictingTransitions(Array.from(enabledTransitionSet), new Set(nextState._nodes), nextState.historyValue);
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
/**
|
|
2135
|
+
* Resolves a partial state value with its full representation in the state node's machine.
|
|
2136
|
+
*
|
|
2137
|
+
* @param stateValue The partial state value to resolve.
|
|
2138
|
+
*/
|
|
2139
|
+
function resolveStateValue(rootNode, stateValue) {
|
|
2140
|
+
const allStateNodes = getAllStateNodes(getStateNodes(rootNode, stateValue));
|
|
2141
|
+
return getStateValue(rootNode, [...allStateNodes]);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
function isMachineSnapshot(value) {
|
|
2145
|
+
return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
|
|
2146
|
+
}
|
|
2147
|
+
const machineSnapshotMatches = function matches(testValue) {
|
|
2148
|
+
return matchesState(testValue, this.value);
|
|
2149
|
+
};
|
|
2150
|
+
const machineSnapshotHasTag = function hasTag(tag) {
|
|
2151
|
+
return this.tags.has(tag);
|
|
2152
|
+
};
|
|
2153
|
+
const machineSnapshotCan = function can(event) {
|
|
2154
|
+
const transitionData = this.machine.getTransitionData(this, event);
|
|
2155
|
+
return !!transitionData?.length &&
|
|
2156
|
+
// Check that at least one transition is not forbidden
|
|
2157
|
+
transitionData.some(t => t.target !== undefined || t.actions.length);
|
|
2158
|
+
};
|
|
2159
|
+
const machineSnapshotToJSON = function toJSON() {
|
|
2160
|
+
const {
|
|
2161
|
+
_nodes: nodes,
|
|
2162
|
+
tags,
|
|
2163
|
+
machine,
|
|
2164
|
+
getMeta,
|
|
2165
|
+
toJSON,
|
|
2166
|
+
can,
|
|
2167
|
+
hasTag,
|
|
2168
|
+
matches,
|
|
2169
|
+
...jsonValues
|
|
2170
|
+
} = this;
|
|
2171
|
+
return {
|
|
2172
|
+
...jsonValues,
|
|
2173
|
+
tags: Array.from(tags)
|
|
2174
|
+
};
|
|
2175
|
+
};
|
|
2176
|
+
const machineSnapshotGetMeta = function getMeta() {
|
|
2177
|
+
return this._nodes.reduce((acc, stateNode) => {
|
|
2178
|
+
if (stateNode.meta !== undefined) {
|
|
2179
|
+
acc[stateNode.id] = stateNode.meta;
|
|
2180
|
+
}
|
|
2181
|
+
return acc;
|
|
2182
|
+
}, {});
|
|
2183
|
+
};
|
|
2184
|
+
function createMachineSnapshot(config, machine) {
|
|
2185
|
+
return {
|
|
2186
|
+
status: config.status,
|
|
2187
|
+
output: config.output,
|
|
2188
|
+
error: config.error,
|
|
2189
|
+
machine,
|
|
2190
|
+
context: config.context,
|
|
2191
|
+
_nodes: config._nodes,
|
|
2192
|
+
value: getStateValue(machine.root, config._nodes),
|
|
2193
|
+
tags: new Set(config._nodes.flatMap(sn => sn.tags)),
|
|
2194
|
+
children: config.children,
|
|
2195
|
+
historyValue: config.historyValue || {},
|
|
2196
|
+
matches: machineSnapshotMatches,
|
|
2197
|
+
hasTag: machineSnapshotHasTag,
|
|
2198
|
+
can: machineSnapshotCan,
|
|
2199
|
+
getMeta: machineSnapshotGetMeta,
|
|
2200
|
+
toJSON: machineSnapshotToJSON
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
function cloneMachineSnapshot(snapshot, config = {}) {
|
|
2204
|
+
return createMachineSnapshot({
|
|
2205
|
+
...snapshot,
|
|
2206
|
+
...config
|
|
2207
|
+
}, snapshot.machine);
|
|
2208
|
+
}
|
|
2209
|
+
function getPersistedSnapshot(snapshot, options) {
|
|
2210
|
+
const {
|
|
2211
|
+
_nodes: nodes,
|
|
2212
|
+
tags,
|
|
2213
|
+
machine,
|
|
2214
|
+
children,
|
|
2215
|
+
context,
|
|
2216
|
+
can,
|
|
2217
|
+
hasTag,
|
|
2218
|
+
matches,
|
|
2219
|
+
getMeta,
|
|
2220
|
+
toJSON,
|
|
2221
|
+
...jsonValues
|
|
2222
|
+
} = snapshot;
|
|
2223
|
+
const childrenJson = {};
|
|
2224
|
+
for (const id in children) {
|
|
2225
|
+
const child = children[id];
|
|
2226
|
+
childrenJson[id] = {
|
|
2227
|
+
snapshot: child.getPersistedSnapshot(options),
|
|
2228
|
+
src: child.src,
|
|
2229
|
+
systemId: child._systemId,
|
|
2230
|
+
syncSnapshot: child._syncSnapshot
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
const persisted = {
|
|
2234
|
+
...jsonValues,
|
|
2235
|
+
context: persistContext(context),
|
|
2236
|
+
children: childrenJson
|
|
2237
|
+
};
|
|
2238
|
+
return persisted;
|
|
2239
|
+
}
|
|
2240
|
+
function persistContext(contextPart) {
|
|
2241
|
+
let copy;
|
|
2242
|
+
for (const key in contextPart) {
|
|
2243
|
+
const value = contextPart[key];
|
|
2244
|
+
if (value && typeof value === 'object') {
|
|
2245
|
+
if ('sessionId' in value && 'send' in value && 'ref' in value) {
|
|
2246
|
+
copy ??= Array.isArray(contextPart) ? contextPart.slice() : {
|
|
2247
|
+
...contextPart
|
|
2248
|
+
};
|
|
2249
|
+
copy[key] = {
|
|
2250
|
+
xstate$$type: $$ACTOR_TYPE,
|
|
2251
|
+
id: value.id
|
|
2252
|
+
};
|
|
2253
|
+
} else {
|
|
2254
|
+
const result = persistContext(value);
|
|
2255
|
+
if (result !== value) {
|
|
2256
|
+
copy ??= Array.isArray(contextPart) ? contextPart.slice() : {
|
|
2257
|
+
...contextPart
|
|
2258
|
+
};
|
|
2259
|
+
copy[key] = result;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
return copy ?? contextPart;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
function resolveRaise(_, snapshot, args, actionParams, {
|
|
2268
|
+
event: eventOrExpr,
|
|
2269
|
+
id,
|
|
2270
|
+
delay
|
|
2271
|
+
}, {
|
|
2272
|
+
internalQueue
|
|
2273
|
+
}) {
|
|
2274
|
+
const delaysMap = snapshot.machine.implementations.delays;
|
|
2275
|
+
if (typeof eventOrExpr === 'string') {
|
|
2276
|
+
throw new Error(`Only event objects may be used with raise; use raise({ type: "${eventOrExpr}" }) instead`);
|
|
2277
|
+
}
|
|
2278
|
+
const resolvedEvent = typeof eventOrExpr === 'function' ? eventOrExpr(args, actionParams) : eventOrExpr;
|
|
2279
|
+
let resolvedDelay;
|
|
2280
|
+
if (typeof delay === 'string') {
|
|
2281
|
+
const configDelay = delaysMap && delaysMap[delay];
|
|
2282
|
+
resolvedDelay = typeof configDelay === 'function' ? configDelay(args, actionParams) : configDelay;
|
|
2283
|
+
} else {
|
|
2284
|
+
resolvedDelay = typeof delay === 'function' ? delay(args, actionParams) : delay;
|
|
2285
|
+
}
|
|
2286
|
+
if (typeof resolvedDelay !== 'number') {
|
|
2287
|
+
internalQueue.push(resolvedEvent);
|
|
2288
|
+
}
|
|
2289
|
+
return [snapshot, {
|
|
2290
|
+
event: resolvedEvent,
|
|
2291
|
+
id,
|
|
2292
|
+
delay: resolvedDelay
|
|
2293
|
+
}];
|
|
2294
|
+
}
|
|
2295
|
+
function executeRaise(actorScope, params) {
|
|
2296
|
+
if (typeof params.delay === 'number') {
|
|
2297
|
+
actorScope.self.delaySend(params);
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* Raises an event. This places the event in the internal event queue, so that
|
|
2303
|
+
* the event is immediately consumed by the machine in the current step.
|
|
2304
|
+
*
|
|
2305
|
+
* @param eventType The event to raise.
|
|
2306
|
+
*/
|
|
2307
|
+
function raise(eventOrExpr, options) {
|
|
2308
|
+
function raise(args, params) {
|
|
2309
|
+
}
|
|
2310
|
+
raise.type = 'xstate.raise';
|
|
2311
|
+
raise.event = eventOrExpr;
|
|
2312
|
+
raise.id = options?.id;
|
|
2313
|
+
raise.delay = options?.delay;
|
|
2314
|
+
raise.resolve = resolveRaise;
|
|
2315
|
+
raise.execute = executeRaise;
|
|
2316
|
+
return raise;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
exports.$$ACTOR_TYPE = $$ACTOR_TYPE;
|
|
2320
|
+
exports.Actor = Actor;
|
|
2321
|
+
exports.NULL_EVENT = NULL_EVENT;
|
|
2322
|
+
exports.ProcessingStatus = ProcessingStatus;
|
|
2323
|
+
exports.STATE_DELIMITER = STATE_DELIMITER;
|
|
2324
|
+
exports.XSTATE_ERROR = XSTATE_ERROR;
|
|
2325
|
+
exports.XSTATE_STOP = XSTATE_STOP;
|
|
2326
|
+
exports.and = and;
|
|
2327
|
+
exports.cancel = cancel;
|
|
2328
|
+
exports.cloneMachineSnapshot = cloneMachineSnapshot;
|
|
2329
|
+
exports.createActor = createActor;
|
|
2330
|
+
exports.createErrorActorEvent = createErrorActorEvent;
|
|
2331
|
+
exports.createInitEvent = createInitEvent;
|
|
2332
|
+
exports.createInvokeId = createInvokeId;
|
|
2333
|
+
exports.createMachineSnapshot = createMachineSnapshot;
|
|
2334
|
+
exports.evaluateGuard = evaluateGuard;
|
|
2335
|
+
exports.formatInitialTransition = formatInitialTransition;
|
|
2336
|
+
exports.formatTransition = formatTransition;
|
|
2337
|
+
exports.formatTransitions = formatTransitions;
|
|
2338
|
+
exports.getAllOwnEventDescriptors = getAllOwnEventDescriptors;
|
|
2339
|
+
exports.getAllStateNodes = getAllStateNodes;
|
|
2340
|
+
exports.getCandidates = getCandidates;
|
|
2341
|
+
exports.getDelayedTransitions = getDelayedTransitions;
|
|
2342
|
+
exports.getInitialStateNodes = getInitialStateNodes;
|
|
2343
|
+
exports.getPersistedSnapshot = getPersistedSnapshot;
|
|
2344
|
+
exports.getStateNodeByPath = getStateNodeByPath;
|
|
2345
|
+
exports.getStateNodes = getStateNodes;
|
|
2346
|
+
exports.interpret = interpret;
|
|
2347
|
+
exports.isInFinalState = isInFinalState;
|
|
2348
|
+
exports.isMachineSnapshot = isMachineSnapshot;
|
|
2349
|
+
exports.isStateId = isStateId;
|
|
2350
|
+
exports.macrostep = macrostep;
|
|
2351
|
+
exports.mapValues = mapValues;
|
|
2352
|
+
exports.matchesState = matchesState;
|
|
2353
|
+
exports.microstep = microstep;
|
|
2354
|
+
exports.not = not;
|
|
2355
|
+
exports.or = or;
|
|
2356
|
+
exports.pathToStateValue = pathToStateValue;
|
|
2357
|
+
exports.raise = raise;
|
|
2358
|
+
exports.resolveActionsAndContext = resolveActionsAndContext;
|
|
2359
|
+
exports.resolveReferencedActor = resolveReferencedActor;
|
|
2360
|
+
exports.resolveStateValue = resolveStateValue;
|
|
2361
|
+
exports.spawnChild = spawnChild;
|
|
2362
|
+
exports.stateIn = stateIn;
|
|
2363
|
+
exports.stop = stop;
|
|
2364
|
+
exports.stopChild = stopChild;
|
|
2365
|
+
exports.toArray = toArray;
|
|
2366
|
+
exports.toObserver = toObserver;
|
|
2367
|
+
exports.toTransitionConfigArray = toTransitionConfigArray;
|
|
2368
|
+
exports.transitionNode = transitionNode;
|