xstate 5.32.1 → 6.0.0-alpha.2
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 +7 -8
- package/dist/StateMachine-2dcdb1f5.esm.js +720 -0
- package/dist/StateMachine-2ea0a7fa.development.esm.js +737 -0
- package/dist/StateMachine-64813f46.cjs.js +723 -0
- package/dist/StateMachine-8d22a79c.development.cjs.js +740 -0
- package/dist/declarations/src/State.d.ts +11 -13
- package/dist/declarations/src/StateMachine.d.ts +27 -21
- package/dist/declarations/src/StateNode.d.ts +18 -54
- package/dist/declarations/src/actions.d.ts +14 -9
- package/dist/declarations/src/actors/callback.d.ts +42 -16
- package/dist/declarations/src/actors/index.d.ts +9 -6
- package/dist/declarations/src/actors/listener.d.ts +18 -0
- package/dist/declarations/src/actors/logic.d.ts +105 -0
- package/dist/declarations/src/actors/observable.d.ts +72 -27
- package/dist/declarations/src/actors/promise.d.ts +133 -48
- package/dist/declarations/src/actors/subscription.d.ts +22 -0
- package/dist/declarations/src/atom.d.ts +81 -0
- package/dist/declarations/src/createActor.d.ts +31 -33
- package/dist/declarations/src/createMachine.d.ts +35 -7
- package/dist/declarations/src/createMachineFromConfig.d.ts +176 -0
- package/dist/declarations/src/graph/graph.d.ts +4 -2
- package/dist/declarations/src/index.d.ts +12 -8
- package/dist/declarations/src/inspection.d.ts +84 -30
- package/dist/declarations/src/mapState.d.ts +4 -2
- package/dist/declarations/src/schema.types.d.ts +90 -0
- package/dist/declarations/src/serialize.d.ts +44 -0
- package/dist/declarations/src/setup.d.ts +251 -135
- package/dist/declarations/src/spawn.d.ts +6 -31
- package/dist/declarations/src/stateUtils.d.ts +21 -19
- package/dist/declarations/src/system.d.ts +8 -7
- package/dist/declarations/src/transition.d.ts +5 -5
- package/dist/declarations/src/types.d.ts +405 -405
- package/dist/declarations/src/types.v6.d.ts +392 -0
- package/dist/declarations/src/utils.d.ts +4 -3
- package/dist/index-93edd3bd.esm.js +4909 -0
- package/dist/index-9cb3b3a0.cjs.js +4968 -0
- package/dist/index-e61170ba.development.esm.js +4948 -0
- package/dist/index-f6ef20d1.development.cjs.js +5007 -0
- package/dist/xstate-actors.cjs.js +12 -838
- package/dist/xstate-actors.cjs.mjs +10 -5
- package/dist/xstate-actors.development.cjs.js +12 -838
- package/dist/xstate-actors.development.cjs.mjs +10 -5
- package/dist/xstate-actors.development.esm.js +1 -836
- package/dist/xstate-actors.esm.js +1 -836
- package/dist/xstate-actors.umd.min.js +1 -1
- package/dist/xstate-actors.umd.min.js.map +1 -1
- package/dist/xstate-graph.cjs.js +18 -29
- package/dist/xstate-graph.development.cjs.js +18 -29
- package/dist/xstate-graph.development.esm.js +15 -26
- package/dist/xstate-graph.esm.js +15 -26
- package/dist/xstate-graph.umd.min.js +1 -1
- package/dist/xstate-graph.umd.min.js.map +1 -1
- package/dist/xstate.cjs.js +1316 -112
- package/dist/xstate.cjs.mjs +22 -22
- package/dist/xstate.development.cjs.js +1316 -112
- package/dist/xstate.development.cjs.mjs +22 -22
- package/dist/xstate.development.esm.js +1278 -76
- package/dist/xstate.esm.js +1278 -76
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/package.json +3 -47
- package/dist/StateMachine-45e79c35.esm.js +0 -560
- package/dist/StateMachine-be2e51a3.development.cjs.js +0 -569
- package/dist/StateMachine-e0178d48.development.esm.js +0 -566
- package/dist/StateMachine-e6732977.cjs.js +0 -563
- package/dist/assign-2aa58daa.esm.js +0 -127
- package/dist/assign-927227d6.development.cjs.js +0 -135
- package/dist/assign-e8f2bd75.cjs.js +0 -129
- package/dist/assign-f338cee3.development.esm.js +0 -133
- package/dist/declarations/src/actions/assign.d.ts +0 -45
- package/dist/declarations/src/actions/cancel.d.ts +0 -39
- package/dist/declarations/src/actions/emit.d.ts +0 -44
- package/dist/declarations/src/actions/enqueueActions.d.ts +0 -57
- package/dist/declarations/src/actions/log.d.ts +0 -16
- package/dist/declarations/src/actions/raise.d.ts +0 -21
- package/dist/declarations/src/actions/send.d.ts +0 -42
- package/dist/declarations/src/actions/spawnChild.d.ts +0 -34
- package/dist/declarations/src/actions/stopChild.d.ts +0 -19
- package/dist/declarations/src/actors/transition.d.ts +0 -143
- package/dist/declarations/src/dev/index.d.ts +0 -14
- package/dist/declarations/src/guards.d.ts +0 -128
- package/dist/log-410e5e55.development.esm.js +0 -429
- package/dist/log-7776fcf2.cjs.js +0 -409
- package/dist/log-c2e11c01.development.cjs.js +0 -437
- package/dist/log-fa75ec2d.esm.js +0 -401
- package/dist/raise-74097812.development.esm.js +0 -2823
- package/dist/raise-7c948725.cjs.js +0 -2816
- package/dist/raise-964cd4e9.development.cjs.js +0 -2878
- package/dist/raise-e974d1c9.esm.js +0 -2761
- package/dist/xstate-actions.cjs.d.mts +0 -2
- package/dist/xstate-actions.cjs.d.ts +0 -2
- package/dist/xstate-actions.cjs.js +0 -23
- package/dist/xstate-actions.cjs.mjs +0 -14
- package/dist/xstate-actions.development.cjs.js +0 -23
- package/dist/xstate-actions.development.cjs.mjs +0 -14
- package/dist/xstate-actions.development.esm.js +0 -4
- package/dist/xstate-actions.esm.js +0 -4
- package/dist/xstate-actions.umd.min.js +0 -2
- package/dist/xstate-actions.umd.min.js.map +0 -1
- package/dist/xstate-dev.cjs.d.mts +0 -2
- package/dist/xstate-dev.cjs.d.ts +0 -2
- package/dist/xstate-dev.cjs.js +0 -48
- package/dist/xstate-dev.cjs.mjs +0 -5
- package/dist/xstate-dev.development.cjs.js +0 -51
- package/dist/xstate-dev.development.cjs.mjs +0 -5
- package/dist/xstate-dev.development.esm.js +0 -45
- package/dist/xstate-dev.esm.js +0 -42
- package/dist/xstate-dev.umd.min.js +0 -2
- package/dist/xstate-dev.umd.min.js.map +0 -1
- package/dist/xstate-guards.cjs.d.mts +0 -2
- package/dist/xstate-guards.cjs.d.ts +0 -2
- package/dist/xstate-guards.cjs.js +0 -14
- package/dist/xstate-guards.cjs.mjs +0 -7
- package/dist/xstate-guards.development.cjs.js +0 -14
- package/dist/xstate-guards.development.cjs.mjs +0 -7
- package/dist/xstate-guards.development.esm.js +0 -2
- package/dist/xstate-guards.esm.js +0 -2
- package/dist/xstate-guards.umd.min.js +0 -2
- package/dist/xstate-guards.umd.min.js.map +0 -1
|
@@ -0,0 +1,5007 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reactivity interop between actors and atoms.
|
|
5
|
+
*
|
|
6
|
+
* `Actor#get()` calls this hook (when installed) so that computed atoms can
|
|
7
|
+
* track actor reads as dependencies. The hook is installed by `atom.ts` on
|
|
8
|
+
* module evaluation, which only happens when atoms are actually used — apps
|
|
9
|
+
* that never import atoms don't bundle the reactive system.
|
|
10
|
+
*/
|
|
11
|
+
let onActorRead;
|
|
12
|
+
function installActorReadHook(hook) {
|
|
13
|
+
onActorRead = hook;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class Mailbox {
|
|
17
|
+
constructor(_process) {
|
|
18
|
+
this._process = _process;
|
|
19
|
+
this._active = false;
|
|
20
|
+
this._current = null;
|
|
21
|
+
this._last = null;
|
|
22
|
+
}
|
|
23
|
+
start() {
|
|
24
|
+
this._active = true;
|
|
25
|
+
this.flush();
|
|
26
|
+
}
|
|
27
|
+
clear() {
|
|
28
|
+
// we can't set _current to null because we might be currently processing
|
|
29
|
+
// and enqueue following clear shouldn't start processing the enqueued item immediately
|
|
30
|
+
if (this._current) {
|
|
31
|
+
this._current.next = null;
|
|
32
|
+
this._last = this._current;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
enqueue(event) {
|
|
36
|
+
const enqueued = {
|
|
37
|
+
value: event,
|
|
38
|
+
next: null
|
|
39
|
+
};
|
|
40
|
+
if (this._current) {
|
|
41
|
+
this._last.next = enqueued;
|
|
42
|
+
this._last = enqueued;
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this._current = enqueued;
|
|
46
|
+
this._last = enqueued;
|
|
47
|
+
if (this._active) {
|
|
48
|
+
this.flush();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
flush() {
|
|
52
|
+
while (this._current) {
|
|
53
|
+
// atm the given _process is responsible for implementing proper try/catch handling
|
|
54
|
+
// we assume here that this won't throw in a way that can affect this mailbox
|
|
55
|
+
const consumed = this._current;
|
|
56
|
+
this._process(consumed.value);
|
|
57
|
+
this._current = consumed.next;
|
|
58
|
+
}
|
|
59
|
+
this._last = null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const STATE_DELIMITER = '.';
|
|
64
|
+
const TARGETLESS_KEY = '';
|
|
65
|
+
const NULL_EVENT = '';
|
|
66
|
+
const STATE_IDENTIFIER = '#';
|
|
67
|
+
const WILDCARD = '*';
|
|
68
|
+
const XSTATE_INIT = '@xstate.init';
|
|
69
|
+
const XSTATE_STOP = '@xstate.stop';
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Returns an event that represents an implicit event that is sent after the
|
|
73
|
+
* specified `delay`.
|
|
74
|
+
*
|
|
75
|
+
* @param delayRef The delay in milliseconds
|
|
76
|
+
* @param id The state node ID where this event is handled
|
|
77
|
+
*/
|
|
78
|
+
function createAfterEvent(delayRef, id) {
|
|
79
|
+
return {
|
|
80
|
+
type: `xstate.after.${delayRef}.${id}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Returns an event that represents an implicit state-level timeout. Fired when
|
|
86
|
+
* a state's `timeout` duration elapses without the state being exited.
|
|
87
|
+
*
|
|
88
|
+
* @param id The state node ID where this timeout is configured
|
|
89
|
+
*/
|
|
90
|
+
function createTimeoutEvent(id) {
|
|
91
|
+
return {
|
|
92
|
+
type: `xstate.timeout.${id}`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Returns an event that represents an implicit invoke-level timeout. Fired when
|
|
98
|
+
* an invoked actor has not completed within its `timeout` duration.
|
|
99
|
+
*
|
|
100
|
+
* @param invokeId The invoked actor's ID
|
|
101
|
+
*/
|
|
102
|
+
function createInvokeTimeoutEvent(invokeId) {
|
|
103
|
+
return {
|
|
104
|
+
type: `xstate.timeout.actor.${invokeId}`
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns an event that represents that a final state node has been reached in
|
|
110
|
+
* the parent state node.
|
|
111
|
+
*
|
|
112
|
+
* @param id The final state node's parent state node `id`
|
|
113
|
+
* @param output The data to pass into the event
|
|
114
|
+
*/
|
|
115
|
+
function createDoneStateEvent(id, output) {
|
|
116
|
+
return {
|
|
117
|
+
type: `xstate.done.state.${id}`,
|
|
118
|
+
output
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Returns an event that represents that an invoked service has terminated.
|
|
124
|
+
*
|
|
125
|
+
* An invoked service is terminated when it has reached a top-level final state
|
|
126
|
+
* node, but not when it is canceled.
|
|
127
|
+
*
|
|
128
|
+
* @param invokeId The invoked service ID
|
|
129
|
+
* @param output The data to pass into the event
|
|
130
|
+
*/
|
|
131
|
+
function createDoneActorEvent(invokeId, output) {
|
|
132
|
+
return {
|
|
133
|
+
type: `xstate.done.actor.${invokeId}`,
|
|
134
|
+
output,
|
|
135
|
+
actorId: invokeId
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function createErrorActorEvent(id, error) {
|
|
139
|
+
return {
|
|
140
|
+
type: `xstate.error.actor.${id}`,
|
|
141
|
+
error,
|
|
142
|
+
actorId: id
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function createInitEvent(input) {
|
|
146
|
+
return {
|
|
147
|
+
type: XSTATE_INIT,
|
|
148
|
+
input
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* This function makes sure that unhandled errors are thrown in a separate
|
|
154
|
+
* macrotask. It allows those errors to be detected by global error handlers and
|
|
155
|
+
* reported to bug tracking services without interrupting our own stack of
|
|
156
|
+
* execution.
|
|
157
|
+
*
|
|
158
|
+
* @param err Error to be thrown
|
|
159
|
+
*/
|
|
160
|
+
function reportUnhandledError(err) {
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
throw err;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const symbolObservable = (() => typeof Symbol === 'function' && Symbol.observable || '@@observable')();
|
|
167
|
+
|
|
168
|
+
const builtInActions = {
|
|
169
|
+
['@xstate.start']: actor => {
|
|
170
|
+
actor.start();
|
|
171
|
+
},
|
|
172
|
+
['@xstate.raise']: (actorScope, event, options) => {
|
|
173
|
+
actorScope.system.scheduler.schedule(actorScope.self, actorScope.self, event, options?.delay ?? 0, options?.id);
|
|
174
|
+
},
|
|
175
|
+
['@xstate.sendTo']: (actorScope, actor, event, options) => {
|
|
176
|
+
if (typeof event === 'string') {
|
|
177
|
+
throw new Error(// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
178
|
+
`Only event objects may be used with sendTo; use sendTo({ type: "${event}" }) instead` );
|
|
179
|
+
}
|
|
180
|
+
if (options?.delay !== undefined) {
|
|
181
|
+
actorScope.system.scheduler.schedule(actorScope.self, actor, event, options?.delay ?? 0, options?.id);
|
|
182
|
+
} else {
|
|
183
|
+
actorScope.defer(() => {
|
|
184
|
+
actorScope.system._relay(actorScope.self, actor, event);
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
['@xstate.cancel']: (actorScope, sendId) => {
|
|
189
|
+
actorScope.system.scheduler.cancel(actorScope.self, sendId);
|
|
190
|
+
},
|
|
191
|
+
['@xstate.stopChild']: (actorScope, actor) => {
|
|
192
|
+
actorScope.stopChild(actor);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const subscriptions = /* #__PURE__ */new WeakMap();
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Internal factory for actor logic that attaches to another actor on start
|
|
200
|
+
* (`enq.listen()` / `enq.subscribeTo()`) and detaches when stopped.
|
|
201
|
+
*/
|
|
202
|
+
function createAttachedLogic(attach) {
|
|
203
|
+
return {
|
|
204
|
+
start: (state, {
|
|
205
|
+
self,
|
|
206
|
+
system
|
|
207
|
+
}) => {
|
|
208
|
+
// Don't attach if the target doesn't exist, or (for actors) is stopped.
|
|
209
|
+
// Atoms have no `getSnapshot` / lifecycle, so they always attach.
|
|
210
|
+
const target = state.input.actor;
|
|
211
|
+
if (!target || typeof target.getSnapshot === 'function' && target.getSnapshot().status === 'stopped') {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const subscription = attach(state.input, {
|
|
215
|
+
self,
|
|
216
|
+
system
|
|
217
|
+
});
|
|
218
|
+
if (!subscription) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Attached actors are not registered as children (their `input` holds
|
|
223
|
+
// non-serializable function mappers), so a parent stop does not cascade
|
|
224
|
+
// to them. Subscribe to the parent and tear down when it stops/errors,
|
|
225
|
+
// so the subscription never relays to a stopped parent or leaks the
|
|
226
|
+
// source subscription.
|
|
227
|
+
let parentSubscription;
|
|
228
|
+
let torndown = false;
|
|
229
|
+
const teardown = () => {
|
|
230
|
+
if (torndown) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
torndown = true;
|
|
234
|
+
subscription.unsubscribe();
|
|
235
|
+
parentSubscription?.unsubscribe();
|
|
236
|
+
};
|
|
237
|
+
const parent = self._parent;
|
|
238
|
+
if (parent) {
|
|
239
|
+
parentSubscription = parent.subscribe({
|
|
240
|
+
complete: teardown,
|
|
241
|
+
error: teardown
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
subscriptions.set(self, {
|
|
245
|
+
unsubscribe: teardown
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
transition: (state, event, {
|
|
249
|
+
self
|
|
250
|
+
}) => {
|
|
251
|
+
if (event.type === XSTATE_STOP) {
|
|
252
|
+
subscriptions.get(self)?.unsubscribe();
|
|
253
|
+
subscriptions.delete(self);
|
|
254
|
+
return {
|
|
255
|
+
...state,
|
|
256
|
+
status: 'stopped',
|
|
257
|
+
error: undefined
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return state;
|
|
261
|
+
},
|
|
262
|
+
getInitialSnapshot: (_, input) => ({
|
|
263
|
+
status: 'active',
|
|
264
|
+
output: undefined,
|
|
265
|
+
error: undefined,
|
|
266
|
+
input
|
|
267
|
+
}),
|
|
268
|
+
getPersistedSnapshot: snapshot => snapshot,
|
|
269
|
+
restoreSnapshot: snapshot => snapshot
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Relays a mapped event to the attached actor's parent, unless the attached
|
|
275
|
+
* actor has been stopped (the mapper is not called in that case).
|
|
276
|
+
*/
|
|
277
|
+
function relayMappedToParent(self, system, getEvent) {
|
|
278
|
+
if (self.getSnapshot().status === 'stopped' || !self._parent) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
system._relay(self, self._parent, getEvent());
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Creates actor logic for listening to emitted events from another actor. Used
|
|
286
|
+
* internally by `enq.listen()`.
|
|
287
|
+
*/
|
|
288
|
+
function createListenerLogic() {
|
|
289
|
+
return createAttachedLogic(({
|
|
290
|
+
actor,
|
|
291
|
+
eventType,
|
|
292
|
+
mapper
|
|
293
|
+
}, {
|
|
294
|
+
self,
|
|
295
|
+
system
|
|
296
|
+
}) => {
|
|
297
|
+
// Determine the subscription type:
|
|
298
|
+
// - For exact matches or '*', subscribe directly
|
|
299
|
+
// - For partial wildcards ('data.*'), subscribe to '*' and filter
|
|
300
|
+
const isPartialWildcard = eventType !== '*' && eventType.endsWith('.*');
|
|
301
|
+
return actor.on(isPartialWildcard ? '*' : eventType, emittedEvent => {
|
|
302
|
+
if (isPartialWildcard && !matchesEventDescriptor(emittedEvent.type, eventType)) {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
relayMappedToParent(self, system, () => mapper(emittedEvent));
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Singleton logic instance
|
|
311
|
+
const listenerLogic = /* #__PURE__ */createListenerLogic();
|
|
312
|
+
|
|
313
|
+
/* eslint-disable */
|
|
314
|
+
// Adapted from Alien Signals
|
|
315
|
+
// https://github.com/stackblitz/alien-signals/
|
|
316
|
+
|
|
317
|
+
let ReactiveFlags = /*#__PURE__*/function (ReactiveFlags) {
|
|
318
|
+
ReactiveFlags[ReactiveFlags["None"] = 0] = "None";
|
|
319
|
+
ReactiveFlags[ReactiveFlags["Mutable"] = 1] = "Mutable";
|
|
320
|
+
ReactiveFlags[ReactiveFlags["Watching"] = 2] = "Watching";
|
|
321
|
+
ReactiveFlags[ReactiveFlags["RecursedCheck"] = 4] = "RecursedCheck";
|
|
322
|
+
ReactiveFlags[ReactiveFlags["Recursed"] = 8] = "Recursed";
|
|
323
|
+
ReactiveFlags[ReactiveFlags["Dirty"] = 16] = "Dirty";
|
|
324
|
+
ReactiveFlags[ReactiveFlags["Pending"] = 32] = "Pending";
|
|
325
|
+
return ReactiveFlags;
|
|
326
|
+
}({});
|
|
327
|
+
function createReactiveSystem({
|
|
328
|
+
update,
|
|
329
|
+
notify,
|
|
330
|
+
unwatched
|
|
331
|
+
}) {
|
|
332
|
+
return {
|
|
333
|
+
link,
|
|
334
|
+
unlink,
|
|
335
|
+
propagate,
|
|
336
|
+
checkDirty,
|
|
337
|
+
shallowPropagate
|
|
338
|
+
};
|
|
339
|
+
function link(dep, sub, version) {
|
|
340
|
+
const prevDep = sub.depsTail;
|
|
341
|
+
if (prevDep !== undefined && prevDep.dep === dep) {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps;
|
|
345
|
+
if (nextDep !== undefined && nextDep.dep === dep) {
|
|
346
|
+
nextDep.version = version;
|
|
347
|
+
sub.depsTail = nextDep;
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const prevSub = dep.subsTail;
|
|
351
|
+
if (prevSub !== undefined && prevSub.version === version && prevSub.sub === sub) {
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const newLink = sub.depsTail = dep.subsTail = {
|
|
355
|
+
version,
|
|
356
|
+
dep,
|
|
357
|
+
sub,
|
|
358
|
+
prevDep,
|
|
359
|
+
nextDep,
|
|
360
|
+
prevSub,
|
|
361
|
+
nextSub: undefined
|
|
362
|
+
};
|
|
363
|
+
if (nextDep !== undefined) {
|
|
364
|
+
nextDep.prevDep = newLink;
|
|
365
|
+
}
|
|
366
|
+
if (prevDep !== undefined) {
|
|
367
|
+
prevDep.nextDep = newLink;
|
|
368
|
+
} else {
|
|
369
|
+
sub.deps = newLink;
|
|
370
|
+
}
|
|
371
|
+
if (prevSub !== undefined) {
|
|
372
|
+
prevSub.nextSub = newLink;
|
|
373
|
+
} else {
|
|
374
|
+
dep.subs = newLink;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function unlink(link, sub = link.sub) {
|
|
378
|
+
const dep = link.dep;
|
|
379
|
+
const prevDep = link.prevDep;
|
|
380
|
+
const nextDep = link.nextDep;
|
|
381
|
+
const nextSub = link.nextSub;
|
|
382
|
+
const prevSub = link.prevSub;
|
|
383
|
+
if (nextDep !== undefined) {
|
|
384
|
+
nextDep.prevDep = prevDep;
|
|
385
|
+
} else {
|
|
386
|
+
sub.depsTail = prevDep;
|
|
387
|
+
}
|
|
388
|
+
if (prevDep !== undefined) {
|
|
389
|
+
prevDep.nextDep = nextDep;
|
|
390
|
+
} else {
|
|
391
|
+
sub.deps = nextDep;
|
|
392
|
+
}
|
|
393
|
+
if (nextSub !== undefined) {
|
|
394
|
+
nextSub.prevSub = prevSub;
|
|
395
|
+
} else {
|
|
396
|
+
dep.subsTail = prevSub;
|
|
397
|
+
}
|
|
398
|
+
if (prevSub !== undefined) {
|
|
399
|
+
prevSub.nextSub = nextSub;
|
|
400
|
+
} else if ((dep.subs = nextSub) === undefined) {
|
|
401
|
+
unwatched(dep);
|
|
402
|
+
}
|
|
403
|
+
return nextDep;
|
|
404
|
+
}
|
|
405
|
+
function propagate(link) {
|
|
406
|
+
let next = link.nextSub;
|
|
407
|
+
let stack;
|
|
408
|
+
top: do {
|
|
409
|
+
const sub = link.sub;
|
|
410
|
+
let flags = sub.flags;
|
|
411
|
+
if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending))) {
|
|
412
|
+
sub.flags = flags | ReactiveFlags.Pending;
|
|
413
|
+
} else if (!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))) {
|
|
414
|
+
flags = ReactiveFlags.None;
|
|
415
|
+
} else if (!(flags & ReactiveFlags.RecursedCheck)) {
|
|
416
|
+
sub.flags = flags & ~ReactiveFlags.Recursed | ReactiveFlags.Pending;
|
|
417
|
+
} else if (!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) && isValidLink(link, sub)) {
|
|
418
|
+
sub.flags = flags | (ReactiveFlags.Recursed | ReactiveFlags.Pending);
|
|
419
|
+
flags &= ReactiveFlags.Mutable;
|
|
420
|
+
} else {
|
|
421
|
+
flags = ReactiveFlags.None;
|
|
422
|
+
}
|
|
423
|
+
if (flags & ReactiveFlags.Watching) {
|
|
424
|
+
notify(sub);
|
|
425
|
+
}
|
|
426
|
+
if (flags & ReactiveFlags.Mutable) {
|
|
427
|
+
const subSubs = sub.subs;
|
|
428
|
+
if (subSubs !== undefined) {
|
|
429
|
+
const nextSub = (link = subSubs).nextSub;
|
|
430
|
+
if (nextSub !== undefined) {
|
|
431
|
+
stack = {
|
|
432
|
+
value: next,
|
|
433
|
+
prev: stack
|
|
434
|
+
};
|
|
435
|
+
next = nextSub;
|
|
436
|
+
}
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if ((link = next) !== undefined) {
|
|
441
|
+
next = link.nextSub;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
while (stack !== undefined) {
|
|
445
|
+
link = stack.value;
|
|
446
|
+
stack = stack.prev;
|
|
447
|
+
if (link !== undefined) {
|
|
448
|
+
next = link.nextSub;
|
|
449
|
+
continue top;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
break;
|
|
453
|
+
} while (true);
|
|
454
|
+
}
|
|
455
|
+
function checkDirty(link, sub) {
|
|
456
|
+
let stack;
|
|
457
|
+
let checkDepth = 0;
|
|
458
|
+
let dirty = false;
|
|
459
|
+
top: do {
|
|
460
|
+
const dep = link.dep;
|
|
461
|
+
const flags = dep.flags;
|
|
462
|
+
if (sub.flags & ReactiveFlags.Dirty) {
|
|
463
|
+
dirty = true;
|
|
464
|
+
} else if ((flags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) === (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) {
|
|
465
|
+
if (update(dep)) {
|
|
466
|
+
const subs = dep.subs;
|
|
467
|
+
if (subs.nextSub !== undefined) {
|
|
468
|
+
shallowPropagate(subs);
|
|
469
|
+
}
|
|
470
|
+
dirty = true;
|
|
471
|
+
}
|
|
472
|
+
} else if ((flags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) === (ReactiveFlags.Mutable | ReactiveFlags.Pending)) {
|
|
473
|
+
if (link.nextSub !== undefined || link.prevSub !== undefined) {
|
|
474
|
+
stack = {
|
|
475
|
+
value: link,
|
|
476
|
+
prev: stack
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
link = dep.deps;
|
|
480
|
+
sub = dep;
|
|
481
|
+
++checkDepth;
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
if (!dirty) {
|
|
485
|
+
const nextDep = link.nextDep;
|
|
486
|
+
if (nextDep !== undefined) {
|
|
487
|
+
link = nextDep;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
while (checkDepth--) {
|
|
492
|
+
const firstSub = sub.subs;
|
|
493
|
+
const hasMultipleSubs = firstSub.nextSub !== undefined;
|
|
494
|
+
if (hasMultipleSubs) {
|
|
495
|
+
link = stack.value;
|
|
496
|
+
stack = stack.prev;
|
|
497
|
+
} else {
|
|
498
|
+
link = firstSub;
|
|
499
|
+
}
|
|
500
|
+
if (dirty) {
|
|
501
|
+
if (update(sub)) {
|
|
502
|
+
if (hasMultipleSubs) {
|
|
503
|
+
shallowPropagate(firstSub);
|
|
504
|
+
}
|
|
505
|
+
sub = link.sub;
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
dirty = false;
|
|
509
|
+
} else {
|
|
510
|
+
sub.flags &= ~ReactiveFlags.Pending;
|
|
511
|
+
}
|
|
512
|
+
sub = link.sub;
|
|
513
|
+
const nextDep = link.nextDep;
|
|
514
|
+
if (nextDep !== undefined) {
|
|
515
|
+
link = nextDep;
|
|
516
|
+
continue top;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return dirty;
|
|
520
|
+
} while (true);
|
|
521
|
+
}
|
|
522
|
+
function shallowPropagate(link) {
|
|
523
|
+
do {
|
|
524
|
+
const sub = link.sub;
|
|
525
|
+
const flags = sub.flags;
|
|
526
|
+
if ((flags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) === ReactiveFlags.Pending) {
|
|
527
|
+
sub.flags = flags | ReactiveFlags.Dirty;
|
|
528
|
+
if ((flags & (ReactiveFlags.Watching | ReactiveFlags.RecursedCheck)) === ReactiveFlags.Watching) {
|
|
529
|
+
notify(sub);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
} while ((link = link.nextSub) !== undefined);
|
|
533
|
+
}
|
|
534
|
+
function isValidLink(checkLink, sub) {
|
|
535
|
+
let link = sub.depsTail;
|
|
536
|
+
while (link !== undefined) {
|
|
537
|
+
if (link === checkLink) {
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
link = link.prevDep;
|
|
541
|
+
}
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Nominal brand (phantom, compile-time only) marking a value as an XState atom.
|
|
548
|
+
* Real atoms get the runtime brand ({@link $$atom}) in `createAtom`; this type
|
|
549
|
+
* brand keeps atom-only APIs (e.g. `enq.subscribeTo`) from structurally
|
|
550
|
+
* accepting plain readables like `actor.select(...)` that would be dispatched
|
|
551
|
+
* down the non-atom path at runtime.
|
|
552
|
+
*/
|
|
553
|
+
|
|
554
|
+
/** Brand marking an object as an atom, so it can be detected structurally. */
|
|
555
|
+
const $$atom = /* #__PURE__ */Symbol.for('xstate.atom');
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Returns `true` if the value is an atom (writable, computed, async, or
|
|
559
|
+
* reducer). Lets actor-consuming APIs (e.g. `enq.subscribeTo`) accept atoms
|
|
560
|
+
* directly.
|
|
561
|
+
*/
|
|
562
|
+
function isAtom(value) {
|
|
563
|
+
return !!value && typeof value === 'object' && $$atom in value;
|
|
564
|
+
}
|
|
565
|
+
const queuedEffects = [];
|
|
566
|
+
let cycle = 0;
|
|
567
|
+
// Initialized lazily on first atom creation (instead of at module evaluation)
|
|
568
|
+
// so the reactive system stays tree-shakeable for apps that never use atoms —
|
|
569
|
+
|
|
570
|
+
// would be retained by consumer bundlers even when atoms are unused.
|
|
571
|
+
let link;
|
|
572
|
+
let unlink;
|
|
573
|
+
let propagate;
|
|
574
|
+
let checkDirty;
|
|
575
|
+
let shallowPropagate;
|
|
576
|
+
let reactiveSystemInstalled = false;
|
|
577
|
+
function ensureReactiveSystem() {
|
|
578
|
+
if (reactiveSystemInstalled) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
reactiveSystemInstalled = true;
|
|
582
|
+
({
|
|
583
|
+
link,
|
|
584
|
+
unlink,
|
|
585
|
+
propagate,
|
|
586
|
+
checkDirty,
|
|
587
|
+
shallowPropagate
|
|
588
|
+
} = createReactiveSystem({
|
|
589
|
+
update(atom) {
|
|
590
|
+
return atom._update();
|
|
591
|
+
},
|
|
592
|
+
notify(effect) {
|
|
593
|
+
queuedEffects[queuedEffectsLength++] = effect;
|
|
594
|
+
effect.flags &= ~ReactiveFlags.Watching;
|
|
595
|
+
},
|
|
596
|
+
unwatched(atom) {
|
|
597
|
+
if (atom.depsTail !== undefined) {
|
|
598
|
+
atom.depsTail = undefined;
|
|
599
|
+
atom.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty;
|
|
600
|
+
purgeDeps(atom);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}));
|
|
604
|
+
}
|
|
605
|
+
let notifyIndex = 0;
|
|
606
|
+
let queuedEffectsLength = 0;
|
|
607
|
+
let activeSub;
|
|
608
|
+
function purgeDeps(sub) {
|
|
609
|
+
const depsTail = sub.depsTail;
|
|
610
|
+
let dep = depsTail !== undefined ? depsTail.nextDep : sub.deps;
|
|
611
|
+
while (dep !== undefined) {
|
|
612
|
+
dep = unlink(dep, sub);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function flush() {
|
|
616
|
+
while (notifyIndex < queuedEffectsLength) {
|
|
617
|
+
const effect = queuedEffects[notifyIndex];
|
|
618
|
+
queuedEffects[notifyIndex++] = undefined;
|
|
619
|
+
effect.notify();
|
|
620
|
+
}
|
|
621
|
+
notifyIndex = 0;
|
|
622
|
+
queuedEffectsLength = 0;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/** The current state of an async atom. */
|
|
626
|
+
|
|
627
|
+
/** Options passed to an async atom getter. */
|
|
628
|
+
|
|
629
|
+
function compareAsyncAtomState(a, b) {
|
|
630
|
+
if (a.status !== b.status) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
if (a.status === 'done' && b.status === 'done') {
|
|
634
|
+
return Object.is(a.data, b.data);
|
|
635
|
+
}
|
|
636
|
+
if (a.status === 'error' && b.status === 'error') {
|
|
637
|
+
return Object.is(a.error, b.error);
|
|
638
|
+
}
|
|
639
|
+
// both 'pending'
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
function updateAsyncAtom(atom, nextValue) {
|
|
643
|
+
if (atom._update(nextValue)) {
|
|
644
|
+
const subs = atom.subs;
|
|
645
|
+
if (subs !== undefined) {
|
|
646
|
+
propagate(subs);
|
|
647
|
+
shallowPropagate(subs);
|
|
648
|
+
flush();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Creates a read-only atom whose value is loaded from an async getter.
|
|
655
|
+
*
|
|
656
|
+
* The getter receives an `AbortSignal`; when the async atom recomputes, the
|
|
657
|
+
* previous signal is aborted and stale resolutions are ignored.
|
|
658
|
+
*/
|
|
659
|
+
function createAsyncAtom(getValue, options) {
|
|
660
|
+
const ref = {};
|
|
661
|
+
let currentController;
|
|
662
|
+
let currentRunId = 0;
|
|
663
|
+
const atom = createAtom(() => {
|
|
664
|
+
currentController?.abort();
|
|
665
|
+
const controller = new AbortController();
|
|
666
|
+
const runId = ++currentRunId;
|
|
667
|
+
currentController = controller;
|
|
668
|
+
getValue({
|
|
669
|
+
signal: controller.signal
|
|
670
|
+
}).then(data => {
|
|
671
|
+
if (runId !== currentRunId || controller.signal.aborted) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
updateAsyncAtom(ref.current, {
|
|
675
|
+
status: 'done',
|
|
676
|
+
data
|
|
677
|
+
});
|
|
678
|
+
}, error => {
|
|
679
|
+
if (runId !== currentRunId || controller.signal.aborted) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
updateAsyncAtom(ref.current, {
|
|
683
|
+
status: 'error',
|
|
684
|
+
error
|
|
685
|
+
});
|
|
686
|
+
});
|
|
687
|
+
return {
|
|
688
|
+
status: 'pending'
|
|
689
|
+
};
|
|
690
|
+
}, {
|
|
691
|
+
compare: compareAsyncAtomState,
|
|
692
|
+
...options
|
|
693
|
+
});
|
|
694
|
+
ref.current = atom;
|
|
695
|
+
return atom;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Mirror atoms that bump a version whenever a tracked actor emits a snapshot,
|
|
700
|
+
* letting computed atoms depend on `actor.get()` reads.
|
|
701
|
+
*
|
|
702
|
+
* The hook is installed lazily on first atom creation (not at module
|
|
703
|
+
* evaluation) so that it remains tree-shakeable for apps that never use atoms —
|
|
704
|
+
* any tracked read necessarily happens inside an atom computation, so first-use
|
|
705
|
+
* installation is always early enough.
|
|
706
|
+
*/
|
|
707
|
+
let actorInteropInstalled = false;
|
|
708
|
+
function ensureActorInterop() {
|
|
709
|
+
if (actorInteropInstalled) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
actorInteropInstalled = true;
|
|
713
|
+
const actorVersions = new WeakMap();
|
|
714
|
+
installActorReadHook(actorRef => {
|
|
715
|
+
if (activeSub === undefined) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
let version = actorVersions.get(actorRef);
|
|
719
|
+
if (version === undefined) {
|
|
720
|
+
version = createAtom(0);
|
|
721
|
+
actorVersions.set(actorRef, version);
|
|
722
|
+
actorRef.subscribe({
|
|
723
|
+
next: () => version.set(v => v + 1),
|
|
724
|
+
error: () => {}
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
version.get();
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Creates an atom.
|
|
733
|
+
*
|
|
734
|
+
* Pass a value for a writable atom or a getter for a computed read-only atom.
|
|
735
|
+
*/
|
|
736
|
+
|
|
737
|
+
function createAtom(valueOrFn, optionsOrInput) {
|
|
738
|
+
ensureReactiveSystem();
|
|
739
|
+
ensureActorInterop();
|
|
740
|
+
const isComputed = typeof valueOrFn === 'function';
|
|
741
|
+
const getter = valueOrFn;
|
|
742
|
+
|
|
743
|
+
// Create plain object atom
|
|
744
|
+
const atom = {
|
|
745
|
+
_snapshot: isComputed ? undefined : valueOrFn,
|
|
746
|
+
subs: undefined,
|
|
747
|
+
subsTail: undefined,
|
|
748
|
+
deps: undefined,
|
|
749
|
+
depsTail: undefined,
|
|
750
|
+
flags: isComputed ? ReactiveFlags.None : ReactiveFlags.Mutable,
|
|
751
|
+
get() {
|
|
752
|
+
if (activeSub !== undefined) {
|
|
753
|
+
link(atom, activeSub, cycle);
|
|
754
|
+
}
|
|
755
|
+
return atom._snapshot;
|
|
756
|
+
},
|
|
757
|
+
subscribe(observerOrFn) {
|
|
758
|
+
const observer = typeof observerOrFn === 'function' ? {
|
|
759
|
+
next: observerOrFn
|
|
760
|
+
} : observerOrFn;
|
|
761
|
+
const observed = {
|
|
762
|
+
current: false
|
|
763
|
+
};
|
|
764
|
+
const e = effect(() => {
|
|
765
|
+
atom.get();
|
|
766
|
+
if (!observed.current) {
|
|
767
|
+
observed.current = true;
|
|
768
|
+
} else {
|
|
769
|
+
const prevSub = activeSub;
|
|
770
|
+
activeSub = undefined;
|
|
771
|
+
try {
|
|
772
|
+
observer.next?.(atom._snapshot);
|
|
773
|
+
} catch (err) {
|
|
774
|
+
reportUnhandledError(err);
|
|
775
|
+
} finally {
|
|
776
|
+
activeSub = prevSub;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// If the observer synchronously updates any of our deps we'll be
|
|
780
|
+
// marked as dirty preventing this effect from re-running. Request
|
|
781
|
+
// the value again to reconcile any dirty deps.
|
|
782
|
+
atom.get();
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
return {
|
|
786
|
+
unsubscribe: () => {
|
|
787
|
+
e.stop();
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
},
|
|
791
|
+
_update(getValue) {
|
|
792
|
+
const prevSub = activeSub;
|
|
793
|
+
const compare = optionsOrInput?.compare ?? Object.is;
|
|
794
|
+
activeSub = atom;
|
|
795
|
+
++cycle;
|
|
796
|
+
atom.depsTail = undefined;
|
|
797
|
+
if (isComputed) {
|
|
798
|
+
atom.flags = ReactiveFlags.Mutable | ReactiveFlags.RecursedCheck;
|
|
799
|
+
}
|
|
800
|
+
try {
|
|
801
|
+
const oldValue = atom._snapshot;
|
|
802
|
+
const newValue = typeof getValue === 'function' ? getValue(oldValue) : getValue === undefined && isComputed ? getter(oldValue) : getValue;
|
|
803
|
+
if (oldValue === undefined || !compare(oldValue, newValue)) {
|
|
804
|
+
atom._snapshot = newValue;
|
|
805
|
+
return true;
|
|
806
|
+
}
|
|
807
|
+
return false;
|
|
808
|
+
} finally {
|
|
809
|
+
activeSub = prevSub;
|
|
810
|
+
if (isComputed) {
|
|
811
|
+
atom.flags &= ~ReactiveFlags.RecursedCheck;
|
|
812
|
+
}
|
|
813
|
+
purgeDeps(atom);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
if (isComputed) {
|
|
818
|
+
atom.flags = ReactiveFlags.Mutable | ReactiveFlags.Dirty;
|
|
819
|
+
atom.get = function () {
|
|
820
|
+
const flags = atom.flags;
|
|
821
|
+
if (flags & ReactiveFlags.Dirty || flags & ReactiveFlags.Pending && checkDirty(atom.deps, atom)) {
|
|
822
|
+
if (atom._update()) {
|
|
823
|
+
const subs = atom.subs;
|
|
824
|
+
if (subs !== undefined) {
|
|
825
|
+
shallowPropagate(subs);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
} else if (flags & ReactiveFlags.Pending) {
|
|
829
|
+
atom.flags = flags & ~ReactiveFlags.Pending;
|
|
830
|
+
}
|
|
831
|
+
if (activeSub !== undefined) {
|
|
832
|
+
link(atom, activeSub, cycle);
|
|
833
|
+
}
|
|
834
|
+
return atom._snapshot;
|
|
835
|
+
};
|
|
836
|
+
} else {
|
|
837
|
+
atom.set = function (valueOrFn) {
|
|
838
|
+
if (atom._update(valueOrFn)) {
|
|
839
|
+
const subs = atom.subs;
|
|
840
|
+
if (subs !== undefined) {
|
|
841
|
+
propagate(subs);
|
|
842
|
+
shallowPropagate(subs);
|
|
843
|
+
flush();
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
atom[$$atom] = true;
|
|
849
|
+
return atom;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* Creates an inert atom config that can be instantiated later.
|
|
854
|
+
*
|
|
855
|
+
* Useful for framework hooks that need to create a stable local atom from
|
|
856
|
+
* component input.
|
|
857
|
+
*/
|
|
858
|
+
|
|
859
|
+
function createAtomConfig(initialValueOrFn, options) {
|
|
860
|
+
return {
|
|
861
|
+
createAtom(input) {
|
|
862
|
+
const initialValue = typeof initialValueOrFn === 'function' ? initialValueOrFn(input) : initialValueOrFn;
|
|
863
|
+
return createAtom(initialValue, options);
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/** Creates an atom whose updates are handled by a reducer function. */
|
|
869
|
+
function createReducerAtom(initialValue, reducer, options) {
|
|
870
|
+
const atom = createAtom(initialValue, options);
|
|
871
|
+
return {
|
|
872
|
+
[$$atom]: true,
|
|
873
|
+
get: atom.get.bind(atom),
|
|
874
|
+
subscribe: atom.subscribe.bind(atom),
|
|
875
|
+
send(event) {
|
|
876
|
+
const prevSub = activeSub;
|
|
877
|
+
activeSub = undefined;
|
|
878
|
+
let nextState;
|
|
879
|
+
try {
|
|
880
|
+
nextState = reducer(atom.get(), event);
|
|
881
|
+
} finally {
|
|
882
|
+
activeSub = prevSub;
|
|
883
|
+
}
|
|
884
|
+
atom.set(nextState);
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
function effect(fn) {
|
|
889
|
+
const run = () => {
|
|
890
|
+
const prevSub = activeSub;
|
|
891
|
+
activeSub = effectObj;
|
|
892
|
+
++cycle;
|
|
893
|
+
effectObj.depsTail = undefined;
|
|
894
|
+
effectObj.flags = ReactiveFlags.Watching | ReactiveFlags.RecursedCheck;
|
|
895
|
+
try {
|
|
896
|
+
return fn();
|
|
897
|
+
} finally {
|
|
898
|
+
activeSub = prevSub;
|
|
899
|
+
effectObj.flags &= ~ReactiveFlags.RecursedCheck;
|
|
900
|
+
purgeDeps(effectObj);
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
const effectObj = {
|
|
904
|
+
deps: undefined,
|
|
905
|
+
depsTail: undefined,
|
|
906
|
+
subs: undefined,
|
|
907
|
+
subsTail: undefined,
|
|
908
|
+
flags: ReactiveFlags.Watching | ReactiveFlags.RecursedCheck,
|
|
909
|
+
notify() {
|
|
910
|
+
const flags = this.flags;
|
|
911
|
+
if (flags & ReactiveFlags.Dirty || flags & ReactiveFlags.Pending && checkDirty(this.deps, this)) {
|
|
912
|
+
run();
|
|
913
|
+
} else {
|
|
914
|
+
this.flags = ReactiveFlags.Watching;
|
|
915
|
+
}
|
|
916
|
+
},
|
|
917
|
+
stop() {
|
|
918
|
+
this.flags = ReactiveFlags.None;
|
|
919
|
+
this.depsTail = undefined;
|
|
920
|
+
purgeDeps(this);
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
run();
|
|
924
|
+
return effectObj;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Creates actor logic for subscribing to lifecycle events (done/error/snapshot)
|
|
929
|
+
* from another actor. Used internally by `enq.subscribeTo()`.
|
|
930
|
+
*/
|
|
931
|
+
function createSubscriptionLogic() {
|
|
932
|
+
return createAttachedLogic(({
|
|
933
|
+
actor,
|
|
934
|
+
mappers
|
|
935
|
+
}, {
|
|
936
|
+
self,
|
|
937
|
+
system
|
|
938
|
+
}) => {
|
|
939
|
+
const {
|
|
940
|
+
done,
|
|
941
|
+
error,
|
|
942
|
+
snapshot: onSnapshot
|
|
943
|
+
} = mappers;
|
|
944
|
+
|
|
945
|
+
// Atoms emit a raw value (no `status`) and have no done/error lifecycle —
|
|
946
|
+
// pass the value straight to the `snapshot` mapper.
|
|
947
|
+
if (isAtom(actor)) {
|
|
948
|
+
if (!onSnapshot) {
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
return actor.subscribe(value => relayMappedToParent(self, system, () => onSnapshot(value)));
|
|
952
|
+
}
|
|
953
|
+
return actor.subscribe({
|
|
954
|
+
next: snapshot => {
|
|
955
|
+
if (snapshot.status === 'done' && done) {
|
|
956
|
+
relayMappedToParent(self, system, () => done(snapshot.output));
|
|
957
|
+
} else if (snapshot.status === 'error' && error) {
|
|
958
|
+
relayMappedToParent(self, system, () => error(snapshot.error));
|
|
959
|
+
} else if (snapshot.status === 'active' && onSnapshot) {
|
|
960
|
+
relayMappedToParent(self, system, () => onSnapshot(snapshot));
|
|
961
|
+
}
|
|
962
|
+
},
|
|
963
|
+
error: err => {
|
|
964
|
+
if (error) {
|
|
965
|
+
relayMappedToParent(self, system, () => error(err));
|
|
966
|
+
}
|
|
967
|
+
},
|
|
968
|
+
complete: () => {
|
|
969
|
+
// Actor completed without output (stopped); no action needed
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// Singleton logic instance
|
|
976
|
+
const subscriptionLogic = /* #__PURE__ */createSubscriptionLogic();
|
|
977
|
+
|
|
978
|
+
function pushBuiltInAction(actions, action, ...args) {
|
|
979
|
+
actions.push({
|
|
980
|
+
action,
|
|
981
|
+
args
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
function registerSpawnedChild(actor, id) {
|
|
985
|
+
return Object.assign(function registerChild(args) {
|
|
986
|
+
return {
|
|
987
|
+
children: {
|
|
988
|
+
...args.children,
|
|
989
|
+
[id]: actor
|
|
990
|
+
}
|
|
991
|
+
};
|
|
992
|
+
}, {
|
|
993
|
+
_special: true
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
function unregisterChild(actor) {
|
|
997
|
+
return Object.assign(function removeChild(args) {
|
|
998
|
+
const children = {
|
|
999
|
+
...args.children
|
|
1000
|
+
};
|
|
1001
|
+
for (const key of Object.keys(children)) {
|
|
1002
|
+
if (children[key] === actor) {
|
|
1003
|
+
delete children[key];
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
children
|
|
1008
|
+
};
|
|
1009
|
+
}, {
|
|
1010
|
+
_special: true
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
function pushStartedChild(actions, actor, id) {
|
|
1014
|
+
pushBuiltInAction(actions, builtInActions['@xstate.start'], actor);
|
|
1015
|
+
actions.push(registerSpawnedChild(actor, id ?? actor.id));
|
|
1016
|
+
}
|
|
1017
|
+
function createTransitionEnqueue(actorScope, actions, internalEvents, actorSubscriptions = false) {
|
|
1018
|
+
const props = {
|
|
1019
|
+
cancel: id => {
|
|
1020
|
+
pushBuiltInAction(actions, builtInActions['@xstate.cancel'], actorScope, id);
|
|
1021
|
+
},
|
|
1022
|
+
emit: emittedEvent => {
|
|
1023
|
+
actions.push(emittedEvent);
|
|
1024
|
+
},
|
|
1025
|
+
log: (...args) => {
|
|
1026
|
+
pushBuiltInAction(actions, actorScope.logger, ...args);
|
|
1027
|
+
},
|
|
1028
|
+
raise: (raisedEvent, options) => {
|
|
1029
|
+
if (typeof raisedEvent === 'string') {
|
|
1030
|
+
throw new Error(`Only event objects may be used with raise; use raise({ type: "${raisedEvent}" }) instead` );
|
|
1031
|
+
}
|
|
1032
|
+
if (options?.delay !== undefined) {
|
|
1033
|
+
pushBuiltInAction(actions, builtInActions['@xstate.raise'], actorScope, raisedEvent, options);
|
|
1034
|
+
} else {
|
|
1035
|
+
internalEvents.push(raisedEvent);
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
spawn: (logic, options) => {
|
|
1039
|
+
const actor = createActor(logic, {
|
|
1040
|
+
...options,
|
|
1041
|
+
parent: actorScope.self
|
|
1042
|
+
});
|
|
1043
|
+
pushStartedChild(actions, actor, options?.id);
|
|
1044
|
+
return actor;
|
|
1045
|
+
},
|
|
1046
|
+
sendTo: (actor, event, options) => {
|
|
1047
|
+
if (actor) {
|
|
1048
|
+
pushBuiltInAction(actions, builtInActions['@xstate.sendTo'], actorScope, actor, event, options);
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
stop: actor => {
|
|
1052
|
+
if (actor) {
|
|
1053
|
+
pushBuiltInAction(actions, builtInActions['@xstate.stopChild'], actorScope, actor);
|
|
1054
|
+
actions.push(unregisterChild(actor));
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
if (actorSubscriptions) {
|
|
1059
|
+
Object.assign(props, {
|
|
1060
|
+
listen: (actor, eventType, mapper) => {
|
|
1061
|
+
const input = {
|
|
1062
|
+
actor,
|
|
1063
|
+
eventType,
|
|
1064
|
+
mapper
|
|
1065
|
+
};
|
|
1066
|
+
const listenerActor = createActor(listenerLogic, {
|
|
1067
|
+
input,
|
|
1068
|
+
parent: actorScope.self
|
|
1069
|
+
});
|
|
1070
|
+
pushBuiltInAction(actions, builtInActions['@xstate.start'], listenerActor);
|
|
1071
|
+
return listenerActor;
|
|
1072
|
+
},
|
|
1073
|
+
subscribeTo: (actor, mappers) => {
|
|
1074
|
+
const normalizedMappers = typeof mappers === 'function' ? {
|
|
1075
|
+
snapshot: mappers
|
|
1076
|
+
} : mappers;
|
|
1077
|
+
const input = {
|
|
1078
|
+
actor,
|
|
1079
|
+
mappers: normalizedMappers
|
|
1080
|
+
};
|
|
1081
|
+
const subscriptionActor = createActor(subscriptionLogic, {
|
|
1082
|
+
input,
|
|
1083
|
+
parent: actorScope.self
|
|
1084
|
+
});
|
|
1085
|
+
pushBuiltInAction(actions, builtInActions['@xstate.start'], subscriptionActor);
|
|
1086
|
+
return subscriptionActor;
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
return createEnqueueObject$1(props, (action, ...args) => {
|
|
1091
|
+
pushBuiltInAction(actions, action, ...args);
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
function resolveAndExecuteActionsWithContext(currentSnapshot, event, actorScope, actions) {
|
|
1095
|
+
let intermediateSnapshot = currentSnapshot;
|
|
1096
|
+
for (const action of actions) {
|
|
1097
|
+
const isInline = typeof action === 'function';
|
|
1098
|
+
const resolvedAction = isInline ? action : typeof action === 'object' && 'action' in action && typeof action.action === 'function' ? action.action.bind(null, ...action.args) : false;
|
|
1099
|
+
if (!resolvedAction && typeof action === 'object' && action !== null) {
|
|
1100
|
+
actorScope.defer(() => {
|
|
1101
|
+
actorScope.emit(action);
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
const actionArgs = {
|
|
1105
|
+
context: intermediateSnapshot.context,
|
|
1106
|
+
event,
|
|
1107
|
+
self: actorScope.self,
|
|
1108
|
+
system: actorScope.system,
|
|
1109
|
+
children: intermediateSnapshot.children,
|
|
1110
|
+
parent: actorScope.self._parent,
|
|
1111
|
+
actions: currentSnapshot.machine.implementations.actions,
|
|
1112
|
+
actors: currentSnapshot.machine.implementations.actors
|
|
1113
|
+
};
|
|
1114
|
+
let actionParams = undefined;
|
|
1115
|
+
if (typeof action === 'object' && action !== null) {
|
|
1116
|
+
const {
|
|
1117
|
+
type: _,
|
|
1118
|
+
...emittedEventParams
|
|
1119
|
+
} = action;
|
|
1120
|
+
actionParams = emittedEventParams;
|
|
1121
|
+
}
|
|
1122
|
+
if (resolvedAction && '_special' in resolvedAction) {
|
|
1123
|
+
actorScope.actionExecutor({
|
|
1124
|
+
type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type ?? '(anonymous)' : action.name || '(anonymous)',
|
|
1125
|
+
params: actionParams,
|
|
1126
|
+
args: [],
|
|
1127
|
+
exec: undefined
|
|
1128
|
+
});
|
|
1129
|
+
const specialAction = resolvedAction;
|
|
1130
|
+
const res = specialAction(actionArgs, emptyEnqueueObject);
|
|
1131
|
+
if (res && ('context' in res || 'children' in res)) {
|
|
1132
|
+
intermediateSnapshot = cloneMachineSnapshot(intermediateSnapshot, {
|
|
1133
|
+
...('context' in res ? {
|
|
1134
|
+
context: res.context
|
|
1135
|
+
} : {}),
|
|
1136
|
+
...('children' in res ? {
|
|
1137
|
+
children: res.children
|
|
1138
|
+
} : {})
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
if (!resolvedAction || !('resolve' in resolvedAction)) {
|
|
1144
|
+
actorScope.actionExecutor({
|
|
1145
|
+
type: typeof action === 'object' ? 'action' in action && typeof action.action === 'function' ? action.action.name ?? '(anonymous)' : action.type : action.name || '(anonymous)',
|
|
1146
|
+
params: actionParams,
|
|
1147
|
+
args: typeof action === 'object' && 'action' in action ? action.args : [],
|
|
1148
|
+
exec: resolvedAction
|
|
1149
|
+
});
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
return intermediateSnapshot;
|
|
1154
|
+
}
|
|
1155
|
+
function createEnqueueObject$1(props, action) {
|
|
1156
|
+
const enqueueFn = (fn, ...args) => {
|
|
1157
|
+
action(fn, ...args);
|
|
1158
|
+
};
|
|
1159
|
+
Object.assign(enqueueFn, {
|
|
1160
|
+
cancel: () => {},
|
|
1161
|
+
emit: () => {},
|
|
1162
|
+
log: () => {},
|
|
1163
|
+
raise: () => {},
|
|
1164
|
+
spawn: () => ({}),
|
|
1165
|
+
sendTo: () => {},
|
|
1166
|
+
stop: () => {},
|
|
1167
|
+
listen: () => ({}),
|
|
1168
|
+
subscribeTo: () => ({}),
|
|
1169
|
+
...props
|
|
1170
|
+
});
|
|
1171
|
+
return enqueueFn;
|
|
1172
|
+
}
|
|
1173
|
+
const emptyEnqueueObject = createEnqueueObject$1({}, () => {});
|
|
1174
|
+
|
|
1175
|
+
function parseDurationToMilliseconds(duration) {
|
|
1176
|
+
const normalizedDuration = duration.trim();
|
|
1177
|
+
const millisecondsMatch = normalizedDuration.match(/^(\d+)ms$/i);
|
|
1178
|
+
if (millisecondsMatch) {
|
|
1179
|
+
return parseInt(millisecondsMatch[1], 10);
|
|
1180
|
+
}
|
|
1181
|
+
const secondsMatch = normalizedDuration.match(/^(\d*)(\.?)(\d*)s$/i);
|
|
1182
|
+
if (secondsMatch) {
|
|
1183
|
+
const wholePart = secondsMatch[1] ? parseInt(secondsMatch[1], 10) : 0;
|
|
1184
|
+
const hasDecimal = !!secondsMatch[2];
|
|
1185
|
+
const fracPart = secondsMatch[3] ? parseInt(secondsMatch[3].padEnd(3, '0').slice(0, 3), 10) : 0;
|
|
1186
|
+
return wholePart * 1000 + (hasDecimal ? fracPart : 0);
|
|
1187
|
+
}
|
|
1188
|
+
const iso8601DurationMatch = normalizedDuration.match(/^P(?:(?<weeks>\d+(?:[.,]\d+)?)W)?(?:(?<days>\d+(?:[.,]\d+)?)D)?(?:T(?:(?<hours>\d+(?:[.,]\d+)?)H)?(?:(?<minutes>\d+(?:[.,]\d+)?)M)?(?:(?<seconds>\d+(?:[.,]\d+)?)S)?)?$/i);
|
|
1189
|
+
if (!iso8601DurationMatch?.groups) {
|
|
1190
|
+
return undefined;
|
|
1191
|
+
}
|
|
1192
|
+
const {
|
|
1193
|
+
weeks,
|
|
1194
|
+
days,
|
|
1195
|
+
hours,
|
|
1196
|
+
minutes,
|
|
1197
|
+
seconds
|
|
1198
|
+
} = iso8601DurationMatch.groups;
|
|
1199
|
+
if (!weeks && !days && !hours && !minutes && !seconds) {
|
|
1200
|
+
return undefined;
|
|
1201
|
+
}
|
|
1202
|
+
const toNumber = value => value ? Number(value.replace(',', '.')) : 0;
|
|
1203
|
+
return toNumber(weeks) * 7 * 24 * 60 * 60 * 1000 + toNumber(days) * 24 * 60 * 60 * 1000 + toNumber(hours) * 60 * 60 * 1000 + toNumber(minutes) * 60 * 1000 + toNumber(seconds) * 1000;
|
|
1204
|
+
}
|
|
1205
|
+
function parseDelayToMilliseconds(delay) {
|
|
1206
|
+
if (delay === undefined) {
|
|
1207
|
+
return undefined;
|
|
1208
|
+
}
|
|
1209
|
+
if (typeof delay === 'number') {
|
|
1210
|
+
return delay;
|
|
1211
|
+
}
|
|
1212
|
+
return parseDurationToMilliseconds(delay);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function getConfiguredDelayValue(delay, delaySource) {
|
|
1216
|
+
if (typeof delay !== 'string') {
|
|
1217
|
+
return delay;
|
|
1218
|
+
}
|
|
1219
|
+
const referencedDelay = delaySource[delay];
|
|
1220
|
+
if (referencedDelay !== undefined) {
|
|
1221
|
+
return referencedDelay;
|
|
1222
|
+
}
|
|
1223
|
+
return parseDurationToMilliseconds(delay) ?? delay;
|
|
1224
|
+
}
|
|
1225
|
+
function resolveDelay(delay, delaySource, args) {
|
|
1226
|
+
if (typeof delay === 'function') {
|
|
1227
|
+
return delay(args);
|
|
1228
|
+
}
|
|
1229
|
+
const configuredDelay = getConfiguredDelayValue(delay, delaySource);
|
|
1230
|
+
if (typeof configuredDelay === 'function') {
|
|
1231
|
+
return configuredDelay(args);
|
|
1232
|
+
}
|
|
1233
|
+
return configuredDelay;
|
|
1234
|
+
}
|
|
1235
|
+
function isAtomicStateNode(stateNode) {
|
|
1236
|
+
return stateNode.type === 'atomic' || stateNode.type === 'final' || stateNode.type === 'choice';
|
|
1237
|
+
}
|
|
1238
|
+
function getChildren(stateNode) {
|
|
1239
|
+
return Object.values(stateNode.states).filter(sn => sn.type !== 'history');
|
|
1240
|
+
}
|
|
1241
|
+
function getProperAncestors(stateNode, toStateNode) {
|
|
1242
|
+
const ancestors = [];
|
|
1243
|
+
if (toStateNode === stateNode) {
|
|
1244
|
+
return ancestors;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// add all ancestors
|
|
1248
|
+
let m = stateNode.parent;
|
|
1249
|
+
while (m && m !== toStateNode) {
|
|
1250
|
+
ancestors.push(m);
|
|
1251
|
+
m = m.parent;
|
|
1252
|
+
}
|
|
1253
|
+
return ancestors;
|
|
1254
|
+
}
|
|
1255
|
+
function getAllStateNodes(stateNodes) {
|
|
1256
|
+
const nodeSet = new Set(stateNodes);
|
|
1257
|
+
const activeParentSet = new Set();
|
|
1258
|
+
for (const stateNode of nodeSet) {
|
|
1259
|
+
if (stateNode.parent) {
|
|
1260
|
+
activeParentSet.add(stateNode.parent);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// add descendants
|
|
1265
|
+
for (const s of nodeSet) {
|
|
1266
|
+
// if previously active, add existing child nodes
|
|
1267
|
+
if (s.type === 'compound' && !activeParentSet.has(s)) {
|
|
1268
|
+
for (const sn of getInitialStateNodes(s)) {
|
|
1269
|
+
nodeSet.add(sn);
|
|
1270
|
+
}
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
if (s.type === 'parallel') {
|
|
1274
|
+
for (const child of getChildren(s)) {
|
|
1275
|
+
if (!nodeSet.has(child)) {
|
|
1276
|
+
const initialStates = getInitialStateNodes(child);
|
|
1277
|
+
for (const initialStateNode of initialStates) {
|
|
1278
|
+
nodeSet.add(initialStateNode);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
// add all ancestors
|
|
1286
|
+
// Stop walking up as soon as we hit a node already in the set: its own
|
|
1287
|
+
// ancestor chain is guaranteed to be added when it is visited by this loop
|
|
1288
|
+
// (Set iteration visits elements added during iteration), so shared ancestor
|
|
1289
|
+
// chains are never re-walked once per descendant.
|
|
1290
|
+
for (const s of nodeSet) {
|
|
1291
|
+
let m = s.parent;
|
|
1292
|
+
while (m && !nodeSet.has(m)) {
|
|
1293
|
+
nodeSet.add(m);
|
|
1294
|
+
m = m.parent;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return nodeSet;
|
|
1298
|
+
}
|
|
1299
|
+
function getStateValue(rootNode, stateNodes) {
|
|
1300
|
+
const config = getAllStateNodes(stateNodes);
|
|
1301
|
+
const adjList = new Map();
|
|
1302
|
+
for (const s of config) {
|
|
1303
|
+
if (!adjList.has(s)) {
|
|
1304
|
+
adjList.set(s, []);
|
|
1305
|
+
}
|
|
1306
|
+
if (!s.parent) {
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
if (!adjList.has(s.parent)) {
|
|
1310
|
+
adjList.set(s.parent, []);
|
|
1311
|
+
}
|
|
1312
|
+
adjList.get(s.parent).push(s);
|
|
1313
|
+
}
|
|
1314
|
+
const getValueFromAdj = baseNode => {
|
|
1315
|
+
const childStateNodes = adjList.get(baseNode);
|
|
1316
|
+
if (!childStateNodes) {
|
|
1317
|
+
return {}; // todo: fix?
|
|
1318
|
+
}
|
|
1319
|
+
if (baseNode.type === 'compound') {
|
|
1320
|
+
const childStateNode = childStateNodes[0];
|
|
1321
|
+
if (!childStateNode) {
|
|
1322
|
+
return {};
|
|
1323
|
+
}
|
|
1324
|
+
if (isAtomicStateNode(childStateNode)) {
|
|
1325
|
+
return childStateNode.key;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
const stateValue = {};
|
|
1329
|
+
for (const childStateNode of childStateNodes) {
|
|
1330
|
+
stateValue[childStateNode.key] = getValueFromAdj(childStateNode);
|
|
1331
|
+
}
|
|
1332
|
+
return stateValue;
|
|
1333
|
+
};
|
|
1334
|
+
return getValueFromAdj(rootNode);
|
|
1335
|
+
}
|
|
1336
|
+
function isInFinalState(stateNodeSet, stateNode) {
|
|
1337
|
+
if (stateNode.type === 'compound') {
|
|
1338
|
+
return getChildren(stateNode).some(s => s.type === 'final' && stateNodeSet.has(s));
|
|
1339
|
+
}
|
|
1340
|
+
if (stateNode.type === 'parallel') {
|
|
1341
|
+
return getChildren(stateNode).every(sn => isInFinalState(stateNodeSet, sn));
|
|
1342
|
+
}
|
|
1343
|
+
return stateNode.type === 'final';
|
|
1344
|
+
}
|
|
1345
|
+
const isStateId = str => str[0] === STATE_IDENTIFIER;
|
|
1346
|
+
function getCandidates(stateNode, receivedEventType) {
|
|
1347
|
+
const candidates = stateNode.transitions.get(receivedEventType);
|
|
1348
|
+
if (candidates) {
|
|
1349
|
+
return candidates;
|
|
1350
|
+
}
|
|
1351
|
+
const matchingDescriptors = [];
|
|
1352
|
+
for (const eventDescriptor of stateNode.transitions.keys()) {
|
|
1353
|
+
if (matchesEventDescriptor(receivedEventType, eventDescriptor)) {
|
|
1354
|
+
matchingDescriptors.push(eventDescriptor);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
matchingDescriptors.sort((a, b) => b.length - a.length);
|
|
1358
|
+
const wildcardCandidates = [];
|
|
1359
|
+
for (const descriptor of matchingDescriptors) {
|
|
1360
|
+
wildcardCandidates.push(...stateNode.transitions.get(descriptor));
|
|
1361
|
+
}
|
|
1362
|
+
return wildcardCandidates;
|
|
1363
|
+
}
|
|
1364
|
+
function scheduleDelayedEvent(stateNode, event, resolveScheduledDelay) {
|
|
1365
|
+
const eventType = event.type;
|
|
1366
|
+
const oldEntry = stateNode.entry;
|
|
1367
|
+
stateNode.entry = (x, enq) => {
|
|
1368
|
+
enq.raise(event, {
|
|
1369
|
+
id: eventType,
|
|
1370
|
+
delay: resolveScheduledDelay(x)
|
|
1371
|
+
});
|
|
1372
|
+
return typeof oldEntry === 'function' ? oldEntry(x, enq) : undefined;
|
|
1373
|
+
};
|
|
1374
|
+
const oldExit = stateNode.exit;
|
|
1375
|
+
stateNode.exit = (_, enq) => {
|
|
1376
|
+
enq.cancel(eventType);
|
|
1377
|
+
return typeof oldExit === 'function' ? oldExit(_, enq) : undefined;
|
|
1378
|
+
};
|
|
1379
|
+
return eventType;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
/** All delayed transitions from the config. */
|
|
1383
|
+
function getDelayedTransitions(stateNode) {
|
|
1384
|
+
const afterConfig = stateNode.config.after;
|
|
1385
|
+
const timeoutConfig = stateNode.config.timeout;
|
|
1386
|
+
const onTimeoutConfig = stateNode.config.onTimeout;
|
|
1387
|
+
const invokeDefs = stateNode.invoke.filter(invokeDef => invokeDef.timeout !== undefined);
|
|
1388
|
+
if (!afterConfig && timeoutConfig === undefined && invokeDefs.length === 0) {
|
|
1389
|
+
return [];
|
|
1390
|
+
}
|
|
1391
|
+
if (timeoutConfig !== undefined && !onTimeoutConfig) {
|
|
1392
|
+
throw new Error(`State "${stateNode.id}" has \`timeout\` but no \`onTimeout\` transition.`);
|
|
1393
|
+
}
|
|
1394
|
+
{
|
|
1395
|
+
for (const invokeDef of invokeDefs) {
|
|
1396
|
+
if (!invokeDef.onTimeout) {
|
|
1397
|
+
throw new Error(`Invoke on state "${stateNode.id}" has \`timeout\` but no \`onTimeout\` transition.`);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
const mutateEntryExitWithDelay = delay => {
|
|
1402
|
+
const afterEvent = createAfterEvent(delay, stateNode.id);
|
|
1403
|
+
return scheduleDelayedEvent(stateNode, afterEvent, x => resolveDelay(delay, x.delays, {
|
|
1404
|
+
context: x.context,
|
|
1405
|
+
event: x.event,
|
|
1406
|
+
stateNode
|
|
1407
|
+
}));
|
|
1408
|
+
};
|
|
1409
|
+
const delayedTransitions = [];
|
|
1410
|
+
const addDelayedTransitions = (transitionsConfig, event, delay) => {
|
|
1411
|
+
for (const transition of toTransitionConfigArray(transitionsConfig)) {
|
|
1412
|
+
delayedTransitions.push({
|
|
1413
|
+
...transition,
|
|
1414
|
+
event,
|
|
1415
|
+
delay
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
if (afterConfig) {
|
|
1420
|
+
for (const delay of Object.keys(afterConfig)) {
|
|
1421
|
+
const configTransition = afterConfig[delay];
|
|
1422
|
+
const parsedDelay = Number.isNaN(+delay) ? delay : +delay;
|
|
1423
|
+
const eventType = mutateEntryExitWithDelay(parsedDelay);
|
|
1424
|
+
const resolvedDelay = getConfiguredDelayValue(parsedDelay, stateNode.machine.implementations.delays);
|
|
1425
|
+
addDelayedTransitions(configTransition, eventType, resolvedDelay);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// Desugar state-level `timeout` + `onTimeout` into a delayed transition.
|
|
1430
|
+
// Uses a dedicated `xstate.timeout.<id>` event so it cannot collide with
|
|
1431
|
+
// explicit `after` entries on the same state.
|
|
1432
|
+
if (timeoutConfig !== undefined && onTimeoutConfig) {
|
|
1433
|
+
const timeoutEvent = createTimeoutEvent(stateNode.id);
|
|
1434
|
+
const timeoutEventType = scheduleDelayedEvent(stateNode, timeoutEvent, x => resolveDelay(timeoutConfig, x.delays, {
|
|
1435
|
+
context: x.context,
|
|
1436
|
+
event: x.event,
|
|
1437
|
+
stateNode
|
|
1438
|
+
}));
|
|
1439
|
+
const resolvedDelay = typeof timeoutConfig === 'function' ? timeoutConfig : getConfiguredDelayValue(timeoutConfig, stateNode.machine.implementations.delays);
|
|
1440
|
+
addDelayedTransitions(onTimeoutConfig, timeoutEventType, resolvedDelay);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
// Desugar invoke-level `timeout` + `onTimeout` into a delayed transition on
|
|
1444
|
+
// the enclosing state. Completion transitions cancel this timer separately,
|
|
1445
|
+
// so the timeout is cleared even when the parent state stays active.
|
|
1446
|
+
for (const invokeDef of invokeDefs) {
|
|
1447
|
+
const invokeTimeoutEvent = createInvokeTimeoutEvent(invokeDef.id);
|
|
1448
|
+
const invokeTimeout = invokeDef.timeout;
|
|
1449
|
+
const invokeTimeoutEventType = scheduleDelayedEvent(stateNode, invokeTimeoutEvent, x => resolveDelay(invokeTimeout, {}, {
|
|
1450
|
+
context: x.context,
|
|
1451
|
+
event: x.event,
|
|
1452
|
+
stateNode
|
|
1453
|
+
}));
|
|
1454
|
+
const invokeOnTimeout = invokeDef.onTimeout;
|
|
1455
|
+
addDelayedTransitions(invokeOnTimeout, invokeTimeoutEventType, invokeTimeout);
|
|
1456
|
+
}
|
|
1457
|
+
return delayedTransitions.map(delayedTransition => ({
|
|
1458
|
+
...formatTransition(stateNode, delayedTransition.event, delayedTransition),
|
|
1459
|
+
delay: delayedTransition.delay
|
|
1460
|
+
}));
|
|
1461
|
+
}
|
|
1462
|
+
function formatTransition(stateNode, descriptor, transitionConfig) {
|
|
1463
|
+
const normalizedTarget = normalizeTarget(transitionConfig.target);
|
|
1464
|
+
const reenter = transitionConfig.reenter ?? false;
|
|
1465
|
+
const target = resolveTarget(stateNode, normalizedTarget);
|
|
1466
|
+
const transition = {
|
|
1467
|
+
...transitionConfig,
|
|
1468
|
+
target,
|
|
1469
|
+
source: stateNode,
|
|
1470
|
+
reenter,
|
|
1471
|
+
eventType: descriptor,
|
|
1472
|
+
toJSON: () => ({
|
|
1473
|
+
...transition,
|
|
1474
|
+
source: `#${stateNode.id}`,
|
|
1475
|
+
target: target ? target.map(t => `#${t.id}`) : undefined
|
|
1476
|
+
})
|
|
1477
|
+
};
|
|
1478
|
+
return transition;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
/**
|
|
1482
|
+
* Collects route transitions from all descendants with explicit IDs. Called
|
|
1483
|
+
* once on the root node to avoid O(N²) repeated traversals.
|
|
1484
|
+
*/
|
|
1485
|
+
function formatRouteTransitions(rootStateNode) {
|
|
1486
|
+
const routeTransitions = [];
|
|
1487
|
+
const collectRoutes = states => {
|
|
1488
|
+
Object.values(states).forEach(sn => {
|
|
1489
|
+
if (sn.config.route && sn.config.id) {
|
|
1490
|
+
const routeId = sn.config.id;
|
|
1491
|
+
const routeConfig = sn.config.route;
|
|
1492
|
+
const routeMatches = ({
|
|
1493
|
+
event
|
|
1494
|
+
}) => event.to === `#${routeId}`;
|
|
1495
|
+
if (typeof routeConfig === 'function') {
|
|
1496
|
+
// Transition-style route: the function is the guard and resolver.
|
|
1497
|
+
// Returning undefined/false blocks the route; returning true or a
|
|
1498
|
+
// config object allows it.
|
|
1499
|
+
routeTransitions.push(formatTransition(rootStateNode, 'xstate.route', {
|
|
1500
|
+
guard: routeMatches,
|
|
1501
|
+
to: args => {
|
|
1502
|
+
const result = routeConfig(args);
|
|
1503
|
+
if (!result) {
|
|
1504
|
+
return undefined;
|
|
1505
|
+
}
|
|
1506
|
+
return {
|
|
1507
|
+
...(result === true ? {} : result),
|
|
1508
|
+
target: `#${routeId}`
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
}));
|
|
1512
|
+
if (sn.states) {
|
|
1513
|
+
collectRoutes(sn.states);
|
|
1514
|
+
}
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
if ('$unserializable' in routeConfig) {
|
|
1518
|
+
throw new Error(`State "${sn.id}" has a route that is not serializable. Re-provide the route function when reviving this machine.`);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// Object-form route. A `guard` (string or function) on the object is
|
|
1522
|
+
// only produced by the JSON layer (createMachineFromConfig) —
|
|
1523
|
+
// authoring uses the function form above.
|
|
1524
|
+
const userGuard = routeConfig.guard;
|
|
1525
|
+
const transition = {
|
|
1526
|
+
...routeConfig,
|
|
1527
|
+
guard: userGuard ? args => {
|
|
1528
|
+
if (!routeMatches(args)) return false;
|
|
1529
|
+
if (typeof userGuard === 'function') return userGuard(args);
|
|
1530
|
+
if (typeof userGuard === 'string') {
|
|
1531
|
+
const guardImpl = args.guards?.[userGuard];
|
|
1532
|
+
if (!guardImpl) {
|
|
1533
|
+
// A typo'd guard name must fail loudly — silently passing
|
|
1534
|
+
// would allow the route and corrupt machine behavior.
|
|
1535
|
+
throw new Error(`Guard '${userGuard}' is not implemented in machine '${rootStateNode.machine.id}'. Available guards: ${Object.keys(args.guards ?? {}).map(key => `'${key}'`).join(', ') || '(none)'}.` );
|
|
1536
|
+
}
|
|
1537
|
+
return guardImpl(args);
|
|
1538
|
+
}
|
|
1539
|
+
return true;
|
|
1540
|
+
} : routeMatches,
|
|
1541
|
+
target: `#${routeId}`
|
|
1542
|
+
};
|
|
1543
|
+
routeTransitions.push(formatTransition(rootStateNode, 'xstate.route', transition));
|
|
1544
|
+
}
|
|
1545
|
+
if (sn.states) {
|
|
1546
|
+
collectRoutes(sn.states);
|
|
1547
|
+
}
|
|
1548
|
+
});
|
|
1549
|
+
};
|
|
1550
|
+
collectRoutes(rootStateNode.states);
|
|
1551
|
+
if (routeTransitions.length > 0) {
|
|
1552
|
+
rootStateNode.transitions.set('xstate.route', routeTransitions);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
function resolveTarget(stateNode, targets) {
|
|
1556
|
+
if (targets === undefined) {
|
|
1557
|
+
// an undefined target signals that the state node should not transition from that state when receiving that event
|
|
1558
|
+
return undefined;
|
|
1559
|
+
}
|
|
1560
|
+
return targets.map(target => {
|
|
1561
|
+
if (typeof target !== 'string') {
|
|
1562
|
+
return target;
|
|
1563
|
+
}
|
|
1564
|
+
if (isStateId(target)) {
|
|
1565
|
+
return stateNode.machine.getStateNodeById(target);
|
|
1566
|
+
}
|
|
1567
|
+
const isInternalTarget = target[0] === STATE_DELIMITER;
|
|
1568
|
+
// If internal target is defined on machine,
|
|
1569
|
+
// do not include machine key on target
|
|
1570
|
+
if (isInternalTarget && !stateNode.parent) {
|
|
1571
|
+
return getStateNodeByPath(stateNode, target.slice(1));
|
|
1572
|
+
}
|
|
1573
|
+
const resolvedTarget = isInternalTarget ? stateNode.key + target : target;
|
|
1574
|
+
if (stateNode.parent) {
|
|
1575
|
+
try {
|
|
1576
|
+
const targetStateNode = getStateNodeByPath(stateNode.parent, resolvedTarget);
|
|
1577
|
+
return targetStateNode;
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
throw new Error(`Invalid transition definition for state node '${stateNode.id}':\n${err.message}`);
|
|
1580
|
+
}
|
|
1581
|
+
} else {
|
|
1582
|
+
throw new Error(`Invalid target: "${target}" is not a valid target from the root node. Did you mean ".${target}"?` );
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
function resolveHistoryDefaultTransition(stateNode) {
|
|
1587
|
+
const normalizedTarget = normalizeTarget(stateNode.config.target);
|
|
1588
|
+
if (!normalizedTarget) {
|
|
1589
|
+
return stateNode.parent.initial;
|
|
1590
|
+
}
|
|
1591
|
+
return {
|
|
1592
|
+
target: normalizedTarget.map(t => typeof t === 'string' ? getStateNodeByPath(stateNode.parent, t) : t),
|
|
1593
|
+
source: stateNode,
|
|
1594
|
+
reenter: false,
|
|
1595
|
+
eventType: ''
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
function isHistoryNode(stateNode) {
|
|
1599
|
+
return stateNode.type === 'history';
|
|
1600
|
+
}
|
|
1601
|
+
function getInitialStateNodes(stateNode) {
|
|
1602
|
+
const set = new Set();
|
|
1603
|
+
function iter(descStateNode) {
|
|
1604
|
+
if (set.has(descStateNode)) {
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
set.add(descStateNode);
|
|
1608
|
+
if (descStateNode.type === 'compound') {
|
|
1609
|
+
iter(descStateNode.initial.target[0]);
|
|
1610
|
+
} else if (descStateNode.type === 'parallel') {
|
|
1611
|
+
for (const child of getChildren(descStateNode)) {
|
|
1612
|
+
iter(child);
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
iter(stateNode);
|
|
1617
|
+
for (const initialState of set) {
|
|
1618
|
+
for (const ancestor of getProperAncestors(initialState, stateNode)) {
|
|
1619
|
+
set.add(ancestor);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return set;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/** Returns the child state node from its relative `stateKey`, or throws. */
|
|
1626
|
+
function getStateNode(stateNode, stateKey) {
|
|
1627
|
+
if (isStateId(stateKey)) {
|
|
1628
|
+
return stateNode.machine.getStateNodeById(stateKey);
|
|
1629
|
+
}
|
|
1630
|
+
if (!stateNode.states) {
|
|
1631
|
+
throw new Error(`Unable to retrieve child state '${stateKey}' from '${stateNode.id}'; no child states exist.`);
|
|
1632
|
+
}
|
|
1633
|
+
const result = stateNode.states[stateKey];
|
|
1634
|
+
if (!result) {
|
|
1635
|
+
throw new Error(`Child state '${stateKey}' does not exist on '${stateNode.id}'`);
|
|
1636
|
+
}
|
|
1637
|
+
return result;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
/**
|
|
1641
|
+
* Returns the relative state node from the given `statePath`, or throws.
|
|
1642
|
+
*
|
|
1643
|
+
* @param statePath The string or string array relative path to the state node.
|
|
1644
|
+
*/
|
|
1645
|
+
function getStateNodeByPath(stateNode, statePath) {
|
|
1646
|
+
if (typeof statePath === 'string' && isStateId(statePath)) {
|
|
1647
|
+
try {
|
|
1648
|
+
return stateNode.machine.getStateNodeById(statePath);
|
|
1649
|
+
} catch {
|
|
1650
|
+
// try individual paths
|
|
1651
|
+
// throw e;
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
const arrayStatePath = toStatePath(statePath).slice();
|
|
1655
|
+
let currentStateNode = stateNode;
|
|
1656
|
+
while (arrayStatePath.length) {
|
|
1657
|
+
const key = arrayStatePath.shift();
|
|
1658
|
+
if (!key.length) {
|
|
1659
|
+
break;
|
|
1660
|
+
}
|
|
1661
|
+
currentStateNode = getStateNode(currentStateNode, key);
|
|
1662
|
+
}
|
|
1663
|
+
return currentStateNode;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* Returns the state nodes represented by the current state value.
|
|
1668
|
+
*
|
|
1669
|
+
* @param stateValue The state value or State instance
|
|
1670
|
+
*/
|
|
1671
|
+
function getStateNodes(stateNode, stateValue) {
|
|
1672
|
+
if (typeof stateValue === 'string') {
|
|
1673
|
+
const childStateNode = stateNode.states[stateValue];
|
|
1674
|
+
if (!childStateNode) {
|
|
1675
|
+
throw new Error(`State '${stateValue}' does not exist on '${stateNode.id}'`);
|
|
1676
|
+
}
|
|
1677
|
+
return [stateNode, childStateNode];
|
|
1678
|
+
}
|
|
1679
|
+
const childStateKeys = Object.keys(stateValue);
|
|
1680
|
+
const childStateNodes = new Array(childStateKeys.length);
|
|
1681
|
+
const allStateNodes = [stateNode.machine.root, stateNode];
|
|
1682
|
+
for (let i = 0; i < childStateKeys.length; i++) {
|
|
1683
|
+
const subStateNode = getStateNode(stateNode, childStateKeys[i]);
|
|
1684
|
+
childStateNodes[i] = subStateNode;
|
|
1685
|
+
allStateNodes.push(subStateNode);
|
|
1686
|
+
}
|
|
1687
|
+
for (let i = 0; i < childStateKeys.length; i++) {
|
|
1688
|
+
allStateNodes.push(...getStateNodes(childStateNodes[i], stateValue[childStateKeys[i]]));
|
|
1689
|
+
}
|
|
1690
|
+
return allStateNodes;
|
|
1691
|
+
}
|
|
1692
|
+
function transitionNode(stateNode, stateValue, snapshot, event, self) {
|
|
1693
|
+
// leaf node
|
|
1694
|
+
if (typeof stateValue === 'string') {
|
|
1695
|
+
const childStateNode = getStateNode(stateNode, stateValue);
|
|
1696
|
+
const next = childStateNode.next(snapshot, event, self);
|
|
1697
|
+
if (!next || !next.length) {
|
|
1698
|
+
return stateNode.next(snapshot, event, self);
|
|
1699
|
+
}
|
|
1700
|
+
return next;
|
|
1701
|
+
}
|
|
1702
|
+
const subStateKeys = Object.keys(stateValue);
|
|
1703
|
+
const subStateKey = subStateKeys[0];
|
|
1704
|
+
if (subStateKeys.length === 1) {
|
|
1705
|
+
const childStateNode = getStateNode(stateNode, subStateKey);
|
|
1706
|
+
const next = transitionNode(childStateNode, stateValue[subStateKey], snapshot, event, self);
|
|
1707
|
+
if (!next || !next.length) {
|
|
1708
|
+
return stateNode.next(snapshot, event, self);
|
|
1709
|
+
}
|
|
1710
|
+
return next;
|
|
1711
|
+
}
|
|
1712
|
+
const allInnerTransitions = [];
|
|
1713
|
+
for (const subStateKey of subStateKeys) {
|
|
1714
|
+
const subStateValue = stateValue[subStateKey];
|
|
1715
|
+
if (!subStateValue) {
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
const subStateNode = getStateNode(stateNode, subStateKey);
|
|
1719
|
+
const innerTransitions = transitionNode(subStateNode, subStateValue, snapshot, event, self);
|
|
1720
|
+
if (innerTransitions) {
|
|
1721
|
+
allInnerTransitions.push(...innerTransitions);
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
if (!allInnerTransitions.length) {
|
|
1725
|
+
return stateNode.next(snapshot, event, self);
|
|
1726
|
+
}
|
|
1727
|
+
return allInnerTransitions;
|
|
1728
|
+
}
|
|
1729
|
+
function isDescendant(childStateNode, parentStateNode) {
|
|
1730
|
+
let marker = childStateNode;
|
|
1731
|
+
while (marker.parent && marker.parent !== parentStateNode) {
|
|
1732
|
+
marker = marker.parent;
|
|
1733
|
+
}
|
|
1734
|
+
return marker.parent === parentStateNode;
|
|
1735
|
+
}
|
|
1736
|
+
function hasDescendantState(stateNodes, parentStateNode) {
|
|
1737
|
+
for (const stateNode of stateNodes) {
|
|
1738
|
+
if (isDescendant(stateNode, parentStateNode)) {
|
|
1739
|
+
return true;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
function hasIntersection(s1, s2) {
|
|
1745
|
+
const s1Size = s1 instanceof Set ? s1.size : Array.isArray(s1) ? s1.length : undefined;
|
|
1746
|
+
const s2Size = s2 instanceof Set ? s2.size : Array.isArray(s2) ? s2.length : undefined;
|
|
1747
|
+
if (s1Size !== undefined && s2Size !== undefined && s2Size < s1Size) {
|
|
1748
|
+
[s1, s2] = [s2, s1];
|
|
1749
|
+
}
|
|
1750
|
+
const set1 = s1 instanceof Set ? s1 : new Set(s1);
|
|
1751
|
+
for (const item of s2) {
|
|
1752
|
+
if (set1.has(item)) {
|
|
1753
|
+
return true;
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
return false;
|
|
1757
|
+
}
|
|
1758
|
+
function removeConflictingTransitions(enabledTransitions, stateNodeSet, snapshot, event, actorScope) {
|
|
1759
|
+
const filteredTransitions = new Set();
|
|
1760
|
+
const exitSets = new Map();
|
|
1761
|
+
const getExitSet = transition => {
|
|
1762
|
+
let exitSet = exitSets.get(transition);
|
|
1763
|
+
if (!exitSet) {
|
|
1764
|
+
exitSet = computeExitSet([transition], stateNodeSet, snapshot, event, actorScope);
|
|
1765
|
+
exitSets.set(transition, exitSet);
|
|
1766
|
+
}
|
|
1767
|
+
return exitSet;
|
|
1768
|
+
};
|
|
1769
|
+
for (const t1 of enabledTransitions) {
|
|
1770
|
+
let t1Preempted = false;
|
|
1771
|
+
const transitionsToRemove = new Set();
|
|
1772
|
+
for (const t2 of filteredTransitions) {
|
|
1773
|
+
if (hasIntersection(getExitSet(t1), getExitSet(t2))) {
|
|
1774
|
+
if (isDescendant(t1.source, t2.source)) {
|
|
1775
|
+
transitionsToRemove.add(t2);
|
|
1776
|
+
} else {
|
|
1777
|
+
t1Preempted = true;
|
|
1778
|
+
break;
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
if (!t1Preempted) {
|
|
1783
|
+
for (const t3 of transitionsToRemove) {
|
|
1784
|
+
filteredTransitions.delete(t3);
|
|
1785
|
+
}
|
|
1786
|
+
filteredTransitions.add(t1);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
return Array.from(filteredTransitions);
|
|
1790
|
+
}
|
|
1791
|
+
function getEffectiveTargetStates(transition, snapshot, event, actorScope) {
|
|
1792
|
+
const historyValue = snapshot.historyValue;
|
|
1793
|
+
const {
|
|
1794
|
+
targets
|
|
1795
|
+
} = getTransitionResult(transition, snapshot, event, actorScope);
|
|
1796
|
+
if (!targets) {
|
|
1797
|
+
return [];
|
|
1798
|
+
}
|
|
1799
|
+
const targetSet = new Set();
|
|
1800
|
+
for (const targetNode of targets) {
|
|
1801
|
+
if (isHistoryNode(targetNode)) {
|
|
1802
|
+
if (historyValue[targetNode.id]) {
|
|
1803
|
+
for (const node of historyValue[targetNode.id]) {
|
|
1804
|
+
targetSet.add(node);
|
|
1805
|
+
}
|
|
1806
|
+
} else {
|
|
1807
|
+
for (const node of getEffectiveTargetStates(resolveHistoryDefaultTransition(targetNode), snapshot, event, actorScope)) {
|
|
1808
|
+
targetSet.add(node);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
} else {
|
|
1812
|
+
targetSet.add(targetNode);
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
return [...targetSet];
|
|
1816
|
+
}
|
|
1817
|
+
function getTransitionDomain(transition, snapshot, event, actorScope) {
|
|
1818
|
+
const targetStates = getEffectiveTargetStates(transition, snapshot, event, actorScope);
|
|
1819
|
+
const {
|
|
1820
|
+
reenter
|
|
1821
|
+
} = getTransitionResult(transition, snapshot, event, actorScope);
|
|
1822
|
+
if (!reenter && targetStates.every(target => target === transition.source || isDescendant(target, transition.source))) {
|
|
1823
|
+
return transition.source;
|
|
1824
|
+
}
|
|
1825
|
+
const [head, ...tail] = targetStates.concat(transition.source);
|
|
1826
|
+
// Find the least common ancestor (LCA) of the source and effective targets.
|
|
1827
|
+
for (const ancestor of getProperAncestors(head, undefined)) {
|
|
1828
|
+
if (tail.every(sn => isDescendant(sn, ancestor))) {
|
|
1829
|
+
return ancestor;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// at this point we know that it's a root transition since LCA couldn't be found
|
|
1834
|
+
if (reenter) {
|
|
1835
|
+
return;
|
|
1836
|
+
}
|
|
1837
|
+
return transition.source.machine.root;
|
|
1838
|
+
}
|
|
1839
|
+
function computeExitSet(transitions, stateNodeSet, snapshot, event, actorScope) {
|
|
1840
|
+
const statesToExit = new Set();
|
|
1841
|
+
for (const transition of transitions) {
|
|
1842
|
+
const {
|
|
1843
|
+
targets
|
|
1844
|
+
} = getTransitionResult(transition, snapshot, event, actorScope);
|
|
1845
|
+
if (targets?.length) {
|
|
1846
|
+
const domain = getTransitionDomain(transition, snapshot, event, actorScope);
|
|
1847
|
+
if (transition.reenter && transition.source === domain) {
|
|
1848
|
+
statesToExit.add(domain);
|
|
1849
|
+
}
|
|
1850
|
+
for (const stateNode of stateNodeSet) {
|
|
1851
|
+
if (isDescendant(stateNode, domain)) {
|
|
1852
|
+
statesToExit.add(stateNode);
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return [...statesToExit];
|
|
1858
|
+
}
|
|
1859
|
+
function initialMicrostep(root, preInitialState, actorScope, initEvent, internalQueue) {
|
|
1860
|
+
return microstep([{
|
|
1861
|
+
target: [...getInitialStateNodes(root)],
|
|
1862
|
+
source: root,
|
|
1863
|
+
reenter: true,
|
|
1864
|
+
eventType: null,
|
|
1865
|
+
toJSON: null
|
|
1866
|
+
}], preInitialState, actorScope, initEvent, true, internalQueue);
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/** https://www.w3.org/TR/scxml/#microstepProcedure */
|
|
1870
|
+
function microstep(transitions, currentSnapshot, actorScope, event, isInitial, internalQueue) {
|
|
1871
|
+
const actions = [];
|
|
1872
|
+
if (!transitions.length) {
|
|
1873
|
+
return [currentSnapshot, actions];
|
|
1874
|
+
}
|
|
1875
|
+
const originalExecutor = actorScope.actionExecutor;
|
|
1876
|
+
actorScope.actionExecutor = action => {
|
|
1877
|
+
actions.push(action);
|
|
1878
|
+
originalExecutor(action);
|
|
1879
|
+
};
|
|
1880
|
+
try {
|
|
1881
|
+
const mutStateNodeSet = new Set(currentSnapshot._nodes);
|
|
1882
|
+
let historyValue = currentSnapshot.historyValue;
|
|
1883
|
+
const originalContext = currentSnapshot.context;
|
|
1884
|
+
const filteredTransitions = removeConflictingTransitions(transitions, mutStateNodeSet, currentSnapshot, event, actorScope);
|
|
1885
|
+
const getCurrentTransitionResult = transition => getTransitionResult(transition, currentSnapshot, event, actorScope);
|
|
1886
|
+
const getStateActionsAndContext = (transitionFn, context, children, input) => {
|
|
1887
|
+
if (transitionFn.length === 2) {
|
|
1888
|
+
// enqueue action; retrieve
|
|
1889
|
+
const actions = [];
|
|
1890
|
+
const internalEvents = [];
|
|
1891
|
+
let updatedContext;
|
|
1892
|
+
const enqueue = createTransitionEnqueue(actorScope, actions, internalEvents, true);
|
|
1893
|
+
const res = transitionFn({
|
|
1894
|
+
context,
|
|
1895
|
+
event,
|
|
1896
|
+
parent: actorScope.self._parent,
|
|
1897
|
+
self: actorScope.self,
|
|
1898
|
+
children,
|
|
1899
|
+
system: actorScope.system,
|
|
1900
|
+
actions: currentSnapshot.machine.implementations.actions,
|
|
1901
|
+
actors: currentSnapshot.machine.implementations.actors,
|
|
1902
|
+
guards: currentSnapshot.machine.implementations.guards,
|
|
1903
|
+
delays: currentSnapshot.machine.implementations.delays,
|
|
1904
|
+
input
|
|
1905
|
+
}, enqueue);
|
|
1906
|
+
if (res?.context) {
|
|
1907
|
+
updatedContext = res.context;
|
|
1908
|
+
}
|
|
1909
|
+
return [actions, updatedContext, internalEvents];
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
// For 1-argument actions, wrap them to include input
|
|
1913
|
+
// Preserve _special flag if present (for entry/exit actions)
|
|
1914
|
+
const wrappedAction = Object.assign((args, enqueue) => transitionFn({
|
|
1915
|
+
...args,
|
|
1916
|
+
input
|
|
1917
|
+
}, enqueue), '_special' in transitionFn ? {
|
|
1918
|
+
_special: true
|
|
1919
|
+
} : {});
|
|
1920
|
+
return [[wrappedAction], undefined, undefined];
|
|
1921
|
+
};
|
|
1922
|
+
let nextState = currentSnapshot;
|
|
1923
|
+
const exitStates = () => {
|
|
1924
|
+
const statesToExit = computeExitSet(filteredTransitions, mutStateNodeSet, currentSnapshot, event, actorScope);
|
|
1925
|
+
statesToExit.sort((a, b) => b.order - a.order);
|
|
1926
|
+
let changedHistory;
|
|
1927
|
+
const currentStateNodes = [...mutStateNodeSet];
|
|
1928
|
+
|
|
1929
|
+
// From SCXML algorithm: https://www.w3.org/TR/scxml/#exitStates
|
|
1930
|
+
for (const exitStateNode of statesToExit) {
|
|
1931
|
+
for (const historyNode of Object.values(exitStateNode.states)) {
|
|
1932
|
+
if (historyNode.type !== 'history') {
|
|
1933
|
+
continue;
|
|
1934
|
+
}
|
|
1935
|
+
const predicate = historyNode.history === 'deep' ? sn => isAtomicStateNode(sn) && isDescendant(sn, exitStateNode) : sn => sn.parent === exitStateNode;
|
|
1936
|
+
changedHistory ??= {
|
|
1937
|
+
...historyValue
|
|
1938
|
+
};
|
|
1939
|
+
changedHistory[historyNode.id] = currentStateNodes.filter(predicate);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
for (const exitStateNode of statesToExit) {
|
|
1943
|
+
const stateInput = currentSnapshot._stateInputs?.[exitStateNode.id];
|
|
1944
|
+
const [exitActions, nextContext, internalEvents] = exitStateNode.exit ? getStateActionsAndContext(exitStateNode.exit, nextState.context, currentSnapshot.children, stateInput) : [[]];
|
|
1945
|
+
if (internalEvents?.length) {
|
|
1946
|
+
internalQueue.push(...internalEvents);
|
|
1947
|
+
}
|
|
1948
|
+
if (nextContext) {
|
|
1949
|
+
nextState = cloneMachineSnapshot(nextState, {
|
|
1950
|
+
context: nextContext
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, exitActions);
|
|
1954
|
+
for (const def of exitStateNode.invoke) {
|
|
1955
|
+
const childActor = nextState.children[def.id];
|
|
1956
|
+
if (childActor && !childActor._isExternal) {
|
|
1957
|
+
actorScope.stopChild(childActor);
|
|
1958
|
+
}
|
|
1959
|
+
delete nextState.children[def.id];
|
|
1960
|
+
}
|
|
1961
|
+
mutStateNodeSet.delete(exitStateNode);
|
|
1962
|
+
}
|
|
1963
|
+
historyValue = changedHistory || historyValue;
|
|
1964
|
+
};
|
|
1965
|
+
|
|
1966
|
+
// Exit states
|
|
1967
|
+
if (!isInitial) {
|
|
1968
|
+
exitStates();
|
|
1969
|
+
}
|
|
1970
|
+
let context = nextState.context;
|
|
1971
|
+
const transitionActions = [];
|
|
1972
|
+
const internalEvents = [];
|
|
1973
|
+
for (const t of filteredTransitions) {
|
|
1974
|
+
if (t.actions) {
|
|
1975
|
+
transitionActions.push(...toArray(t.actions));
|
|
1976
|
+
}
|
|
1977
|
+
const res = getCurrentTransitionResult(t);
|
|
1978
|
+
if (res.context) {
|
|
1979
|
+
context = res.context;
|
|
1980
|
+
}
|
|
1981
|
+
if (res.actions) {
|
|
1982
|
+
transitionActions.push(...res.actions);
|
|
1983
|
+
}
|
|
1984
|
+
if (res.internalEvents) {
|
|
1985
|
+
internalEvents.push(...res.internalEvents);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
if (internalEvents.length) {
|
|
1989
|
+
internalQueue.push(...internalEvents);
|
|
1990
|
+
}
|
|
1991
|
+
const enterStates = () => {
|
|
1992
|
+
const getMachineOutput = rootCompletionNode => {
|
|
1993
|
+
const rootNode = nextState.machine.root;
|
|
1994
|
+
if (rootNode.output === undefined) {
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
let completionOutput;
|
|
1998
|
+
if (rootCompletionNode.output !== undefined && rootCompletionNode.parent) {
|
|
1999
|
+
completionOutput = resolveOutput(rootCompletionNode.output, nextState.context, event, actorScope.self);
|
|
2000
|
+
} else if (rootCompletionNode.type === 'parallel') {
|
|
2001
|
+
const parallelDoneType = `xstate.done.state.${rootCompletionNode.id}`;
|
|
2002
|
+
const parallelDoneEvent = internalQueue.find(e => e.type === parallelDoneType);
|
|
2003
|
+
completionOutput = parallelDoneEvent?.output;
|
|
2004
|
+
}
|
|
2005
|
+
return resolveOutput(rootNode.output, nextState.context, createDoneStateEvent(rootCompletionNode.id, completionOutput), actorScope.self);
|
|
2006
|
+
};
|
|
2007
|
+
const statesToEnter = new Set();
|
|
2008
|
+
// those are states that were directly targeted or indirectly targeted by the explicit target
|
|
2009
|
+
// in other words, those are states for which initial actions should be executed
|
|
2010
|
+
// when we target `#deep_child` initial actions of its ancestors shouldn't be executed
|
|
2011
|
+
const statesForDefaultEntry = new Set();
|
|
2012
|
+
const addAncestorStatesToEnter = (ancestors, reentrancyDomain) => {
|
|
2013
|
+
for (const anc of ancestors) {
|
|
2014
|
+
if (!reentrancyDomain || isDescendant(anc, reentrancyDomain)) {
|
|
2015
|
+
statesToEnter.add(anc);
|
|
2016
|
+
}
|
|
2017
|
+
if (anc.type === 'parallel') {
|
|
2018
|
+
for (const child of getChildren(anc)) {
|
|
2019
|
+
if (!hasDescendantState(statesToEnter, child)) {
|
|
2020
|
+
statesToEnter.add(child);
|
|
2021
|
+
addDescendantStatesToEnter(child);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
};
|
|
2027
|
+
const addDescendantStatesToEnter = stateNode => {
|
|
2028
|
+
if (isHistoryNode(stateNode)) {
|
|
2029
|
+
if (historyValue[stateNode.id]) {
|
|
2030
|
+
const historyStateNodes = historyValue[stateNode.id];
|
|
2031
|
+
for (const s of historyStateNodes) {
|
|
2032
|
+
statesToEnter.add(s);
|
|
2033
|
+
addDescendantStatesToEnter(s);
|
|
2034
|
+
}
|
|
2035
|
+
for (const s of historyStateNodes) {
|
|
2036
|
+
addAncestorStatesToEnter(getProperAncestors(s, stateNode.parent), undefined);
|
|
2037
|
+
}
|
|
2038
|
+
} else {
|
|
2039
|
+
const historyDefaultTransition = resolveHistoryDefaultTransition(stateNode);
|
|
2040
|
+
const {
|
|
2041
|
+
targets
|
|
2042
|
+
} = getCurrentTransitionResult(historyDefaultTransition);
|
|
2043
|
+
for (const s of targets ?? []) {
|
|
2044
|
+
statesToEnter.add(s);
|
|
2045
|
+
if (historyDefaultTransition === stateNode.parent?.initial) {
|
|
2046
|
+
statesForDefaultEntry.add(stateNode.parent);
|
|
2047
|
+
}
|
|
2048
|
+
addDescendantStatesToEnter(s);
|
|
2049
|
+
}
|
|
2050
|
+
for (const s of targets ?? []) {
|
|
2051
|
+
addAncestorStatesToEnter(getProperAncestors(s, stateNode.parent), undefined);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (stateNode.type === 'compound') {
|
|
2057
|
+
const [initialState] = getCurrentTransitionResult(stateNode.initial).targets;
|
|
2058
|
+
if (!isHistoryNode(initialState)) {
|
|
2059
|
+
statesToEnter.add(initialState);
|
|
2060
|
+
statesForDefaultEntry.add(initialState);
|
|
2061
|
+
}
|
|
2062
|
+
addDescendantStatesToEnter(initialState);
|
|
2063
|
+
addAncestorStatesToEnter(getProperAncestors(initialState, stateNode), undefined);
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
if (stateNode.type === 'parallel') {
|
|
2067
|
+
for (const child of getChildren(stateNode)) {
|
|
2068
|
+
if (!hasDescendantState(statesToEnter, child)) {
|
|
2069
|
+
statesToEnter.add(child);
|
|
2070
|
+
statesForDefaultEntry.add(child);
|
|
2071
|
+
addDescendantStatesToEnter(child);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
};
|
|
2076
|
+
for (const transition of filteredTransitions) {
|
|
2077
|
+
const domain = getTransitionDomain(transition, currentSnapshot, event, actorScope);
|
|
2078
|
+
const {
|
|
2079
|
+
targets,
|
|
2080
|
+
reenter
|
|
2081
|
+
} = getCurrentTransitionResult(transition);
|
|
2082
|
+
for (const targetNode of targets ?? []) {
|
|
2083
|
+
if (!isHistoryNode(targetNode) && (transition.source !== targetNode || transition.source !== domain || reenter)) {
|
|
2084
|
+
statesToEnter.add(targetNode);
|
|
2085
|
+
statesForDefaultEntry.add(targetNode);
|
|
2086
|
+
}
|
|
2087
|
+
addDescendantStatesToEnter(targetNode);
|
|
2088
|
+
}
|
|
2089
|
+
const targetStates = getEffectiveTargetStates(transition, currentSnapshot, event, actorScope);
|
|
2090
|
+
for (const s of targetStates) {
|
|
2091
|
+
const ancestors = getProperAncestors(s, domain);
|
|
2092
|
+
if (domain?.type === 'parallel') {
|
|
2093
|
+
ancestors.push(domain);
|
|
2094
|
+
}
|
|
2095
|
+
addAncestorStatesToEnter(ancestors, !transition.source.parent && reenter ? undefined : domain);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
if (isInitial) {
|
|
2099
|
+
statesForDefaultEntry.add(currentSnapshot.machine.root);
|
|
2100
|
+
}
|
|
2101
|
+
const stateInputMap = {
|
|
2102
|
+
...currentSnapshot._stateInputs
|
|
2103
|
+
};
|
|
2104
|
+
for (const transition of filteredTransitions) {
|
|
2105
|
+
const {
|
|
2106
|
+
targets,
|
|
2107
|
+
input
|
|
2108
|
+
} = getCurrentTransitionResult(transition);
|
|
2109
|
+
if (input && targets) {
|
|
2110
|
+
for (const targetNode of targets) {
|
|
2111
|
+
stateInputMap[targetNode.id] = input;
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
const completedNodes = new Set();
|
|
2116
|
+
const children = {
|
|
2117
|
+
...currentSnapshot.children
|
|
2118
|
+
};
|
|
2119
|
+
for (const stateNodeToEnter of [...statesToEnter].sort((a, b) => a.order - b.order)) {
|
|
2120
|
+
mutStateNodeSet.add(stateNodeToEnter);
|
|
2121
|
+
const actions = [];
|
|
2122
|
+
let invoked = false;
|
|
2123
|
+
for (const invokeDef of stateNodeToEnter.invoke) {
|
|
2124
|
+
invoked = true;
|
|
2125
|
+
let src = invokeDef.logic;
|
|
2126
|
+
if (typeof src === 'function') {
|
|
2127
|
+
src = src({
|
|
2128
|
+
actors: currentSnapshot.machine.implementations.actors,
|
|
2129
|
+
context: nextState.context,
|
|
2130
|
+
event,
|
|
2131
|
+
self: actorScope.self
|
|
2132
|
+
});
|
|
2133
|
+
}
|
|
2134
|
+
const logic = typeof src === 'string' ? resolveReferencedActor(currentSnapshot.machine, src) : src;
|
|
2135
|
+
if (!logic) {
|
|
2136
|
+
throw new Error(`Actor logic '${typeof src === 'string' ? src : 'inline'}' not implemented in machine '${currentSnapshot.machine.id}'`);
|
|
2137
|
+
}
|
|
2138
|
+
const input = typeof invokeDef.input === 'function' ? invokeDef.input({
|
|
2139
|
+
self: actorScope.self,
|
|
2140
|
+
context: nextState.context,
|
|
2141
|
+
event
|
|
2142
|
+
}) : invokeDef.input;
|
|
2143
|
+
const actor = createActor(logic, {
|
|
2144
|
+
...invokeDef,
|
|
2145
|
+
input,
|
|
2146
|
+
parent: actorScope.self,
|
|
2147
|
+
syncSnapshot: !!invokeDef.onSnapshot
|
|
2148
|
+
});
|
|
2149
|
+
actions.push({
|
|
2150
|
+
action: builtInActions['@xstate.start'],
|
|
2151
|
+
args: [actor]
|
|
2152
|
+
});
|
|
2153
|
+
if (invokeDef.id) {
|
|
2154
|
+
children[invokeDef.id] = actor;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
if (invoked) {
|
|
2158
|
+
nextState = cloneMachineSnapshot(nextState, {
|
|
2159
|
+
children
|
|
2160
|
+
});
|
|
2161
|
+
}
|
|
2162
|
+
let context;
|
|
2163
|
+
const stateInput = stateInputMap[stateNodeToEnter.id];
|
|
2164
|
+
if (stateNodeToEnter.entry) {
|
|
2165
|
+
const [resultActions, nextContext, nextInternalEvents] = getStateActionsAndContext(stateNodeToEnter.entry, nextState.context, children, stateInput);
|
|
2166
|
+
actions.push(...resultActions);
|
|
2167
|
+
if (nextInternalEvents?.length) {
|
|
2168
|
+
internalQueue.push(...nextInternalEvents);
|
|
2169
|
+
}
|
|
2170
|
+
if (nextContext) {
|
|
2171
|
+
context = nextContext;
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
if (statesForDefaultEntry.has(stateNodeToEnter)) {
|
|
2175
|
+
const {
|
|
2176
|
+
actions: initialActions,
|
|
2177
|
+
input: initialInput
|
|
2178
|
+
} = getTransitionResult(stateNodeToEnter.initial, nextState, event, actorScope);
|
|
2179
|
+
if (initialActions) {
|
|
2180
|
+
actions.push(...initialActions);
|
|
2181
|
+
}
|
|
2182
|
+
if (initialInput && stateNodeToEnter.initial?.target) {
|
|
2183
|
+
for (const targetNode of stateNodeToEnter.initial.target) {
|
|
2184
|
+
stateInputMap[targetNode.id] = initialInput;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
}
|
|
2188
|
+
nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, actions);
|
|
2189
|
+
if (context) {
|
|
2190
|
+
nextState.context = context;
|
|
2191
|
+
}
|
|
2192
|
+
if (stateNodeToEnter.type !== 'final') {
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
const parent = stateNodeToEnter.parent;
|
|
2196
|
+
let ancestorMarker = parent?.type === 'parallel' ? parent : parent?.parent;
|
|
2197
|
+
let rootCompletionNode = ancestorMarker || stateNodeToEnter;
|
|
2198
|
+
if (parent?.type === 'compound') {
|
|
2199
|
+
internalQueue.push(createDoneStateEvent(parent.id, stateNodeToEnter.output !== undefined ? resolveOutput(stateNodeToEnter.output, nextState.context, event, actorScope.self) : undefined));
|
|
2200
|
+
}
|
|
2201
|
+
while (ancestorMarker?.type === 'parallel' && !completedNodes.has(ancestorMarker) && isInFinalState(mutStateNodeSet, ancestorMarker)) {
|
|
2202
|
+
completedNodes.add(ancestorMarker);
|
|
2203
|
+
const regionOutput = {};
|
|
2204
|
+
for (const region of getChildren(ancestorMarker)) {
|
|
2205
|
+
if (region.type === 'final') {
|
|
2206
|
+
regionOutput[region.key] = region.output !== undefined ? resolveOutput(region.output, nextState.context, event, actorScope.self) : undefined;
|
|
2207
|
+
continue;
|
|
2208
|
+
}
|
|
2209
|
+
if (region.type === 'parallel') {
|
|
2210
|
+
const regionDoneType = `xstate.done.state.${region.id}`;
|
|
2211
|
+
const regionDoneEvent = internalQueue.find(e => e.type === regionDoneType);
|
|
2212
|
+
regionOutput[region.key] = regionDoneEvent?.output;
|
|
2213
|
+
continue;
|
|
2214
|
+
}
|
|
2215
|
+
const finalChild = getChildren(region).find(s => s.type === 'final' && mutStateNodeSet.has(s));
|
|
2216
|
+
regionOutput[region.key] = finalChild?.output !== undefined ? resolveOutput(finalChild.output, nextState.context, event, actorScope.self) : undefined;
|
|
2217
|
+
}
|
|
2218
|
+
internalQueue.push(createDoneStateEvent(ancestorMarker.id, regionOutput));
|
|
2219
|
+
rootCompletionNode = ancestorMarker;
|
|
2220
|
+
ancestorMarker = ancestorMarker.parent;
|
|
2221
|
+
}
|
|
2222
|
+
if (ancestorMarker) {
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
nextState = cloneMachineSnapshot(nextState, {
|
|
2226
|
+
status: 'done',
|
|
2227
|
+
output: getMachineOutput(rootCompletionNode)
|
|
2228
|
+
});
|
|
2229
|
+
}
|
|
2230
|
+
const inputChanged = JSON.stringify(stateInputMap) !== JSON.stringify(currentSnapshot._stateInputs || {});
|
|
2231
|
+
if (inputChanged) {
|
|
2232
|
+
nextState = cloneMachineSnapshot(nextState, {
|
|
2233
|
+
_stateInputs: stateInputMap
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
};
|
|
2237
|
+
|
|
2238
|
+
// Execute transition content
|
|
2239
|
+
nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, transitionActions);
|
|
2240
|
+
if (context && context !== currentSnapshot.context) {
|
|
2241
|
+
nextState = cloneMachineSnapshot(nextState, {
|
|
2242
|
+
context
|
|
2243
|
+
});
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// Enter states
|
|
2247
|
+
enterStates();
|
|
2248
|
+
const nextStateNodes = [...mutStateNodeSet];
|
|
2249
|
+
if (nextState.status === 'done') {
|
|
2250
|
+
const allExitActions = [];
|
|
2251
|
+
const nextStateNodesToExit = nextStateNodes.sort((a, b) => b.order - a.order);
|
|
2252
|
+
nextStateNodesToExit.forEach(stateNode => {
|
|
2253
|
+
if (stateNode.exit) {
|
|
2254
|
+
const stateInput = nextState._stateInputs?.[stateNode.id];
|
|
2255
|
+
const [exitActions,, nextInternalEvents] = getStateActionsAndContext(stateNode.exit, nextState.context, nextState.children, stateInput);
|
|
2256
|
+
allExitActions.push(...exitActions);
|
|
2257
|
+
if (nextInternalEvents?.length) {
|
|
2258
|
+
internalQueue.push(...nextInternalEvents);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
});
|
|
2262
|
+
nextState = resolveAndExecuteActionsWithContext(nextState, event, actorScope, allExitActions);
|
|
2263
|
+
}
|
|
2264
|
+
if (historyValue === currentSnapshot.historyValue && currentSnapshot._nodes.length === mutStateNodeSet.size && currentSnapshot._nodes.every(node => mutStateNodeSet.has(node))) {
|
|
2265
|
+
// If context was changed (e.g. by entry actions during self-transition),
|
|
2266
|
+
// clone to ensure reference inequality for eventless transition re-evaluation
|
|
2267
|
+
if (nextState.context !== originalContext) {
|
|
2268
|
+
return [cloneMachineSnapshot(nextState), actions];
|
|
2269
|
+
}
|
|
2270
|
+
return [nextState, actions];
|
|
2271
|
+
}
|
|
2272
|
+
return [cloneMachineSnapshot(nextState, {
|
|
2273
|
+
_nodes: nextStateNodes,
|
|
2274
|
+
historyValue
|
|
2275
|
+
}), actions];
|
|
2276
|
+
} finally {
|
|
2277
|
+
actorScope.actionExecutor = originalExecutor;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
/**
|
|
2282
|
+
* Gets the transition result for a given transition without executing the
|
|
2283
|
+
* transition.
|
|
2284
|
+
*/
|
|
2285
|
+
function getTransitionResult(transition, snapshot, event, actorScope) {
|
|
2286
|
+
if (transition.to) {
|
|
2287
|
+
const actions = [];
|
|
2288
|
+
const internalEvents = [];
|
|
2289
|
+
const enqueue = createTransitionEnqueue(actorScope, actions, internalEvents);
|
|
2290
|
+
const res = transition.to({
|
|
2291
|
+
context: snapshot.context,
|
|
2292
|
+
event,
|
|
2293
|
+
value: snapshot.value,
|
|
2294
|
+
children: snapshot.children,
|
|
2295
|
+
parent: actorScope.self._parent,
|
|
2296
|
+
self: actorScope.self,
|
|
2297
|
+
actions: snapshot.machine.implementations.actions,
|
|
2298
|
+
actors: snapshot.machine.implementations.actors,
|
|
2299
|
+
guards: snapshot.machine.implementations.guards,
|
|
2300
|
+
delays: snapshot.machine.implementations.delays
|
|
2301
|
+
}, enqueue);
|
|
2302
|
+
const targets = res?.target ? resolveTarget(transition.source, toArray(res.target)) : undefined;
|
|
2303
|
+
// Resolve input for .to transitions
|
|
2304
|
+
const resolvedInput = typeof transition.input === 'function' ? transition.input({
|
|
2305
|
+
context: snapshot.context,
|
|
2306
|
+
event
|
|
2307
|
+
}) : transition.input;
|
|
2308
|
+
return {
|
|
2309
|
+
targets: targets,
|
|
2310
|
+
context: res?.context,
|
|
2311
|
+
reenter: res?.reenter,
|
|
2312
|
+
actions,
|
|
2313
|
+
internalEvents,
|
|
2314
|
+
input: resolvedInput
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
// Resolve input for regular transitions
|
|
2319
|
+
const resolvedInput = typeof transition.input === 'function' ? transition.input({
|
|
2320
|
+
context: snapshot.context,
|
|
2321
|
+
event
|
|
2322
|
+
}) : transition.input;
|
|
2323
|
+
return {
|
|
2324
|
+
targets: transition.target,
|
|
2325
|
+
context: undefined,
|
|
2326
|
+
reenter: transition.reenter,
|
|
2327
|
+
actions: undefined,
|
|
2328
|
+
internalEvents: undefined,
|
|
2329
|
+
input: resolvedInput
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
function macrostep(snapshot, event, actorScope, internalQueue) {
|
|
2333
|
+
let nextSnapshot = snapshot;
|
|
2334
|
+
const microsteps = [];
|
|
2335
|
+
function addMicrostep(step, transitions) {
|
|
2336
|
+
// collect microsteps; surfaced on the enclosing '@xstate.transition' event
|
|
2337
|
+
// via its `microsteps[]` facet (there is no standalone microstep event)
|
|
2338
|
+
const collectedMicrosteps = actorScope.self._collectedMicrosteps || [];
|
|
2339
|
+
collectedMicrosteps.push(...transitions);
|
|
2340
|
+
actorScope.self._collectedMicrosteps = collectedMicrosteps;
|
|
2341
|
+
microsteps.push(step);
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
// Handle stop event
|
|
2345
|
+
if (event.type === XSTATE_STOP) {
|
|
2346
|
+
nextSnapshot = cloneMachineSnapshot(stopChildren(nextSnapshot, actorScope), {
|
|
2347
|
+
status: 'stopped'
|
|
2348
|
+
});
|
|
2349
|
+
addMicrostep([nextSnapshot, []], []);
|
|
2350
|
+
return {
|
|
2351
|
+
snapshot: nextSnapshot,
|
|
2352
|
+
microsteps
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
let nextEvent = event;
|
|
2356
|
+
|
|
2357
|
+
// Assume the state is at rest (no raised events)
|
|
2358
|
+
// Determine the next state based on the next microstep
|
|
2359
|
+
if (nextEvent.type !== XSTATE_INIT) {
|
|
2360
|
+
const currentEvent = nextEvent;
|
|
2361
|
+
const isErr = isErrorActorEvent(currentEvent);
|
|
2362
|
+
const transitions = nextSnapshot.machine.getTransitionData(nextSnapshot, currentEvent, actorScope.self);
|
|
2363
|
+
if (isErr && !transitions.length) {
|
|
2364
|
+
// TODO: we should likely only allow transitions selected by very explicit descriptors
|
|
2365
|
+
// `*` shouldn't be matched, likely `xstate.error.*` shouldn't be either
|
|
2366
|
+
// similarly `xstate.error.actor.*` and `xstate.error.actor.todo.*` have to be considered too
|
|
2367
|
+
nextSnapshot = cloneMachineSnapshot(snapshot, {
|
|
2368
|
+
status: 'error',
|
|
2369
|
+
error: currentEvent.error
|
|
2370
|
+
});
|
|
2371
|
+
addMicrostep([nextSnapshot, []], []);
|
|
2372
|
+
return {
|
|
2373
|
+
snapshot: nextSnapshot,
|
|
2374
|
+
microsteps
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
const step = microstep(transitions, snapshot, actorScope, nextEvent, false,
|
|
2378
|
+
// isInitial
|
|
2379
|
+
internalQueue);
|
|
2380
|
+
nextSnapshot = step[0];
|
|
2381
|
+
addMicrostep(step, transitions);
|
|
2382
|
+
}
|
|
2383
|
+
let shouldSelectEventlessTransitions = true;
|
|
2384
|
+
const maxIterations = snapshot.machine.options?.maxIterations ?? Infinity;
|
|
2385
|
+
let iterationCount = 0;
|
|
2386
|
+
let microstepCount = 0;
|
|
2387
|
+
while (nextSnapshot.status === 'active') {
|
|
2388
|
+
microstepCount++;
|
|
2389
|
+
if (microstepCount > 1000) {
|
|
2390
|
+
throw new Error('Microstep count exceeded 1000');
|
|
2391
|
+
}
|
|
2392
|
+
iterationCount++;
|
|
2393
|
+
if (iterationCount > maxIterations) {
|
|
2394
|
+
throw new Error(`Infinite loop detected: the machine has processed more than ${maxIterations} microsteps without reaching a stable state. This usually happens when there's a cycle of transitions (e.g., eventless transitions or raised events causing state A -> B -> C -> A).` );
|
|
2395
|
+
}
|
|
2396
|
+
let enabledTransitions = shouldSelectEventlessTransitions ? selectEventlessTransitions(nextSnapshot, nextEvent, actorScope) : [];
|
|
2397
|
+
|
|
2398
|
+
// eventless transitions should always be selected after selecting *regular* transitions
|
|
2399
|
+
// by assigning `undefined` to `previousState` we ensure that `shouldSelectEventlessTransitions` gets always computed to true in such a case
|
|
2400
|
+
const previousState = enabledTransitions.length ? nextSnapshot : undefined;
|
|
2401
|
+
if (!enabledTransitions.length) {
|
|
2402
|
+
if (!internalQueue.length) {
|
|
2403
|
+
break;
|
|
2404
|
+
}
|
|
2405
|
+
nextEvent = internalQueue.shift();
|
|
2406
|
+
enabledTransitions = nextSnapshot.machine.getTransitionData(nextSnapshot, nextEvent, actorScope.self);
|
|
2407
|
+
}
|
|
2408
|
+
const step = microstep(enabledTransitions, nextSnapshot, actorScope, nextEvent, false, internalQueue);
|
|
2409
|
+
nextSnapshot = step[0];
|
|
2410
|
+
shouldSelectEventlessTransitions = nextSnapshot !== previousState;
|
|
2411
|
+
addMicrostep(step, enabledTransitions);
|
|
2412
|
+
}
|
|
2413
|
+
if (nextSnapshot.status !== 'active' && nextSnapshot.children) {
|
|
2414
|
+
stopChildren(nextSnapshot, actorScope);
|
|
2415
|
+
}
|
|
2416
|
+
return {
|
|
2417
|
+
snapshot: nextSnapshot,
|
|
2418
|
+
microsteps
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
/**
|
|
2423
|
+
* Resolves a partial state value with its full representation in the state
|
|
2424
|
+
* node's machine.
|
|
2425
|
+
*
|
|
2426
|
+
* @param stateValue The partial state value to resolve.
|
|
2427
|
+
*/
|
|
2428
|
+
function resolveStateValue(rootNode, stateValue) {
|
|
2429
|
+
const allStateNodes = getAllStateNodes(getStateNodes(rootNode, stateValue));
|
|
2430
|
+
return getStateValue(rootNode, allStateNodes);
|
|
2431
|
+
}
|
|
2432
|
+
function createEnqueueObject(props, action) {
|
|
2433
|
+
const enqueueFn = (fn, ...args) => {
|
|
2434
|
+
action(fn, ...args);
|
|
2435
|
+
};
|
|
2436
|
+
Object.assign(enqueueFn, {
|
|
2437
|
+
cancel: () => {},
|
|
2438
|
+
emit: () => {},
|
|
2439
|
+
log: () => {},
|
|
2440
|
+
raise: () => {},
|
|
2441
|
+
spawn: () => ({}),
|
|
2442
|
+
sendTo: () => {},
|
|
2443
|
+
stop: () => {},
|
|
2444
|
+
listen: () => ({}),
|
|
2445
|
+
subscribeTo: () => ({}),
|
|
2446
|
+
...props
|
|
2447
|
+
});
|
|
2448
|
+
return enqueueFn;
|
|
2449
|
+
}
|
|
2450
|
+
function hasEffect(transition, context, event, snapshot, self) {
|
|
2451
|
+
if (transition.to) {
|
|
2452
|
+
return transitionToHasEffect(transition.to, context, event, snapshot, self, snapshot.machine.implementations);
|
|
2453
|
+
}
|
|
2454
|
+
return false;
|
|
2455
|
+
}
|
|
2456
|
+
function transitionToHasEffect(transitionTo, context, event, snapshot, self, implementations) {
|
|
2457
|
+
let hasEffect = false;
|
|
2458
|
+
let res;
|
|
2459
|
+
try {
|
|
2460
|
+
const triggerEffect = () => {
|
|
2461
|
+
hasEffect = true;
|
|
2462
|
+
throw new Error('Effect triggered');
|
|
2463
|
+
};
|
|
2464
|
+
res = transitionTo({
|
|
2465
|
+
context,
|
|
2466
|
+
event,
|
|
2467
|
+
self,
|
|
2468
|
+
value: snapshot.value,
|
|
2469
|
+
children: snapshot.children,
|
|
2470
|
+
parent: {
|
|
2471
|
+
send: triggerEffect
|
|
2472
|
+
},
|
|
2473
|
+
actions: implementations.actions,
|
|
2474
|
+
actors: implementations.actors,
|
|
2475
|
+
guards: implementations.guards,
|
|
2476
|
+
delays: implementations.delays
|
|
2477
|
+
}, createEnqueueObject({
|
|
2478
|
+
emit: triggerEffect,
|
|
2479
|
+
cancel: triggerEffect,
|
|
2480
|
+
log: triggerEffect,
|
|
2481
|
+
raise: triggerEffect,
|
|
2482
|
+
spawn: triggerEffect,
|
|
2483
|
+
sendTo: triggerEffect,
|
|
2484
|
+
stop: triggerEffect
|
|
2485
|
+
}, triggerEffect));
|
|
2486
|
+
} catch (err) {
|
|
2487
|
+
if (hasEffect) {
|
|
2488
|
+
return true;
|
|
2489
|
+
}
|
|
2490
|
+
throw err;
|
|
2491
|
+
}
|
|
2492
|
+
return res !== undefined;
|
|
2493
|
+
}
|
|
2494
|
+
function stopChildren(snapshot, actorScope) {
|
|
2495
|
+
let children;
|
|
2496
|
+
if (!snapshot.children || (children = Object.values(snapshot.children).filter(Boolean)).length === 0) {
|
|
2497
|
+
return snapshot;
|
|
2498
|
+
}
|
|
2499
|
+
for (const child of children) {
|
|
2500
|
+
actorScope.stopChild(child);
|
|
2501
|
+
}
|
|
2502
|
+
return cloneMachineSnapshot(snapshot, {
|
|
2503
|
+
children: {}
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
function selectEventlessTransitions(snapshot, event, actorScope) {
|
|
2507
|
+
const enabledTransitionSet = new Set();
|
|
2508
|
+
const atomicStates = snapshot._nodes.filter(isAtomicStateNode);
|
|
2509
|
+
for (const atomicStateNode of atomicStates) {
|
|
2510
|
+
loop: for (let stateNode = atomicStateNode; stateNode; stateNode = stateNode.parent) {
|
|
2511
|
+
if (!stateNode.always) {
|
|
2512
|
+
continue;
|
|
2513
|
+
}
|
|
2514
|
+
for (const transition of stateNode.always) {
|
|
2515
|
+
if (evaluateCandidate(transition, event, snapshot, stateNode, actorScope.self)) {
|
|
2516
|
+
enabledTransitionSet.add(transition);
|
|
2517
|
+
break loop;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return removeConflictingTransitions(Array.from(enabledTransitionSet), new Set(snapshot._nodes), snapshot, event, actorScope);
|
|
2523
|
+
}
|
|
2524
|
+
function evaluateCandidate(candidate, event, snapshot, stateNode, self) {
|
|
2525
|
+
if (candidate.guard) {
|
|
2526
|
+
const guardArgs = {
|
|
2527
|
+
context: snapshot.context,
|
|
2528
|
+
event,
|
|
2529
|
+
self,
|
|
2530
|
+
parent: self._parent,
|
|
2531
|
+
children: snapshot.children,
|
|
2532
|
+
actions: stateNode.machine.implementations.actions,
|
|
2533
|
+
actors: stateNode.machine.implementations.actors,
|
|
2534
|
+
guards: stateNode.machine.implementations.guards,
|
|
2535
|
+
delays: stateNode.machine.implementations.delays,
|
|
2536
|
+
_snapshot: snapshot
|
|
2537
|
+
};
|
|
2538
|
+
const guardConfig = candidate.guard;
|
|
2539
|
+
const guardParams = typeof guardConfig?.params === 'function' ? guardConfig.params({
|
|
2540
|
+
context: snapshot.context,
|
|
2541
|
+
event
|
|
2542
|
+
}) : guardConfig?.params;
|
|
2543
|
+
let guardPassed = true;
|
|
2544
|
+
if (typeof guardConfig === 'function') {
|
|
2545
|
+
guardPassed = guardConfig(guardArgs, guardParams);
|
|
2546
|
+
} else if (typeof guardConfig?.type === 'string') {
|
|
2547
|
+
const guardImpl = stateNode.machine.implementations.guards[guardConfig.type];
|
|
2548
|
+
if (!guardImpl) {
|
|
2549
|
+
// A typo'd guard name must fail loudly — silently passing would
|
|
2550
|
+
// take the guarded transition and corrupt machine behavior.
|
|
2551
|
+
throw new Error(`Guard '${guardConfig.type}' is not implemented in machine '${stateNode.machine.id}'. Available guards: ${Object.keys(stateNode.machine.implementations.guards).map(key => `'${key}'`).join(', ') || '(none)'}.` );
|
|
2552
|
+
}
|
|
2553
|
+
guardPassed = guardImpl(guardArgs, guardParams);
|
|
2554
|
+
}
|
|
2555
|
+
if (!guardPassed) {
|
|
2556
|
+
return false;
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
if (candidate.to) {
|
|
2560
|
+
return transitionToHasEffect(candidate.to, snapshot.context, event, snapshot, self, stateNode.machine.implementations);
|
|
2561
|
+
}
|
|
2562
|
+
return true;
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
function isMachineSnapshot(value) {
|
|
2566
|
+
return !!value && typeof value === 'object' && 'machine' in value && 'value' in value;
|
|
2567
|
+
}
|
|
2568
|
+
let emptyCanActor;
|
|
2569
|
+
let emptyCanActorScope;
|
|
2570
|
+
function getEmptyCanActor() {
|
|
2571
|
+
return emptyCanActor ??= createEmptyActor();
|
|
2572
|
+
}
|
|
2573
|
+
function getEmptyCanActorScope() {
|
|
2574
|
+
if (emptyCanActorScope) {
|
|
2575
|
+
return emptyCanActorScope;
|
|
2576
|
+
}
|
|
2577
|
+
const actor = getEmptyCanActor();
|
|
2578
|
+
emptyCanActorScope = {
|
|
2579
|
+
self: actor,
|
|
2580
|
+
logger: () => {},
|
|
2581
|
+
id: '',
|
|
2582
|
+
sessionId: '',
|
|
2583
|
+
defer: () => {},
|
|
2584
|
+
system: actor.system,
|
|
2585
|
+
stopChild: () => {},
|
|
2586
|
+
emit: () => {},
|
|
2587
|
+
actionExecutor: () => {}
|
|
2588
|
+
};
|
|
2589
|
+
return emptyCanActorScope;
|
|
2590
|
+
}
|
|
2591
|
+
const machineSnapshotMatches = function matches(testValue) {
|
|
2592
|
+
return matchesState(testValue, this.value);
|
|
2593
|
+
};
|
|
2594
|
+
const machineSnapshotHasTag = function hasTag(tag) {
|
|
2595
|
+
return this.tags.has(tag);
|
|
2596
|
+
};
|
|
2597
|
+
const machineSnapshotCan = function can(event) {
|
|
2598
|
+
if (!this.machine) {
|
|
2599
|
+
console.warn(`state.can(...) used outside of a machine-created State object; this will always return false.`);
|
|
2600
|
+
}
|
|
2601
|
+
const emptyActor = getEmptyCanActor();
|
|
2602
|
+
const emptyActorScope = getEmptyCanActorScope();
|
|
2603
|
+
const transitionData = this.machine.getTransitionData(this, event, emptyActor);
|
|
2604
|
+
if (!transitionData?.length) {
|
|
2605
|
+
return false;
|
|
2606
|
+
}
|
|
2607
|
+
|
|
2608
|
+
// Check that at least one transition is not forbidden
|
|
2609
|
+
for (const transition of transitionData) {
|
|
2610
|
+
if (transition.target !== undefined) {
|
|
2611
|
+
return true;
|
|
2612
|
+
}
|
|
2613
|
+
const res = getTransitionResult(transition, this, event, emptyActorScope);
|
|
2614
|
+
if (res.targets?.length || res.context || hasEffect(transition, this.context, event, this, emptyActor)) {
|
|
2615
|
+
return true;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
return false;
|
|
2619
|
+
};
|
|
2620
|
+
const machineSnapshotToJSON = function toJSON() {
|
|
2621
|
+
const {
|
|
2622
|
+
_nodes: nodes,
|
|
2623
|
+
_stateInputs,
|
|
2624
|
+
tags,
|
|
2625
|
+
machine,
|
|
2626
|
+
getMeta,
|
|
2627
|
+
getInputs,
|
|
2628
|
+
toJSON,
|
|
2629
|
+
can,
|
|
2630
|
+
hasTag,
|
|
2631
|
+
matches,
|
|
2632
|
+
...jsonValues
|
|
2633
|
+
} = this;
|
|
2634
|
+
return {
|
|
2635
|
+
...jsonValues,
|
|
2636
|
+
tags: Array.from(tags)
|
|
2637
|
+
};
|
|
2638
|
+
};
|
|
2639
|
+
const machineSnapshotGetMeta = function getMeta() {
|
|
2640
|
+
const meta = {};
|
|
2641
|
+
for (const stateNode of this._nodes) {
|
|
2642
|
+
if (stateNode.meta !== undefined) {
|
|
2643
|
+
meta[stateNode.id] = stateNode.meta;
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
return meta;
|
|
2647
|
+
};
|
|
2648
|
+
const machineSnapshotGetInputs = function getInputs() {
|
|
2649
|
+
return this._stateInputs;
|
|
2650
|
+
};
|
|
2651
|
+
function collectTags(stateNodes) {
|
|
2652
|
+
const tags = new Set();
|
|
2653
|
+
for (const stateNode of stateNodes) {
|
|
2654
|
+
for (const tag of stateNode.tags) {
|
|
2655
|
+
tags.add(tag);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
return tags;
|
|
2659
|
+
}
|
|
2660
|
+
function createMachineSnapshot(config, machine) {
|
|
2661
|
+
return {
|
|
2662
|
+
status: config.status,
|
|
2663
|
+
output: config.output,
|
|
2664
|
+
error: config.error,
|
|
2665
|
+
machine,
|
|
2666
|
+
context: config.context,
|
|
2667
|
+
_nodes: config._nodes,
|
|
2668
|
+
value: config.value ?? getStateValue(machine.root, config._nodes),
|
|
2669
|
+
tags: collectTags(config._nodes),
|
|
2670
|
+
children: config.children,
|
|
2671
|
+
historyValue: config.historyValue || {},
|
|
2672
|
+
_stateInputs: config._stateInputs || {},
|
|
2673
|
+
matches: machineSnapshotMatches,
|
|
2674
|
+
hasTag: machineSnapshotHasTag,
|
|
2675
|
+
can: machineSnapshotCan,
|
|
2676
|
+
getMeta: machineSnapshotGetMeta,
|
|
2677
|
+
getInputs: machineSnapshotGetInputs,
|
|
2678
|
+
toJSON: machineSnapshotToJSON
|
|
2679
|
+
};
|
|
2680
|
+
}
|
|
2681
|
+
function cloneMachineSnapshot(snapshot, config = {}) {
|
|
2682
|
+
const configWithSnapshot = {
|
|
2683
|
+
...snapshot,
|
|
2684
|
+
...config
|
|
2685
|
+
};
|
|
2686
|
+
if ((config._nodes ?? snapshot._nodes) === snapshot._nodes) {
|
|
2687
|
+
return {
|
|
2688
|
+
status: configWithSnapshot.status,
|
|
2689
|
+
output: configWithSnapshot.output,
|
|
2690
|
+
error: configWithSnapshot.error,
|
|
2691
|
+
machine: snapshot.machine,
|
|
2692
|
+
context: configWithSnapshot.context,
|
|
2693
|
+
_nodes: snapshot._nodes,
|
|
2694
|
+
value: snapshot.value,
|
|
2695
|
+
tags: snapshot.tags,
|
|
2696
|
+
children: configWithSnapshot.children,
|
|
2697
|
+
historyValue: configWithSnapshot.historyValue || {},
|
|
2698
|
+
_stateInputs: configWithSnapshot._stateInputs || {},
|
|
2699
|
+
matches: machineSnapshotMatches,
|
|
2700
|
+
hasTag: machineSnapshotHasTag,
|
|
2701
|
+
can: machineSnapshotCan,
|
|
2702
|
+
getMeta: machineSnapshotGetMeta,
|
|
2703
|
+
getInputs: machineSnapshotGetInputs,
|
|
2704
|
+
toJSON: machineSnapshotToJSON
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
return createMachineSnapshot({
|
|
2708
|
+
...configWithSnapshot,
|
|
2709
|
+
value: undefined
|
|
2710
|
+
}, snapshot.machine);
|
|
2711
|
+
}
|
|
2712
|
+
function serializeHistoryValue(historyValue) {
|
|
2713
|
+
const result = {};
|
|
2714
|
+
for (const key in historyValue) {
|
|
2715
|
+
const value = historyValue[key];
|
|
2716
|
+
if (Array.isArray(value)) {
|
|
2717
|
+
result[key] = value.map(item => ({
|
|
2718
|
+
id: item.id
|
|
2719
|
+
}));
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
return result;
|
|
2723
|
+
}
|
|
2724
|
+
function getPersistedSnapshot(snapshot, options) {
|
|
2725
|
+
const {
|
|
2726
|
+
_nodes: nodes,
|
|
2727
|
+
_stateInputs,
|
|
2728
|
+
tags,
|
|
2729
|
+
machine,
|
|
2730
|
+
children,
|
|
2731
|
+
context,
|
|
2732
|
+
can,
|
|
2733
|
+
hasTag,
|
|
2734
|
+
matches,
|
|
2735
|
+
getMeta,
|
|
2736
|
+
getInputs,
|
|
2737
|
+
toJSON,
|
|
2738
|
+
...jsonValues
|
|
2739
|
+
} = snapshot;
|
|
2740
|
+
const childrenJson = {};
|
|
2741
|
+
for (const id in children) {
|
|
2742
|
+
const child = children[id];
|
|
2743
|
+
if (typeof child.src !== 'string' && (!options || !('__unsafeAllowInlineActors' in options))) {
|
|
2744
|
+
throw new Error('An inline child actor cannot be persisted.');
|
|
2745
|
+
}
|
|
2746
|
+
childrenJson[id] = {
|
|
2747
|
+
snapshot: child.getPersistedSnapshot(options),
|
|
2748
|
+
src: child.src,
|
|
2749
|
+
systemId: child.systemId,
|
|
2750
|
+
syncSnapshot: child._syncSnapshot
|
|
2751
|
+
};
|
|
2752
|
+
}
|
|
2753
|
+
const persisted = {
|
|
2754
|
+
...jsonValues,
|
|
2755
|
+
context: persistContext(context),
|
|
2756
|
+
children: childrenJson,
|
|
2757
|
+
historyValue: serializeHistoryValue(jsonValues.historyValue)
|
|
2758
|
+
};
|
|
2759
|
+
if (machine.version !== undefined) {
|
|
2760
|
+
persisted.version = machine.version;
|
|
2761
|
+
}
|
|
2762
|
+
return persisted;
|
|
2763
|
+
}
|
|
2764
|
+
function persistContext(contextPart) {
|
|
2765
|
+
let copy;
|
|
2766
|
+
for (const key in contextPart) {
|
|
2767
|
+
const value = contextPart[key];
|
|
2768
|
+
if (value && typeof value === 'object') {
|
|
2769
|
+
if ('sessionId' in value && 'send' in value && 'ref' in value) {
|
|
2770
|
+
copy ??= Array.isArray(contextPart) ? contextPart.slice() : {
|
|
2771
|
+
...contextPart
|
|
2772
|
+
};
|
|
2773
|
+
copy[key] = {
|
|
2774
|
+
xstate$$type: $$ACTOR_TYPE,
|
|
2775
|
+
id: value.id
|
|
2776
|
+
};
|
|
2777
|
+
} else {
|
|
2778
|
+
const result = persistContext(value);
|
|
2779
|
+
if (result !== value) {
|
|
2780
|
+
copy ??= Array.isArray(contextPart) ? contextPart.slice() : {
|
|
2781
|
+
...contextPart
|
|
2782
|
+
};
|
|
2783
|
+
copy[key] = result;
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
return copy ?? contextPart;
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
function matchesState(parentStateId, childStateId) {
|
|
2792
|
+
const parentStateValue = toStateValue(parentStateId);
|
|
2793
|
+
const childStateValue = toStateValue(childStateId);
|
|
2794
|
+
if (typeof childStateValue === 'string') {
|
|
2795
|
+
if (typeof parentStateValue === 'string') {
|
|
2796
|
+
return childStateValue === parentStateValue;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
// Parent more specific than child
|
|
2800
|
+
return false;
|
|
2801
|
+
}
|
|
2802
|
+
if (typeof parentStateValue === 'string') {
|
|
2803
|
+
return parentStateValue in childStateValue;
|
|
2804
|
+
}
|
|
2805
|
+
return Object.keys(parentStateValue).every(key => {
|
|
2806
|
+
if (!(key in childStateValue)) {
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
return matchesState(parentStateValue[key], childStateValue[key]);
|
|
2810
|
+
});
|
|
2811
|
+
}
|
|
2812
|
+
function checkStateIn(snapshot, stateValue) {
|
|
2813
|
+
if (typeof stateValue === 'string' && isStateId(stateValue)) {
|
|
2814
|
+
const target = snapshot.machine.getStateNodeById(stateValue);
|
|
2815
|
+
return snapshot._nodes.some(sn => sn === target);
|
|
2816
|
+
}
|
|
2817
|
+
return snapshot.matches(stateValue);
|
|
2818
|
+
}
|
|
2819
|
+
function toStatePath(stateId) {
|
|
2820
|
+
if (isArray(stateId)) {
|
|
2821
|
+
return stateId;
|
|
2822
|
+
}
|
|
2823
|
+
const result = [];
|
|
2824
|
+
let segment = '';
|
|
2825
|
+
for (let i = 0; i < stateId.length; i++) {
|
|
2826
|
+
const char = stateId.charCodeAt(i);
|
|
2827
|
+
switch (char) {
|
|
2828
|
+
// \
|
|
2829
|
+
case 92:
|
|
2830
|
+
// consume the next character
|
|
2831
|
+
segment += stateId[i + 1];
|
|
2832
|
+
// and skip over it
|
|
2833
|
+
i++;
|
|
2834
|
+
continue;
|
|
2835
|
+
// .
|
|
2836
|
+
case 46:
|
|
2837
|
+
result.push(segment);
|
|
2838
|
+
segment = '';
|
|
2839
|
+
continue;
|
|
2840
|
+
}
|
|
2841
|
+
segment += stateId[i];
|
|
2842
|
+
}
|
|
2843
|
+
result.push(segment);
|
|
2844
|
+
return result;
|
|
2845
|
+
}
|
|
2846
|
+
function toStateValue(stateValue) {
|
|
2847
|
+
if (isMachineSnapshot(stateValue)) {
|
|
2848
|
+
return stateValue.value;
|
|
2849
|
+
}
|
|
2850
|
+
if (typeof stateValue !== 'string') {
|
|
2851
|
+
return stateValue;
|
|
2852
|
+
}
|
|
2853
|
+
const statePath = toStatePath(stateValue);
|
|
2854
|
+
return pathToStateValue(statePath);
|
|
2855
|
+
}
|
|
2856
|
+
function pathToStateValue(statePath) {
|
|
2857
|
+
if (statePath.length === 1) {
|
|
2858
|
+
return statePath[0];
|
|
2859
|
+
}
|
|
2860
|
+
const value = {};
|
|
2861
|
+
let marker = value;
|
|
2862
|
+
for (let i = 0; i < statePath.length - 1; i++) {
|
|
2863
|
+
if (i === statePath.length - 2) {
|
|
2864
|
+
marker[statePath[i]] = statePath[i + 1];
|
|
2865
|
+
} else {
|
|
2866
|
+
const previous = marker;
|
|
2867
|
+
marker = {};
|
|
2868
|
+
previous[statePath[i]] = marker;
|
|
2869
|
+
}
|
|
2870
|
+
}
|
|
2871
|
+
return value;
|
|
2872
|
+
}
|
|
2873
|
+
function mapValues(collection, iteratee) {
|
|
2874
|
+
const result = {};
|
|
2875
|
+
const collectionKeys = Object.keys(collection);
|
|
2876
|
+
for (let i = 0; i < collectionKeys.length; i++) {
|
|
2877
|
+
const key = collectionKeys[i];
|
|
2878
|
+
result[key] = iteratee(collection[key], key, collection, i);
|
|
2879
|
+
}
|
|
2880
|
+
return result;
|
|
2881
|
+
}
|
|
2882
|
+
function toArrayStrict(value) {
|
|
2883
|
+
if (isArray(value)) {
|
|
2884
|
+
return value;
|
|
2885
|
+
}
|
|
2886
|
+
return [value];
|
|
2887
|
+
}
|
|
2888
|
+
function toArray(value) {
|
|
2889
|
+
if (value === undefined) {
|
|
2890
|
+
return [];
|
|
2891
|
+
}
|
|
2892
|
+
return toArrayStrict(value);
|
|
2893
|
+
}
|
|
2894
|
+
function resolveOutput(mapper, context, event, self) {
|
|
2895
|
+
if (typeof mapper === 'function') {
|
|
2896
|
+
return mapper({
|
|
2897
|
+
context,
|
|
2898
|
+
event,
|
|
2899
|
+
self
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2902
|
+
if (!!mapper && typeof mapper === 'object' && Object.values(mapper).some(val => typeof val === 'function')) {
|
|
2903
|
+
console.warn(`Dynamically mapping values to individual properties is deprecated. Use a single function that returns the mapped object instead.\nFound object containing properties whose values are possibly mapping functions: ${Object.entries(mapper).filter(([, value]) => typeof value === 'function').map(([key, value]) => `\n - ${key}: ${value.toString().replace(/\n\s*/g, '')}`).join('')}`);
|
|
2904
|
+
}
|
|
2905
|
+
return mapper;
|
|
2906
|
+
}
|
|
2907
|
+
function isArray(value) {
|
|
2908
|
+
return Array.isArray(value);
|
|
2909
|
+
}
|
|
2910
|
+
function isErrorActorEvent(event) {
|
|
2911
|
+
return event.type.startsWith('xstate.error.actor');
|
|
2912
|
+
}
|
|
2913
|
+
function toTransitionConfigArray(configLike) {
|
|
2914
|
+
return toArrayStrict(configLike).map(transitionLike => {
|
|
2915
|
+
if (typeof transitionLike === 'undefined' || typeof transitionLike === 'string') {
|
|
2916
|
+
return {
|
|
2917
|
+
target: transitionLike
|
|
2918
|
+
};
|
|
2919
|
+
}
|
|
2920
|
+
if (typeof transitionLike === 'function') {
|
|
2921
|
+
return {
|
|
2922
|
+
to: transitionLike
|
|
2923
|
+
};
|
|
2924
|
+
}
|
|
2925
|
+
return transitionLike;
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
function normalizeTarget(target) {
|
|
2929
|
+
if (target === undefined || target === TARGETLESS_KEY) {
|
|
2930
|
+
return undefined;
|
|
2931
|
+
}
|
|
2932
|
+
return toArray(target);
|
|
2933
|
+
}
|
|
2934
|
+
function toObserver(nextHandler, errorHandler, completionHandler) {
|
|
2935
|
+
const isObserver = typeof nextHandler === 'object';
|
|
2936
|
+
const self = isObserver ? nextHandler : undefined;
|
|
2937
|
+
return {
|
|
2938
|
+
next: (isObserver ? nextHandler.next : nextHandler)?.bind(self),
|
|
2939
|
+
error: (isObserver ? nextHandler.error : errorHandler)?.bind(self),
|
|
2940
|
+
complete: (isObserver ? nextHandler.complete : completionHandler)?.bind(self)
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
function createInvokeId(stateNodeId, index) {
|
|
2944
|
+
return `${index}.${stateNodeId}`;
|
|
2945
|
+
}
|
|
2946
|
+
function resolveReferencedActor(machine, src) {
|
|
2947
|
+
const match = src.match(/^xstate\.invoke\.(\d+)\.(.*)/);
|
|
2948
|
+
if (!match) {
|
|
2949
|
+
return machine.implementations.actors[src];
|
|
2950
|
+
}
|
|
2951
|
+
const [, indexStr, nodeId] = match;
|
|
2952
|
+
const node = machine.getStateNodeById(nodeId);
|
|
2953
|
+
const invokeConfig = node.config.invoke;
|
|
2954
|
+
const configSrc = (Array.isArray(invokeConfig) ? invokeConfig[indexStr] : invokeConfig).src;
|
|
2955
|
+
// A referenced actor may itself be registered by name.
|
|
2956
|
+
return typeof configSrc === 'string' ? machine.implementations.actors[configSrc] : configSrc;
|
|
2957
|
+
}
|
|
2958
|
+
function getAllOwnEventDescriptors(snapshot) {
|
|
2959
|
+
return [...new Set([...snapshot._nodes.flatMap(sn => sn.ownEvents)])];
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
/**
|
|
2963
|
+
* Checks if an event type matches an event descriptor, supporting wildcards.
|
|
2964
|
+
* Event descriptors can be:
|
|
2965
|
+
*
|
|
2966
|
+
* - Exact matches: "event.type"
|
|
2967
|
+
* - Wildcard: "*"
|
|
2968
|
+
* - Partial matches: "event.*"
|
|
2969
|
+
*
|
|
2970
|
+
* @param eventType - The actual event type string
|
|
2971
|
+
* @param descriptor - The event descriptor to match against
|
|
2972
|
+
* @returns True if the event type matches the descriptor
|
|
2973
|
+
*/
|
|
2974
|
+
function matchesEventDescriptor(eventType, descriptor) {
|
|
2975
|
+
if (descriptor === eventType) {
|
|
2976
|
+
return true;
|
|
2977
|
+
}
|
|
2978
|
+
if (descriptor === WILDCARD) {
|
|
2979
|
+
return true;
|
|
2980
|
+
}
|
|
2981
|
+
if (!descriptor.endsWith('.*')) {
|
|
2982
|
+
return false;
|
|
2983
|
+
}
|
|
2984
|
+
if (/.*\*.+/.test(descriptor)) {
|
|
2985
|
+
console.warn(`Wildcards can only be the last token of an event descriptor (e.g., "event.*") or the entire event descriptor ("*"). Check the "${descriptor}" event.`);
|
|
2986
|
+
}
|
|
2987
|
+
const partialEventTokens = descriptor.split('.');
|
|
2988
|
+
const eventTokens = eventType.split('.');
|
|
2989
|
+
for (let tokenIndex = 0; tokenIndex < partialEventTokens.length; tokenIndex++) {
|
|
2990
|
+
const partialEventToken = partialEventTokens[tokenIndex];
|
|
2991
|
+
const eventToken = eventTokens[tokenIndex];
|
|
2992
|
+
if (partialEventToken === '*') {
|
|
2993
|
+
const isLastToken = tokenIndex === partialEventTokens.length - 1;
|
|
2994
|
+
if (!isLastToken) {
|
|
2995
|
+
console.warn(`Infix wildcards in transition events are not allowed. Check the "${descriptor}" transition.`);
|
|
2996
|
+
}
|
|
2997
|
+
return isLastToken;
|
|
2998
|
+
}
|
|
2999
|
+
if (partialEventToken !== eventToken) {
|
|
3000
|
+
return false;
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
return true;
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
function createScheduledEventId(actor, id) {
|
|
3007
|
+
return `${actor.sessionId}.${id}`;
|
|
3008
|
+
}
|
|
3009
|
+
function createSystem(rootActor, options) {
|
|
3010
|
+
let idCounter = 0;
|
|
3011
|
+
const children = new Map();
|
|
3012
|
+
const keyedActors = new Map();
|
|
3013
|
+
const reverseKeyedActors = new WeakMap();
|
|
3014
|
+
const inspectionObservers = new Set();
|
|
3015
|
+
const timerMap = {};
|
|
3016
|
+
const {
|
|
3017
|
+
clock,
|
|
3018
|
+
logger
|
|
3019
|
+
} = options;
|
|
3020
|
+
|
|
3021
|
+
// Records a send on the *sender's* transition for the `sent[]` inspection
|
|
3022
|
+
// facet. Captures the send when it is initiated (including delayed sends that
|
|
3023
|
+
// may never deliver), keyed to the source actor's in-flight transition.
|
|
3024
|
+
const recordSent = (source, target, event, delay, id) => {
|
|
3025
|
+
if (!inspectionObservers.size || !source) {
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
const collected = source._collectedSent ??= [];
|
|
3029
|
+
collected.push({
|
|
3030
|
+
targetRef: target,
|
|
3031
|
+
targetId: target.id,
|
|
3032
|
+
event,
|
|
3033
|
+
delay,
|
|
3034
|
+
id
|
|
3035
|
+
});
|
|
3036
|
+
};
|
|
3037
|
+
const scheduler = {
|
|
3038
|
+
schedule: (source, target, event, delay, id = Math.random().toString(36).slice(2)) => {
|
|
3039
|
+
recordSent(source, target, event, delay, id);
|
|
3040
|
+
const scheduledEvent = {
|
|
3041
|
+
source,
|
|
3042
|
+
target,
|
|
3043
|
+
event,
|
|
3044
|
+
delay,
|
|
3045
|
+
id,
|
|
3046
|
+
startedAt: Date.now()
|
|
3047
|
+
};
|
|
3048
|
+
const scheduledEventId = createScheduledEventId(source, id);
|
|
3049
|
+
system._snapshot._scheduledEvents[scheduledEventId] = scheduledEvent;
|
|
3050
|
+
const timeout = clock.setTimeout(() => {
|
|
3051
|
+
delete timerMap[scheduledEventId];
|
|
3052
|
+
delete system._snapshot._scheduledEvents[scheduledEventId];
|
|
3053
|
+
|
|
3054
|
+
// The send was already recorded at schedule time on the sender's
|
|
3055
|
+
// transition, so deliver without re-recording.
|
|
3056
|
+
deliver(source, target, event);
|
|
3057
|
+
}, delay);
|
|
3058
|
+
timerMap[scheduledEventId] = timeout;
|
|
3059
|
+
},
|
|
3060
|
+
cancel: (source, id) => {
|
|
3061
|
+
const scheduledEventId = createScheduledEventId(source, id);
|
|
3062
|
+
const timeout = timerMap[scheduledEventId];
|
|
3063
|
+
delete timerMap[scheduledEventId];
|
|
3064
|
+
delete system._snapshot._scheduledEvents[scheduledEventId];
|
|
3065
|
+
if (timeout !== undefined) {
|
|
3066
|
+
clock.clearTimeout(timeout);
|
|
3067
|
+
}
|
|
3068
|
+
},
|
|
3069
|
+
cancelAll: actor => {
|
|
3070
|
+
for (const scheduledEventId in system._snapshot._scheduledEvents) {
|
|
3071
|
+
const scheduledEvent = system._snapshot._scheduledEvents[scheduledEventId];
|
|
3072
|
+
if (scheduledEvent.source === actor) {
|
|
3073
|
+
scheduler.cancel(actor, scheduledEvent.id);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
// Delivers an event to the target actor. Used by both `_relay` (which also
|
|
3079
|
+
// records the send) and the scheduler's timer (which already recorded it).
|
|
3080
|
+
const deliver = (source, target, event) => {
|
|
3081
|
+
const targetMachine = target.logic;
|
|
3082
|
+
const isInternalEvent = typeof targetMachine?.isInternalEventType === 'function' && targetMachine.isInternalEventType(event.type);
|
|
3083
|
+
if (isInternalEvent && source !== target) {
|
|
3084
|
+
throw new Error(`Internal event "${event.type}" cannot be sent to actor "${target.id}" from outside.`);
|
|
3085
|
+
}
|
|
3086
|
+
|
|
3087
|
+
// remember the last source for unified transition inspect event
|
|
3088
|
+
target._lastSourceRef = source;
|
|
3089
|
+
target._send(event);
|
|
3090
|
+
};
|
|
3091
|
+
const sendInspectionEvent = event => {
|
|
3092
|
+
if (!inspectionObservers.size) {
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3095
|
+
const resolvedInspectionEvent = {
|
|
3096
|
+
...event,
|
|
3097
|
+
rootId: rootActor.sessionId
|
|
3098
|
+
};
|
|
3099
|
+
inspectionObservers.forEach(observer => observer.next?.(resolvedInspectionEvent));
|
|
3100
|
+
};
|
|
3101
|
+
const system = {
|
|
3102
|
+
children,
|
|
3103
|
+
reverseKeyedActors,
|
|
3104
|
+
keyedActors,
|
|
3105
|
+
_snapshot: {
|
|
3106
|
+
_scheduledEvents: (options?.snapshot && options.snapshot.scheduler) ?? {}
|
|
3107
|
+
},
|
|
3108
|
+
_bookId: () => `x:${idCounter++}`,
|
|
3109
|
+
_register: (sessionId, actor) => {
|
|
3110
|
+
children.set(sessionId, actor);
|
|
3111
|
+
return sessionId;
|
|
3112
|
+
},
|
|
3113
|
+
_unregister: actor => {
|
|
3114
|
+
children.delete(actor.sessionId);
|
|
3115
|
+
const systemId = reverseKeyedActors.get(actor);
|
|
3116
|
+
if (systemId !== undefined) {
|
|
3117
|
+
keyedActors.delete(systemId);
|
|
3118
|
+
reverseKeyedActors.delete(actor);
|
|
3119
|
+
}
|
|
3120
|
+
},
|
|
3121
|
+
get: systemId => {
|
|
3122
|
+
return keyedActors.get(systemId);
|
|
3123
|
+
},
|
|
3124
|
+
getAll: () => {
|
|
3125
|
+
return Object.fromEntries(keyedActors.entries());
|
|
3126
|
+
},
|
|
3127
|
+
_set: (systemId, actor) => {
|
|
3128
|
+
const existing = keyedActors.get(systemId);
|
|
3129
|
+
if (existing && existing !== actor) {
|
|
3130
|
+
throw new Error(`Actor with system ID '${systemId}' already exists.`);
|
|
3131
|
+
}
|
|
3132
|
+
keyedActors.set(systemId, actor);
|
|
3133
|
+
reverseKeyedActors.set(actor, systemId);
|
|
3134
|
+
},
|
|
3135
|
+
inspect: observerOrFn => {
|
|
3136
|
+
const observer = toObserver(observerOrFn);
|
|
3137
|
+
inspectionObservers.add(observer);
|
|
3138
|
+
return {
|
|
3139
|
+
unsubscribe() {
|
|
3140
|
+
inspectionObservers.delete(observer);
|
|
3141
|
+
}
|
|
3142
|
+
};
|
|
3143
|
+
},
|
|
3144
|
+
_sendInspectionEvent: sendInspectionEvent,
|
|
3145
|
+
_relay: (source, target, event) => {
|
|
3146
|
+
recordSent(source, target, event);
|
|
3147
|
+
deliver(source, target, event);
|
|
3148
|
+
},
|
|
3149
|
+
scheduler,
|
|
3150
|
+
getSnapshot: () => {
|
|
3151
|
+
return {
|
|
3152
|
+
_scheduledEvents: {
|
|
3153
|
+
...system._snapshot._scheduledEvents
|
|
3154
|
+
}
|
|
3155
|
+
};
|
|
3156
|
+
},
|
|
3157
|
+
start: () => {
|
|
3158
|
+
const scheduledEvents = system._snapshot._scheduledEvents;
|
|
3159
|
+
system._snapshot._scheduledEvents = {};
|
|
3160
|
+
for (const scheduledId in scheduledEvents) {
|
|
3161
|
+
const {
|
|
3162
|
+
source,
|
|
3163
|
+
target,
|
|
3164
|
+
event,
|
|
3165
|
+
delay,
|
|
3166
|
+
id
|
|
3167
|
+
} = scheduledEvents[scheduledId];
|
|
3168
|
+
scheduler.schedule(source, target, event, delay, id);
|
|
3169
|
+
}
|
|
3170
|
+
},
|
|
3171
|
+
_clock: clock,
|
|
3172
|
+
_logger: logger,
|
|
3173
|
+
_timerStrategy: options.timers
|
|
3174
|
+
};
|
|
3175
|
+
return system;
|
|
3176
|
+
}
|
|
3177
|
+
|
|
3178
|
+
const effectStates = new WeakMap();
|
|
3179
|
+
const XSTATE_LOGIC_EFFECT_RESOLVE = 'xstate.logic.effect.resolve';
|
|
3180
|
+
const XSTATE_LOGIC_EFFECT_REJECT = 'xstate.logic.effect.reject';
|
|
3181
|
+
const XSTATE_LOGIC_EFFECT_START = 'xstate.logic.effect.start';
|
|
3182
|
+
function getEffectState(self) {
|
|
3183
|
+
let state = effectStates.get(self);
|
|
3184
|
+
if (!state) {
|
|
3185
|
+
state = new Map();
|
|
3186
|
+
effectStates.set(self, state);
|
|
3187
|
+
}
|
|
3188
|
+
return state;
|
|
3189
|
+
}
|
|
3190
|
+
function executeLogicEffects(effects, actorScope) {
|
|
3191
|
+
if (!effects?.length) {
|
|
3192
|
+
return;
|
|
3193
|
+
}
|
|
3194
|
+
for (const effect of effects) {
|
|
3195
|
+
switch (effect.type) {
|
|
3196
|
+
case 'emit':
|
|
3197
|
+
actorScope.emit(effect.event);
|
|
3198
|
+
break;
|
|
3199
|
+
case 'sendBack':
|
|
3200
|
+
{
|
|
3201
|
+
const parent = actorScope.self._parent;
|
|
3202
|
+
if (parent) {
|
|
3203
|
+
actorScope.system._relay(actorScope.self, parent, effect.event);
|
|
3204
|
+
}
|
|
3205
|
+
break;
|
|
3206
|
+
}
|
|
3207
|
+
case 'raise':
|
|
3208
|
+
actorScope.system._relay(actorScope.self, actorScope.self, effect.event);
|
|
3209
|
+
break;
|
|
3210
|
+
case 'effect':
|
|
3211
|
+
{
|
|
3212
|
+
if (!effect.key) {
|
|
3213
|
+
const cleanup = effect.exec();
|
|
3214
|
+
if (typeof cleanup === 'function') {
|
|
3215
|
+
getEffectState(actorScope.self).set(Symbol(), {
|
|
3216
|
+
cleanup
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
break;
|
|
3220
|
+
}
|
|
3221
|
+
const state = getEffectState(actorScope.self);
|
|
3222
|
+
if (state.has(effect.key)) {
|
|
3223
|
+
break;
|
|
3224
|
+
}
|
|
3225
|
+
const cleanup = effect.exec();
|
|
3226
|
+
state.set(effect.key, {
|
|
3227
|
+
cleanup: typeof cleanup === 'function' ? cleanup : undefined
|
|
3228
|
+
});
|
|
3229
|
+
break;
|
|
3230
|
+
}
|
|
3231
|
+
case 'cleanupEffects':
|
|
3232
|
+
{
|
|
3233
|
+
const state = effectStates.get(actorScope.self);
|
|
3234
|
+
if (!state) {
|
|
3235
|
+
break;
|
|
3236
|
+
}
|
|
3237
|
+
for (const {
|
|
3238
|
+
cleanup
|
|
3239
|
+
} of state.values()) {
|
|
3240
|
+
cleanup?.();
|
|
3241
|
+
}
|
|
3242
|
+
effectStates.delete(actorScope.self);
|
|
3243
|
+
break;
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
function resolveContext(context, input) {
|
|
3249
|
+
return typeof context === 'function' ? context({
|
|
3250
|
+
input
|
|
3251
|
+
}) : context;
|
|
3252
|
+
}
|
|
3253
|
+
function createLogic(config) {
|
|
3254
|
+
const transition = (snapshot, event, actorScope) => {
|
|
3255
|
+
if (snapshot.status !== 'active') {
|
|
3256
|
+
return [snapshot, []];
|
|
3257
|
+
}
|
|
3258
|
+
if (event.type === XSTATE_STOP) {
|
|
3259
|
+
return [{
|
|
3260
|
+
...snapshot,
|
|
3261
|
+
status: 'stopped',
|
|
3262
|
+
input: undefined
|
|
3263
|
+
}, [{
|
|
3264
|
+
type: 'cleanupEffects'
|
|
3265
|
+
}]];
|
|
3266
|
+
}
|
|
3267
|
+
if (event.type === XSTATE_LOGIC_EFFECT_START) {
|
|
3268
|
+
return [{
|
|
3269
|
+
...snapshot,
|
|
3270
|
+
effects: {
|
|
3271
|
+
...snapshot.effects,
|
|
3272
|
+
[event.key]: {
|
|
3273
|
+
status: 'active'
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
}, []];
|
|
3277
|
+
}
|
|
3278
|
+
if (event.type === XSTATE_LOGIC_EFFECT_RESOLVE) {
|
|
3279
|
+
return [{
|
|
3280
|
+
...snapshot,
|
|
3281
|
+
effects: {
|
|
3282
|
+
...snapshot.effects,
|
|
3283
|
+
[event.key]: {
|
|
3284
|
+
status: 'done',
|
|
3285
|
+
output: event.output
|
|
3286
|
+
}
|
|
3287
|
+
}
|
|
3288
|
+
}, []];
|
|
3289
|
+
}
|
|
3290
|
+
if (event.type === XSTATE_LOGIC_EFFECT_REJECT) {
|
|
3291
|
+
return [{
|
|
3292
|
+
...snapshot,
|
|
3293
|
+
effects: {
|
|
3294
|
+
...snapshot.effects,
|
|
3295
|
+
[event.key]: {
|
|
3296
|
+
status: 'error',
|
|
3297
|
+
error: event.error
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
}, []];
|
|
3301
|
+
}
|
|
3302
|
+
const effects = [];
|
|
3303
|
+
const trackedEffects = {};
|
|
3304
|
+
const enqueueEffect = (key, exec) => {
|
|
3305
|
+
if (snapshot.effects?.[key]) {
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
effects.push({
|
|
3309
|
+
type: 'effect',
|
|
3310
|
+
key,
|
|
3311
|
+
exec
|
|
3312
|
+
});
|
|
3313
|
+
trackedEffects[key] = {
|
|
3314
|
+
status: 'active'
|
|
3315
|
+
};
|
|
3316
|
+
};
|
|
3317
|
+
const enq = {
|
|
3318
|
+
emit: emitted => {
|
|
3319
|
+
effects.push({
|
|
3320
|
+
type: 'emit',
|
|
3321
|
+
event: emitted
|
|
3322
|
+
});
|
|
3323
|
+
},
|
|
3324
|
+
sendBack: sentEvent => {
|
|
3325
|
+
effects.push({
|
|
3326
|
+
type: 'sendBack',
|
|
3327
|
+
event: sentEvent
|
|
3328
|
+
});
|
|
3329
|
+
},
|
|
3330
|
+
raise: raisedEvent => {
|
|
3331
|
+
effects.push({
|
|
3332
|
+
type: 'raise',
|
|
3333
|
+
event: raisedEvent
|
|
3334
|
+
});
|
|
3335
|
+
},
|
|
3336
|
+
effect: (keyOrExec, maybeExec) => {
|
|
3337
|
+
if (typeof keyOrExec === 'string') {
|
|
3338
|
+
enqueueEffect(keyOrExec, maybeExec);
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
effects.push({
|
|
3342
|
+
type: 'effect',
|
|
3343
|
+
exec: keyOrExec
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
};
|
|
3347
|
+
const patch = config.run({
|
|
3348
|
+
context: snapshot.context,
|
|
3349
|
+
event,
|
|
3350
|
+
input: snapshot.input,
|
|
3351
|
+
system: actorScope.system,
|
|
3352
|
+
self: actorScope.self,
|
|
3353
|
+
emit: actorScope.emit
|
|
3354
|
+
}, enq);
|
|
3355
|
+
const nextSnapshot = {
|
|
3356
|
+
...snapshot,
|
|
3357
|
+
...(patch || {})
|
|
3358
|
+
};
|
|
3359
|
+
if ('context' in snapshot || patch?.context !== undefined) {
|
|
3360
|
+
nextSnapshot.context = patch?.context ?? snapshot.context;
|
|
3361
|
+
}
|
|
3362
|
+
if (patch?.effects || Object.keys(trackedEffects).length) {
|
|
3363
|
+
nextSnapshot.effects = {
|
|
3364
|
+
...snapshot.effects,
|
|
3365
|
+
...patch?.effects,
|
|
3366
|
+
...trackedEffects
|
|
3367
|
+
};
|
|
3368
|
+
}
|
|
3369
|
+
return [nextSnapshot, effects];
|
|
3370
|
+
};
|
|
3371
|
+
const logic = {
|
|
3372
|
+
id: config.id,
|
|
3373
|
+
config,
|
|
3374
|
+
transition,
|
|
3375
|
+
start: (snapshot, actorScope) => {
|
|
3376
|
+
const [nextSnapshot, effects] = transition(snapshot, createInitEvent(snapshot.input), actorScope);
|
|
3377
|
+
Object.assign(snapshot, nextSnapshot);
|
|
3378
|
+
executeLogicEffects(effects, actorScope);
|
|
3379
|
+
},
|
|
3380
|
+
getInitialSnapshot: (_, input) => {
|
|
3381
|
+
const context = resolveContext(config.context, input);
|
|
3382
|
+
const snapshot = {
|
|
3383
|
+
status: 'active',
|
|
3384
|
+
output: undefined,
|
|
3385
|
+
error: undefined,
|
|
3386
|
+
input
|
|
3387
|
+
};
|
|
3388
|
+
if (context !== undefined) {
|
|
3389
|
+
snapshot.context = context;
|
|
3390
|
+
}
|
|
3391
|
+
return {
|
|
3392
|
+
...snapshot
|
|
3393
|
+
};
|
|
3394
|
+
},
|
|
3395
|
+
getPersistedSnapshot: snapshot => snapshot,
|
|
3396
|
+
restoreSnapshot: snapshot => snapshot
|
|
3397
|
+
};
|
|
3398
|
+
return logic;
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
// those are needed to make JSDoc `@link` work properly
|
|
3402
|
+
|
|
3403
|
+
let executingCustomAction = false;
|
|
3404
|
+
const $$ACTOR_TYPE = 1;
|
|
3405
|
+
|
|
3406
|
+
// those values are currently used by @xstate/react directly so it's important to keep the assigned values in sync
|
|
3407
|
+
let ProcessingStatus = /*#__PURE__*/function (ProcessingStatus) {
|
|
3408
|
+
ProcessingStatus[ProcessingStatus["NotStarted"] = 0] = "NotStarted";
|
|
3409
|
+
ProcessingStatus[ProcessingStatus["Running"] = 1] = "Running";
|
|
3410
|
+
ProcessingStatus[ProcessingStatus["Stopped"] = 2] = "Stopped";
|
|
3411
|
+
return ProcessingStatus;
|
|
3412
|
+
}({});
|
|
3413
|
+
const defaultOptions = {
|
|
3414
|
+
clock: {
|
|
3415
|
+
setTimeout: (fn, ms) => {
|
|
3416
|
+
return setTimeout(fn, ms);
|
|
3417
|
+
},
|
|
3418
|
+
clearTimeout: id => {
|
|
3419
|
+
return clearTimeout(id);
|
|
3420
|
+
}
|
|
3421
|
+
},
|
|
3422
|
+
logger: console.log.bind(console)
|
|
3423
|
+
};
|
|
3424
|
+
|
|
3425
|
+
/**
|
|
3426
|
+
* An Actor is a running process that can receive events, send events and change
|
|
3427
|
+
* its behavior based on the events it receives, which can cause effects outside
|
|
3428
|
+
* of the actor. When you run a state machine, it becomes an actor.
|
|
3429
|
+
*
|
|
3430
|
+
* An `Actor` is the concrete runtime instance with lifecycle methods and
|
|
3431
|
+
* system-owned internals. It also satisfies the narrower `ActorRef` contract,
|
|
3432
|
+
* so consumer APIs should accept `ActorRef` when they only need to send events
|
|
3433
|
+
* or read snapshots.
|
|
3434
|
+
*/
|
|
3435
|
+
class Actor {
|
|
3436
|
+
/**
|
|
3437
|
+
* Creates a new actor instance for the given logic with the provided options,
|
|
3438
|
+
* if any.
|
|
3439
|
+
*
|
|
3440
|
+
* @param logic The logic to create an actor from
|
|
3441
|
+
* @param options Actor options
|
|
3442
|
+
*/
|
|
3443
|
+
constructor(logic, options) {
|
|
3444
|
+
this.logic = logic;
|
|
3445
|
+
/** The current internal state of the actor. */
|
|
3446
|
+
this._snapshot = void 0;
|
|
3447
|
+
/**
|
|
3448
|
+
* The clock that is responsible for setting and clearing timeouts, such as
|
|
3449
|
+
* delayed events and transitions.
|
|
3450
|
+
*/
|
|
3451
|
+
this.clock = void 0;
|
|
3452
|
+
this.options = void 0;
|
|
3453
|
+
/** The unique identifier for this actor relative to its parent. */
|
|
3454
|
+
this.id = void 0;
|
|
3455
|
+
this._boundProcess = this._process.bind(this);
|
|
3456
|
+
this.mailbox = new Mailbox(this._boundProcess);
|
|
3457
|
+
this.observers = new Set();
|
|
3458
|
+
this.eventListeners = new Map();
|
|
3459
|
+
this.logger = void 0;
|
|
3460
|
+
/** @internal */
|
|
3461
|
+
this._processingStatus = ProcessingStatus.NotStarted;
|
|
3462
|
+
// Actor Ref
|
|
3463
|
+
this._parent = void 0;
|
|
3464
|
+
/** @internal */
|
|
3465
|
+
this._syncSnapshot = void 0;
|
|
3466
|
+
this.ref = void 0;
|
|
3467
|
+
// TODO: add typings for system
|
|
3468
|
+
this._actorScope = void 0;
|
|
3469
|
+
/** @internal */
|
|
3470
|
+
this._lastSourceRef = void 0;
|
|
3471
|
+
/** @internal */
|
|
3472
|
+
this._collectedMicrosteps = [];
|
|
3473
|
+
/** @internal Actions executed during the in-flight transition. */
|
|
3474
|
+
this._collectedActions = [];
|
|
3475
|
+
/** @internal Events relayed to other actors during the in-flight transition. */
|
|
3476
|
+
this._collectedSent = [];
|
|
3477
|
+
this.systemId = void 0;
|
|
3478
|
+
/** The globally unique process ID for this invocation. */
|
|
3479
|
+
this.sessionId = void 0;
|
|
3480
|
+
/** The system to which this actor belongs. */
|
|
3481
|
+
this.system = void 0;
|
|
3482
|
+
this.trigger = void 0;
|
|
3483
|
+
this.src = void 0;
|
|
3484
|
+
// array of functions to defer
|
|
3485
|
+
this._deferred = [];
|
|
3486
|
+
// pending effects (timers) from a persisted snapshot, rescheduled on `start()`
|
|
3487
|
+
this._pendingEffects = void 0;
|
|
3488
|
+
const resolvedOptions = {
|
|
3489
|
+
...defaultOptions,
|
|
3490
|
+
...options
|
|
3491
|
+
};
|
|
3492
|
+
const {
|
|
3493
|
+
clock,
|
|
3494
|
+
logger,
|
|
3495
|
+
parent,
|
|
3496
|
+
syncSnapshot,
|
|
3497
|
+
id,
|
|
3498
|
+
systemId,
|
|
3499
|
+
inspect
|
|
3500
|
+
} = resolvedOptions;
|
|
3501
|
+
this.system = parent ? parent.system : createSystem(this, {
|
|
3502
|
+
clock,
|
|
3503
|
+
logger,
|
|
3504
|
+
timers: resolvedOptions.timers
|
|
3505
|
+
});
|
|
3506
|
+
if (inspect && !parent) {
|
|
3507
|
+
// Always inspect at the system-level
|
|
3508
|
+
this.system.inspect(toObserver(inspect));
|
|
3509
|
+
}
|
|
3510
|
+
this.sessionId = this.system._bookId();
|
|
3511
|
+
this.id = id ?? this.sessionId;
|
|
3512
|
+
this.logger = options?.logger ?? this.system._logger;
|
|
3513
|
+
this.clock = options?.clock ?? this.system._clock;
|
|
3514
|
+
this._parent = parent;
|
|
3515
|
+
this._syncSnapshot = syncSnapshot;
|
|
3516
|
+
this.options = resolvedOptions;
|
|
3517
|
+
this.src = resolvedOptions.src ?? logic;
|
|
3518
|
+
this.ref = this;
|
|
3519
|
+
this._actorScope = {
|
|
3520
|
+
self: this,
|
|
3521
|
+
id: this.id,
|
|
3522
|
+
sessionId: this.sessionId,
|
|
3523
|
+
logger: this.logger,
|
|
3524
|
+
defer: fn => {
|
|
3525
|
+
this._deferred.push(fn);
|
|
3526
|
+
},
|
|
3527
|
+
system: this.system,
|
|
3528
|
+
stopChild: child => {
|
|
3529
|
+
if (child._parent !== this) {
|
|
3530
|
+
throw new Error(`Cannot stop child actor ${child.id} of ${this.id} because it is not a child`);
|
|
3531
|
+
}
|
|
3532
|
+
child._stop();
|
|
3533
|
+
},
|
|
3534
|
+
emit: emittedEvent => {
|
|
3535
|
+
const listeners = this.eventListeners.get(emittedEvent.type);
|
|
3536
|
+
const wildcardListener = this.eventListeners.get('*');
|
|
3537
|
+
if (!listeners && !wildcardListener) {
|
|
3538
|
+
return;
|
|
3539
|
+
}
|
|
3540
|
+
if (listeners) {
|
|
3541
|
+
for (const handler of listeners) {
|
|
3542
|
+
try {
|
|
3543
|
+
handler(emittedEvent);
|
|
3544
|
+
} catch (err) {
|
|
3545
|
+
reportUnhandledError(err);
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
if (!wildcardListener) {
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
for (const handler of wildcardListener) {
|
|
3553
|
+
try {
|
|
3554
|
+
handler(emittedEvent);
|
|
3555
|
+
} catch (err) {
|
|
3556
|
+
reportUnhandledError(err);
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
},
|
|
3560
|
+
actionExecutor: action => {
|
|
3561
|
+
const exec = () => {
|
|
3562
|
+
// Record every executed action for the '@xstate.transition' inspection
|
|
3563
|
+
// event's `actions[]` facet (replaces the v5 '@xstate.action' event).
|
|
3564
|
+
this._collectedActions.push({
|
|
3565
|
+
type: action.type,
|
|
3566
|
+
params: action.params
|
|
3567
|
+
});
|
|
3568
|
+
if (!action.exec) {
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
const saveExecutingCustomAction = executingCustomAction;
|
|
3572
|
+
try {
|
|
3573
|
+
executingCustomAction = true;
|
|
3574
|
+
action.exec();
|
|
3575
|
+
} finally {
|
|
3576
|
+
executingCustomAction = saveExecutingCustomAction;
|
|
3577
|
+
}
|
|
3578
|
+
};
|
|
3579
|
+
if (this._processingStatus === ProcessingStatus.Running) {
|
|
3580
|
+
exec();
|
|
3581
|
+
} else {
|
|
3582
|
+
this._deferred.push(exec);
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
};
|
|
3586
|
+
|
|
3587
|
+
// Ensure that the send method is bound to this Actor instance
|
|
3588
|
+
// if destructured
|
|
3589
|
+
this.send = this.send.bind(this);
|
|
3590
|
+
this.trigger = new Proxy({}, {
|
|
3591
|
+
get: (_, eventType) => {
|
|
3592
|
+
return payload => {
|
|
3593
|
+
this.send({
|
|
3594
|
+
...payload,
|
|
3595
|
+
type: eventType
|
|
3596
|
+
});
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
|
|
3601
|
+
// unified '@xstate.transition' event replaces '@xstate.actor'
|
|
3602
|
+
|
|
3603
|
+
if (systemId) {
|
|
3604
|
+
this.systemId = systemId;
|
|
3605
|
+
this.system._set(systemId, this);
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
// prepare to collect initial microsteps during getInitialSnapshot
|
|
3609
|
+
this._collectedMicrosteps = [];
|
|
3610
|
+
let persistedState = options?.snapshot ?? options?.state;
|
|
3611
|
+
if (persistedState && typeof persistedState === 'object' && '_pendingEffects' in persistedState) {
|
|
3612
|
+
// Pending effects are restored by this actor on `start()`; the logic
|
|
3613
|
+
// only sees the rest of the persisted snapshot.
|
|
3614
|
+
const {
|
|
3615
|
+
_pendingEffects,
|
|
3616
|
+
...rest
|
|
3617
|
+
} = persistedState;
|
|
3618
|
+
this._pendingEffects = _pendingEffects;
|
|
3619
|
+
persistedState = rest;
|
|
3620
|
+
}
|
|
3621
|
+
try {
|
|
3622
|
+
this._snapshot = persistedState ? this.logic.restoreSnapshot ? this.logic.restoreSnapshot(persistedState, this._actorScope) : persistedState : this.logic.getInitialSnapshot(this._actorScope, this.options?.input);
|
|
3623
|
+
} catch (err) {
|
|
3624
|
+
// if we get here then it means that we assign a value to this._snapshot that is not of the correct type
|
|
3625
|
+
// we can't get the true `TSnapshot & { status: 'error'; }`, it's impossible
|
|
3626
|
+
// so right now this is a lie of sorts
|
|
3627
|
+
this._snapshot = {
|
|
3628
|
+
status: 'error',
|
|
3629
|
+
output: undefined,
|
|
3630
|
+
error: err
|
|
3631
|
+
};
|
|
3632
|
+
// discard any functions deferred during the failed initial snapshot
|
|
3633
|
+
// computation so they can't run against an inconsistent actor
|
|
3634
|
+
this._deferred.length = 0;
|
|
3635
|
+
}
|
|
3636
|
+
if (systemId && this._snapshot.status !== 'active') {
|
|
3637
|
+
this.system._unregister(this);
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
// Announce actor topology: emitted once for every actor (root and every
|
|
3641
|
+
// spawned/invoked child) so the actor graph can be drawn before any
|
|
3642
|
+
// transitions occur. This is the only place actor identity is announced.
|
|
3643
|
+
this.system._sendInspectionEvent({
|
|
3644
|
+
type: '@xstate.actor',
|
|
3645
|
+
actorRef: this,
|
|
3646
|
+
parentRef: this._parent,
|
|
3647
|
+
id: this.id,
|
|
3648
|
+
src: this.src,
|
|
3649
|
+
snapshot: this._snapshot
|
|
3650
|
+
});
|
|
3651
|
+
}
|
|
3652
|
+
_setErrorSnapshot(err, snapshot = this._snapshot) {
|
|
3653
|
+
this._snapshot = {
|
|
3654
|
+
...snapshot,
|
|
3655
|
+
status: 'error',
|
|
3656
|
+
error: err
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
_next(snapshot) {
|
|
3660
|
+
for (const observer of this.observers) {
|
|
3661
|
+
try {
|
|
3662
|
+
observer.next?.(snapshot);
|
|
3663
|
+
} catch (err) {
|
|
3664
|
+
reportUnhandledError(err);
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
}
|
|
3668
|
+
update(snapshot, event) {
|
|
3669
|
+
// Update state
|
|
3670
|
+
this._snapshot = snapshot;
|
|
3671
|
+
|
|
3672
|
+
// Execute deferred effects
|
|
3673
|
+
for (let i = 0; i < this._deferred.length; i++) {
|
|
3674
|
+
const deferredFn = this._deferred[i];
|
|
3675
|
+
try {
|
|
3676
|
+
deferredFn();
|
|
3677
|
+
} catch (err) {
|
|
3678
|
+
// this error can only be caught when executing *initial* actions
|
|
3679
|
+
// it's the only time when we call actions provided by the user through those deferreds
|
|
3680
|
+
// when the actor is already running we always execute them synchronously while transitioning
|
|
3681
|
+
// no "builtin deferred" should actually throw an error since they are either safe
|
|
3682
|
+
// or the control flow is passed through the mailbox and errors should be caught by the `_process` used by the mailbox
|
|
3683
|
+
this._deferred.length = 0;
|
|
3684
|
+
this._setErrorSnapshot(err, snapshot);
|
|
3685
|
+
break;
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
this._deferred.length = 0;
|
|
3689
|
+
switch (this._snapshot.status) {
|
|
3690
|
+
case 'active':
|
|
3691
|
+
this._next(snapshot);
|
|
3692
|
+
break;
|
|
3693
|
+
case 'done':
|
|
3694
|
+
{
|
|
3695
|
+
// next observers are meant to be notified about done snapshots
|
|
3696
|
+
// this can be seen as something that is different from how observable work
|
|
3697
|
+
// but with observables `complete` callback is called without any arguments
|
|
3698
|
+
// it's more ergonomic for XState to treat a done snapshot as a "next" value
|
|
3699
|
+
// and the completion event as something that is separate,
|
|
3700
|
+
// something that merely follows emitting that done snapshot
|
|
3701
|
+
this._next(snapshot);
|
|
3702
|
+
this._stopProcedure();
|
|
3703
|
+
this._complete();
|
|
3704
|
+
const doneEvent = createDoneActorEvent(this.id, this._snapshot.output);
|
|
3705
|
+
if (this._parent) {
|
|
3706
|
+
this.system._relay(this, this._parent, doneEvent);
|
|
3707
|
+
}
|
|
3708
|
+
break;
|
|
3709
|
+
}
|
|
3710
|
+
case 'error':
|
|
3711
|
+
this._error(this._snapshot.error);
|
|
3712
|
+
break;
|
|
3713
|
+
}
|
|
3714
|
+
this.system._sendInspectionEvent({
|
|
3715
|
+
type: '@xstate.transition',
|
|
3716
|
+
actorRef: this,
|
|
3717
|
+
event,
|
|
3718
|
+
sourceRef: this._lastSourceRef,
|
|
3719
|
+
targetRef: this,
|
|
3720
|
+
snapshot,
|
|
3721
|
+
microsteps: this._collectedMicrosteps,
|
|
3722
|
+
actions: this._collectedActions,
|
|
3723
|
+
sent: this._collectedSent,
|
|
3724
|
+
eventType: event.type
|
|
3725
|
+
});
|
|
3726
|
+
// reset facets after emission
|
|
3727
|
+
this._collectedMicrosteps = [];
|
|
3728
|
+
this._collectedActions = [];
|
|
3729
|
+
this._collectedSent = [];
|
|
3730
|
+
}
|
|
3731
|
+
|
|
3732
|
+
/**
|
|
3733
|
+
* Subscribe an observer to an actor’s snapshot values.
|
|
3734
|
+
*
|
|
3735
|
+
* @remarks
|
|
3736
|
+
* The observer will receive the actor’s snapshot value when it is emitted.
|
|
3737
|
+
* The observer can be:
|
|
3738
|
+
*
|
|
3739
|
+
* - A plain function that receives the latest snapshot, or
|
|
3740
|
+
* - An observer object whose `.next(snapshot)` method receives the latest
|
|
3741
|
+
* snapshot
|
|
3742
|
+
*
|
|
3743
|
+
* @example
|
|
3744
|
+
*
|
|
3745
|
+
* ```ts
|
|
3746
|
+
* // Observer as a plain function
|
|
3747
|
+
* const subscription = actor.subscribe((snapshot) => {
|
|
3748
|
+
* console.log(snapshot);
|
|
3749
|
+
* });
|
|
3750
|
+
* ```
|
|
3751
|
+
*
|
|
3752
|
+
* @example
|
|
3753
|
+
*
|
|
3754
|
+
* ```ts
|
|
3755
|
+
* // Observer as an object
|
|
3756
|
+
* const subscription = actor.subscribe({
|
|
3757
|
+
* next(snapshot) {
|
|
3758
|
+
* console.log(snapshot);
|
|
3759
|
+
* },
|
|
3760
|
+
* error(err) {
|
|
3761
|
+
* // ...
|
|
3762
|
+
* },
|
|
3763
|
+
* complete() {
|
|
3764
|
+
* // ...
|
|
3765
|
+
* }
|
|
3766
|
+
* });
|
|
3767
|
+
* ```
|
|
3768
|
+
*
|
|
3769
|
+
* The return value of `actor.subscribe(observer)` is a subscription object
|
|
3770
|
+
* that has an `.unsubscribe()` method. You can call
|
|
3771
|
+
* `subscription.unsubscribe()` to unsubscribe the observer:
|
|
3772
|
+
*
|
|
3773
|
+
* @example
|
|
3774
|
+
*
|
|
3775
|
+
* ```ts
|
|
3776
|
+
* const subscription = actor.subscribe((snapshot) => {
|
|
3777
|
+
* // ...
|
|
3778
|
+
* });
|
|
3779
|
+
*
|
|
3780
|
+
* // Unsubscribe the observer
|
|
3781
|
+
* subscription.unsubscribe();
|
|
3782
|
+
* ```
|
|
3783
|
+
*
|
|
3784
|
+
* When the actor is stopped, all of its observers will automatically be
|
|
3785
|
+
* unsubscribed.
|
|
3786
|
+
*
|
|
3787
|
+
* @param observer - Either a plain function that receives the latest
|
|
3788
|
+
* snapshot, or an observer object whose `.next(snapshot)` method receives
|
|
3789
|
+
* the latest snapshot
|
|
3790
|
+
*/
|
|
3791
|
+
|
|
3792
|
+
subscribe(nextListenerOrObserver, errorListener, completeListener) {
|
|
3793
|
+
const observer = toObserver(nextListenerOrObserver, errorListener, completeListener);
|
|
3794
|
+
if (this._processingStatus !== ProcessingStatus.Stopped) {
|
|
3795
|
+
this.observers.add(observer);
|
|
3796
|
+
} else {
|
|
3797
|
+
switch (this._snapshot.status) {
|
|
3798
|
+
case 'done':
|
|
3799
|
+
try {
|
|
3800
|
+
observer.complete?.();
|
|
3801
|
+
} catch (err) {
|
|
3802
|
+
reportUnhandledError(err);
|
|
3803
|
+
}
|
|
3804
|
+
break;
|
|
3805
|
+
case 'error':
|
|
3806
|
+
{
|
|
3807
|
+
const err = this._snapshot.error;
|
|
3808
|
+
if (!observer.error) {
|
|
3809
|
+
reportUnhandledError(err);
|
|
3810
|
+
} else {
|
|
3811
|
+
try {
|
|
3812
|
+
observer.error(err);
|
|
3813
|
+
} catch (err) {
|
|
3814
|
+
reportUnhandledError(err);
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
break;
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
return {
|
|
3822
|
+
unsubscribe: () => {
|
|
3823
|
+
this.observers.delete(observer);
|
|
3824
|
+
}
|
|
3825
|
+
};
|
|
3826
|
+
}
|
|
3827
|
+
on(type, handler) {
|
|
3828
|
+
let listeners = this.eventListeners.get(type);
|
|
3829
|
+
if (!listeners) {
|
|
3830
|
+
listeners = new Set();
|
|
3831
|
+
this.eventListeners.set(type, listeners);
|
|
3832
|
+
}
|
|
3833
|
+
listeners.add(handler);
|
|
3834
|
+
return {
|
|
3835
|
+
unsubscribe: () => {
|
|
3836
|
+
listeners.delete(handler);
|
|
3837
|
+
}
|
|
3838
|
+
};
|
|
3839
|
+
}
|
|
3840
|
+
select(selector, equalityFn = Object.is) {
|
|
3841
|
+
return {
|
|
3842
|
+
subscribe: (observerOrFn, errorListener, completeListener) => {
|
|
3843
|
+
const observer = toObserver(observerOrFn, errorListener, completeListener);
|
|
3844
|
+
let selected = selector(this.getSnapshot());
|
|
3845
|
+
return this.subscribe({
|
|
3846
|
+
next: snapshot => {
|
|
3847
|
+
const next = selector(snapshot);
|
|
3848
|
+
if (!equalityFn(selected, next)) {
|
|
3849
|
+
selected = next;
|
|
3850
|
+
observer.next?.(next);
|
|
3851
|
+
}
|
|
3852
|
+
},
|
|
3853
|
+
error: observer.error,
|
|
3854
|
+
complete: observer.complete
|
|
3855
|
+
});
|
|
3856
|
+
},
|
|
3857
|
+
get: () => selector(this.get())
|
|
3858
|
+
};
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
/** Starts the Actor from the initial state */
|
|
3862
|
+
start() {
|
|
3863
|
+
if (this._processingStatus === ProcessingStatus.Running) {
|
|
3864
|
+
// Do not restart the service if it is already started
|
|
3865
|
+
return this;
|
|
3866
|
+
}
|
|
3867
|
+
if (this._syncSnapshot) {
|
|
3868
|
+
this.subscribe({
|
|
3869
|
+
next: snapshot => {
|
|
3870
|
+
if (snapshot.status === 'active') {
|
|
3871
|
+
this.system._relay(this, this._parent, {
|
|
3872
|
+
type: `xstate.snapshot.${this.id}`,
|
|
3873
|
+
snapshot
|
|
3874
|
+
});
|
|
3875
|
+
}
|
|
3876
|
+
},
|
|
3877
|
+
error: () => {}
|
|
3878
|
+
});
|
|
3879
|
+
}
|
|
3880
|
+
this.system._register(this.sessionId, this);
|
|
3881
|
+
if (this.systemId) {
|
|
3882
|
+
this.system._set(this.systemId, this);
|
|
3883
|
+
}
|
|
3884
|
+
this._processingStatus = ProcessingStatus.Running;
|
|
3885
|
+
|
|
3886
|
+
// TODO: this isn't correct when rehydrating
|
|
3887
|
+
const initEvent = createInitEvent(this.options.input);
|
|
3888
|
+
// remember source of init as parent for unified transition event
|
|
3889
|
+
this._lastSourceRef = this._parent;
|
|
3890
|
+
const status = this._snapshot.status;
|
|
3891
|
+
switch (status) {
|
|
3892
|
+
case 'done':
|
|
3893
|
+
// a state machine can be "done" upon initialization (it could reach a final state using initial microsteps)
|
|
3894
|
+
// we still need to complete observers, flush deferreds etc
|
|
3895
|
+
this.update(this._snapshot, initEvent);
|
|
3896
|
+
// TODO: rethink cleanup of observers, mailbox, etc
|
|
3897
|
+
return this;
|
|
3898
|
+
case 'error':
|
|
3899
|
+
this._error(this._snapshot.error);
|
|
3900
|
+
return this;
|
|
3901
|
+
}
|
|
3902
|
+
if (!this._parent) {
|
|
3903
|
+
this.system.start();
|
|
3904
|
+
}
|
|
3905
|
+
if (this.logic.start) {
|
|
3906
|
+
try {
|
|
3907
|
+
this.logic.start(this._snapshot, this._actorScope);
|
|
3908
|
+
} catch (err) {
|
|
3909
|
+
this._setErrorSnapshot(err);
|
|
3910
|
+
this._error(err);
|
|
3911
|
+
return this;
|
|
3912
|
+
}
|
|
3913
|
+
}
|
|
3914
|
+
|
|
3915
|
+
// TODO: this notifies all subscribers but usually this is redundant
|
|
3916
|
+
// there is no real change happening here
|
|
3917
|
+
// we need to rethink if this needs to be refactored
|
|
3918
|
+
this.update(this._snapshot, initEvent);
|
|
3919
|
+
if (this._pendingEffects) {
|
|
3920
|
+
const strategy = this.options.timers ?? this.system._timerStrategy ?? 'resume';
|
|
3921
|
+
for (const effect of this._pendingEffects) {
|
|
3922
|
+
// Re-execute the pending effect with its remaining delay. Only
|
|
3923
|
+
// self-targeted raises exist today; other effect types are ignored.
|
|
3924
|
+
if (effect.type === '@xstate.raise') {
|
|
3925
|
+
this.system.scheduler.schedule(this, this, effect.event, resolveRestoredTimerDelay(strategy, effect), effect.id);
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
this._pendingEffects = undefined;
|
|
3929
|
+
}
|
|
3930
|
+
this.mailbox.start();
|
|
3931
|
+
return this;
|
|
3932
|
+
}
|
|
3933
|
+
_process(event) {
|
|
3934
|
+
let nextState;
|
|
3935
|
+
let caughtError;
|
|
3936
|
+
try {
|
|
3937
|
+
nextState = this.logic.transition(this._snapshot, event, this._actorScope);
|
|
3938
|
+
} catch (err) {
|
|
3939
|
+
// we wrap it in a box so we can rethrow it later even if falsy value gets caught here
|
|
3940
|
+
caughtError = {
|
|
3941
|
+
err
|
|
3942
|
+
};
|
|
3943
|
+
}
|
|
3944
|
+
if (caughtError) {
|
|
3945
|
+
const {
|
|
3946
|
+
err
|
|
3947
|
+
} = caughtError;
|
|
3948
|
+
this._setErrorSnapshot(err);
|
|
3949
|
+
this._error(err);
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
3952
|
+
const [snapshot, effects] = Array.isArray(nextState) ? nextState : [nextState, undefined];
|
|
3953
|
+
this.update(snapshot, event);
|
|
3954
|
+
executeLogicEffects(effects, this._actorScope);
|
|
3955
|
+
if (event.type === XSTATE_STOP) {
|
|
3956
|
+
this._stopProcedure();
|
|
3957
|
+
this._complete();
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
|
|
3961
|
+
/** @internal */
|
|
3962
|
+
_stop() {
|
|
3963
|
+
if (this._processingStatus === ProcessingStatus.Stopped) {
|
|
3964
|
+
return this;
|
|
3965
|
+
}
|
|
3966
|
+
this.mailbox.clear();
|
|
3967
|
+
if (this._processingStatus === ProcessingStatus.NotStarted) {
|
|
3968
|
+
this._processingStatus = ProcessingStatus.Stopped;
|
|
3969
|
+
return this;
|
|
3970
|
+
}
|
|
3971
|
+
this.mailbox.enqueue({
|
|
3972
|
+
type: XSTATE_STOP
|
|
3973
|
+
});
|
|
3974
|
+
this.system._unregister(this);
|
|
3975
|
+
return this;
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
/** Stops the Actor and unsubscribe all listeners. */
|
|
3979
|
+
stop() {
|
|
3980
|
+
if (this._parent) {
|
|
3981
|
+
throw new Error('A non-root actor cannot be stopped directly.');
|
|
3982
|
+
}
|
|
3983
|
+
return this._stop();
|
|
3984
|
+
}
|
|
3985
|
+
_complete() {
|
|
3986
|
+
for (const observer of this.observers) {
|
|
3987
|
+
try {
|
|
3988
|
+
observer.complete?.();
|
|
3989
|
+
} catch (err) {
|
|
3990
|
+
reportUnhandledError(err);
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
this.observers.clear();
|
|
3994
|
+
this.eventListeners.clear();
|
|
3995
|
+
}
|
|
3996
|
+
_error(err) {
|
|
3997
|
+
this._stopProcedure();
|
|
3998
|
+
if (!this.observers.size) {
|
|
3999
|
+
if (!this._parent) {
|
|
4000
|
+
reportUnhandledError(err);
|
|
4001
|
+
}
|
|
4002
|
+
this.eventListeners.clear();
|
|
4003
|
+
} else {
|
|
4004
|
+
let reportError = false;
|
|
4005
|
+
for (const observer of this.observers) {
|
|
4006
|
+
const errorListener = observer.error;
|
|
4007
|
+
reportError ||= !errorListener;
|
|
4008
|
+
try {
|
|
4009
|
+
errorListener?.(err);
|
|
4010
|
+
} catch (err2) {
|
|
4011
|
+
reportUnhandledError(err2);
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
this.observers.clear();
|
|
4015
|
+
this.eventListeners.clear();
|
|
4016
|
+
if (reportError) {
|
|
4017
|
+
reportUnhandledError(err);
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
if (this._parent) {
|
|
4021
|
+
this.system._relay(this, this._parent, createErrorActorEvent(this.id, err));
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
// TODO: atm children don't belong entirely to the actor so
|
|
4025
|
+
// in a way - it's not even super aware of them
|
|
4026
|
+
// so we can't stop them from here but we really should!
|
|
4027
|
+
// right now, they are being stopped within the machine's transition
|
|
4028
|
+
// but that could throw and leave us with "orphaned" active actors
|
|
4029
|
+
_stopProcedure() {
|
|
4030
|
+
if (this._processingStatus !== ProcessingStatus.Running) {
|
|
4031
|
+
// Actor already stopped; do nothing
|
|
4032
|
+
return;
|
|
4033
|
+
}
|
|
4034
|
+
|
|
4035
|
+
// Cancel all delayed events
|
|
4036
|
+
this.system.scheduler.cancelAll(this);
|
|
4037
|
+
|
|
4038
|
+
// TODO: mailbox.reset
|
|
4039
|
+
this.mailbox.clear();
|
|
4040
|
+
// TODO: after `stop` we must prepare ourselves for receiving events again
|
|
4041
|
+
// events sent *after* stop signal must be queued
|
|
4042
|
+
// it seems like this should be the common behavior for all of our consumers
|
|
4043
|
+
// so perhaps this should be unified somehow for all of them
|
|
4044
|
+
this.mailbox = new Mailbox(this._boundProcess);
|
|
4045
|
+
this._processingStatus = ProcessingStatus.Stopped;
|
|
4046
|
+
this.system._unregister(this);
|
|
4047
|
+
}
|
|
4048
|
+
|
|
4049
|
+
/** @internal */
|
|
4050
|
+
_send(event) {
|
|
4051
|
+
if (this._processingStatus === ProcessingStatus.Stopped) {
|
|
4052
|
+
// do nothing
|
|
4053
|
+
{
|
|
4054
|
+
// TODO: circular serialization issues
|
|
4055
|
+
// const eventString = ''; //JSON.stringify(event);
|
|
4056
|
+
|
|
4057
|
+
console.warn(`Event "${event.type}" was sent to stopped actor "${this.id} (${this.sessionId})". This actor has already reached its final state, and will not transition.`);
|
|
4058
|
+
}
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
this.mailbox.enqueue(event);
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
/**
|
|
4065
|
+
* Sends an event to the running Actor to trigger a transition.
|
|
4066
|
+
*
|
|
4067
|
+
* @param event The event to send
|
|
4068
|
+
*/
|
|
4069
|
+
send(event) {
|
|
4070
|
+
if (typeof event === 'string') {
|
|
4071
|
+
throw new Error(`Only event objects may be sent to actors; use .send({ type: "${event}" }) instead`);
|
|
4072
|
+
}
|
|
4073
|
+
this.system._relay(undefined, this, event);
|
|
4074
|
+
}
|
|
4075
|
+
toJSON() {
|
|
4076
|
+
return {
|
|
4077
|
+
xstate$$type: $$ACTOR_TYPE,
|
|
4078
|
+
id: this.id
|
|
4079
|
+
};
|
|
4080
|
+
}
|
|
4081
|
+
|
|
4082
|
+
/**
|
|
4083
|
+
* Obtain the internal state of the actor, which can be persisted.
|
|
4084
|
+
*
|
|
4085
|
+
* @remarks
|
|
4086
|
+
* The internal state can be persisted from any actor, not only machines.
|
|
4087
|
+
*
|
|
4088
|
+
* Note that the persisted state is not the same as the snapshot from
|
|
4089
|
+
* {@link Actor.getSnapshot}. Persisted state represents the internal state of
|
|
4090
|
+
* the actor, while snapshots represent the actor's last emitted value.
|
|
4091
|
+
*
|
|
4092
|
+
* Can be restored with {@link ActorOptions.state}
|
|
4093
|
+
* @see https://stately.ai/docs/persistence
|
|
4094
|
+
*/
|
|
4095
|
+
|
|
4096
|
+
getPersistedSnapshot(options) {
|
|
4097
|
+
const persisted = this.logic.getPersistedSnapshot(this._snapshot, options);
|
|
4098
|
+
|
|
4099
|
+
// Capture this actor's pending self-targeted effects (delayed
|
|
4100
|
+
// events/transitions) as serialized action descriptors plus their
|
|
4101
|
+
// runtime progress, so they can be restored on `start()`.
|
|
4102
|
+
const scheduledEvents = this.system._snapshot._scheduledEvents;
|
|
4103
|
+
let pendingEffects;
|
|
4104
|
+
const now = Date.now();
|
|
4105
|
+
for (const key in scheduledEvents) {
|
|
4106
|
+
const scheduled = scheduledEvents[key];
|
|
4107
|
+
if (scheduled.source === this && scheduled.target === this) {
|
|
4108
|
+
(pendingEffects ??= []).push({
|
|
4109
|
+
type: '@xstate.raise',
|
|
4110
|
+
event: scheduled.event,
|
|
4111
|
+
id: scheduled.id,
|
|
4112
|
+
delay: scheduled.delay,
|
|
4113
|
+
startedAt: scheduled.startedAt,
|
|
4114
|
+
elapsed: Math.max(0, now - scheduled.startedAt)
|
|
4115
|
+
});
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
// If this actor was restored but not yet started, its pending effects
|
|
4120
|
+
// are not in the scheduler yet — carry them through as-is.
|
|
4121
|
+
pendingEffects ??= this._pendingEffects;
|
|
4122
|
+
return pendingEffects ? {
|
|
4123
|
+
...persisted,
|
|
4124
|
+
_pendingEffects: pendingEffects
|
|
4125
|
+
} : persisted;
|
|
4126
|
+
}
|
|
4127
|
+
[symbolObservable]() {
|
|
4128
|
+
return this;
|
|
4129
|
+
}
|
|
4130
|
+
|
|
4131
|
+
/**
|
|
4132
|
+
* Read an actor’s snapshot synchronously.
|
|
4133
|
+
*
|
|
4134
|
+
* @remarks
|
|
4135
|
+
* The snapshot represent an actor's last emitted value.
|
|
4136
|
+
*
|
|
4137
|
+
* When an actor receives an event, its internal state may change. An actor
|
|
4138
|
+
* may emit a snapshot when a state transition occurs.
|
|
4139
|
+
*
|
|
4140
|
+
* Note that some actors, such as callback actors generated with
|
|
4141
|
+
* `createCallbackLogic`, will not emit snapshots.
|
|
4142
|
+
* @see {@link Actor.subscribe} to subscribe to an actor’s snapshot values.
|
|
4143
|
+
* @see {@link Actor.getPersistedSnapshot} to persist the internal state of an actor (which is more than just a snapshot).
|
|
4144
|
+
*/
|
|
4145
|
+
getSnapshot() {
|
|
4146
|
+
return this.get();
|
|
4147
|
+
}
|
|
4148
|
+
|
|
4149
|
+
/** Read the actor's current snapshot as an atom value. */
|
|
4150
|
+
get() {
|
|
4151
|
+
if (!this._snapshot) {
|
|
4152
|
+
throw new Error(`Snapshot can't be read while the actor initializes itself`);
|
|
4153
|
+
}
|
|
4154
|
+
onActorRead?.(this);
|
|
4155
|
+
return this._snapshot;
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
function resolveRestoredTimerDelay(strategy, effect) {
|
|
4159
|
+
if (typeof strategy === 'function') {
|
|
4160
|
+
return strategy(effect);
|
|
4161
|
+
}
|
|
4162
|
+
switch (strategy) {
|
|
4163
|
+
case 'restart':
|
|
4164
|
+
return effect.delay;
|
|
4165
|
+
case 'absolute':
|
|
4166
|
+
return Math.max(0, effect.startedAt + effect.delay - Date.now());
|
|
4167
|
+
case 'resume':
|
|
4168
|
+
default:
|
|
4169
|
+
return Math.max(0, effect.delay - effect.elapsed);
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
/**
|
|
4173
|
+
* Creates a new actor instance for the given actor logic with the provided
|
|
4174
|
+
* options, if any.
|
|
4175
|
+
*
|
|
4176
|
+
* @remarks
|
|
4177
|
+
* When you create an actor from actor logic via `createActor(logic)`, you
|
|
4178
|
+
* implicitly create an actor system where the created actor is the root actor.
|
|
4179
|
+
* Any actors spawned from this root actor and its descendants are part of that
|
|
4180
|
+
* actor system.
|
|
4181
|
+
* @example
|
|
4182
|
+
*
|
|
4183
|
+
* ```ts
|
|
4184
|
+
* import { createActor } from 'xstate';
|
|
4185
|
+
* import { someActorLogic } from './someActorLogic.ts';
|
|
4186
|
+
*
|
|
4187
|
+
* // Creating the actor, which implicitly creates an actor system with itself as the root actor
|
|
4188
|
+
* const actor = createActor(someActorLogic);
|
|
4189
|
+
*
|
|
4190
|
+
* actor.subscribe((snapshot) => {
|
|
4191
|
+
* console.log(snapshot);
|
|
4192
|
+
* });
|
|
4193
|
+
*
|
|
4194
|
+
* // Actors must be started by calling `actor.start()`, which will also start the actor system.
|
|
4195
|
+
* actor.start();
|
|
4196
|
+
*
|
|
4197
|
+
* // Actors can receive events
|
|
4198
|
+
* actor.send({ type: 'someEvent' });
|
|
4199
|
+
*
|
|
4200
|
+
* // You can stop root actors by calling `actor.stop()`, which will also stop the actor system and all actors in that system.
|
|
4201
|
+
* actor.stop();
|
|
4202
|
+
* ```
|
|
4203
|
+
*
|
|
4204
|
+
* @param logic - The actor logic to create an actor from. For a state machine
|
|
4205
|
+
* actor logic creator, see {@link createMachine}. Other actor logic creators
|
|
4206
|
+
* include {@link createCallbackLogic}, {@link createEventObservableLogic},
|
|
4207
|
+
* {@link createObservableLogic}, {@link createLogic}, and
|
|
4208
|
+
* {@link createAsyncLogic}.
|
|
4209
|
+
* @param options - Actor options
|
|
4210
|
+
*/
|
|
4211
|
+
function createActor(logic, options) {
|
|
4212
|
+
return new Actor(logic, options);
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
const instanceStates = /* #__PURE__ */new WeakMap();
|
|
4216
|
+
|
|
4217
|
+
/**
|
|
4218
|
+
* Represents an actor created by `createCallbackLogic`.
|
|
4219
|
+
*
|
|
4220
|
+
* The type of `self` within the actor's logic.
|
|
4221
|
+
*
|
|
4222
|
+
* @example
|
|
4223
|
+
*
|
|
4224
|
+
* ```ts
|
|
4225
|
+
* import { createCallbackLogic, createActor } from 'xstate';
|
|
4226
|
+
*
|
|
4227
|
+
* // The events the actor receives.
|
|
4228
|
+
* type Event = { type: 'someEvent' };
|
|
4229
|
+
* // The actor's input.
|
|
4230
|
+
* type Input = { name: string };
|
|
4231
|
+
*
|
|
4232
|
+
* // Actor logic that logs whenever it receives an event of type `someEvent`.
|
|
4233
|
+
* const logic = createCallbackLogic<Event, Input>(
|
|
4234
|
+
* ({ self, input, receive }) => {
|
|
4235
|
+
* self;
|
|
4236
|
+
* // ^? CallbackActorRef<Event, Input>
|
|
4237
|
+
*
|
|
4238
|
+
* receive((event) => {
|
|
4239
|
+
* if (event.type === 'someEvent') {
|
|
4240
|
+
* console.log(`${input.name}: received "someEvent" event`);
|
|
4241
|
+
* // logs 'myActor: received "someEvent" event'
|
|
4242
|
+
* }
|
|
4243
|
+
* });
|
|
4244
|
+
* }
|
|
4245
|
+
* );
|
|
4246
|
+
*
|
|
4247
|
+
* const actor = createActor(logic, { input: { name: 'myActor' } });
|
|
4248
|
+
* // ^? CallbackActorRef<Event, Input>
|
|
4249
|
+
* ```
|
|
4250
|
+
*
|
|
4251
|
+
* @see {@link createCallbackLogic}
|
|
4252
|
+
*/
|
|
4253
|
+
|
|
4254
|
+
/**
|
|
4255
|
+
* An actor logic creator which returns callback logic as defined by a callback
|
|
4256
|
+
* function.
|
|
4257
|
+
*
|
|
4258
|
+
* @remarks
|
|
4259
|
+
* Useful for subscription-based or other free-form logic that can send events
|
|
4260
|
+
* back to the parent actor.
|
|
4261
|
+
*
|
|
4262
|
+
* Actors created from callback logic (“callback actors”) can:
|
|
4263
|
+
*
|
|
4264
|
+
* - Receive events via the `receive` function
|
|
4265
|
+
* - Send events to the parent actor via the `sendBack` function
|
|
4266
|
+
*
|
|
4267
|
+
* Callback actors are a bit different from other actors in that they:
|
|
4268
|
+
*
|
|
4269
|
+
* - Do not work with `onDone`
|
|
4270
|
+
* - Do not produce a snapshot using `.getSnapshot()`
|
|
4271
|
+
* - Do not emit values when used with `.subscribe()`
|
|
4272
|
+
* - Can not be stopped with `.stop()`
|
|
4273
|
+
*
|
|
4274
|
+
* @example
|
|
4275
|
+
*
|
|
4276
|
+
* ```typescript
|
|
4277
|
+
* const callbackLogic = createCallbackLogic(({ sendBack, receive }) => {
|
|
4278
|
+
* let lockStatus = 'unlocked';
|
|
4279
|
+
*
|
|
4280
|
+
* const handler = (event) => {
|
|
4281
|
+
* if (lockStatus === 'locked') {
|
|
4282
|
+
* return;
|
|
4283
|
+
* }
|
|
4284
|
+
* sendBack(event);
|
|
4285
|
+
* };
|
|
4286
|
+
*
|
|
4287
|
+
* receive((event) => {
|
|
4288
|
+
* if (event.type === 'lock') {
|
|
4289
|
+
* lockStatus = 'locked';
|
|
4290
|
+
* } else if (event.type === 'unlock') {
|
|
4291
|
+
* lockStatus = 'unlocked';
|
|
4292
|
+
* }
|
|
4293
|
+
* });
|
|
4294
|
+
*
|
|
4295
|
+
* document.body.addEventListener('click', handler);
|
|
4296
|
+
*
|
|
4297
|
+
* return () => {
|
|
4298
|
+
* document.body.removeEventListener('click', handler);
|
|
4299
|
+
* };
|
|
4300
|
+
* });
|
|
4301
|
+
* ```
|
|
4302
|
+
*
|
|
4303
|
+
* @param callback - The callback function used to describe the callback logic
|
|
4304
|
+
* The callback function is passed an object with the following properties:
|
|
4305
|
+
*
|
|
4306
|
+
* - `receive` - A function that can send events back to the parent actor; the
|
|
4307
|
+
* listener is then called whenever events are received by the callback
|
|
4308
|
+
* actor
|
|
4309
|
+
* - `sendBack` - A function that can send events back to the parent actor
|
|
4310
|
+
* - `input` - Data that was provided to the callback actor
|
|
4311
|
+
* - `self` - The parent actor of the callback actor
|
|
4312
|
+
* - `system` - The actor system to which the callback actor belongs The callback
|
|
4313
|
+
* function can (optionally) return a cleanup function, which is called
|
|
4314
|
+
* when the actor is stopped.
|
|
4315
|
+
*
|
|
4316
|
+
* @returns Callback logic
|
|
4317
|
+
* @see {@link CallbackLogicFunction} for more information about the callback function and its object argument
|
|
4318
|
+
* @see {@link https://stately.ai/docs/input | Input docs} for more information about how input is passed
|
|
4319
|
+
*/
|
|
4320
|
+
|
|
4321
|
+
function createCallbackLogic(callbackOrConfig) {
|
|
4322
|
+
const callback = typeof callbackOrConfig === 'function' ? callbackOrConfig : callbackOrConfig.run;
|
|
4323
|
+
const schemas = typeof callbackOrConfig === 'function' ? undefined : callbackOrConfig.schemas;
|
|
4324
|
+
return createLogic({
|
|
4325
|
+
schemas,
|
|
4326
|
+
context: undefined,
|
|
4327
|
+
run: (args, enq) => {
|
|
4328
|
+
const {
|
|
4329
|
+
event,
|
|
4330
|
+
input,
|
|
4331
|
+
self,
|
|
4332
|
+
system
|
|
4333
|
+
} = args;
|
|
4334
|
+
const emit = args.emit;
|
|
4335
|
+
const callbackState = instanceStates.get(self);
|
|
4336
|
+
callbackState?.receivers?.forEach(receiver => receiver(event));
|
|
4337
|
+
enq.effect('callback', () => {
|
|
4338
|
+
const callbackState = {
|
|
4339
|
+
receivers: undefined,
|
|
4340
|
+
dispose: undefined
|
|
4341
|
+
};
|
|
4342
|
+
instanceStates.set(self, callbackState);
|
|
4343
|
+
callbackState.dispose = callback({
|
|
4344
|
+
input,
|
|
4345
|
+
system,
|
|
4346
|
+
self: self,
|
|
4347
|
+
sendBack: event => {
|
|
4348
|
+
if (self.getSnapshot().status === 'stopped') {
|
|
4349
|
+
return;
|
|
4350
|
+
}
|
|
4351
|
+
const parent = self._parent;
|
|
4352
|
+
if (parent) {
|
|
4353
|
+
system._relay(self, parent, event);
|
|
4354
|
+
}
|
|
4355
|
+
},
|
|
4356
|
+
receive: listener => {
|
|
4357
|
+
callbackState.receivers ??= new Set();
|
|
4358
|
+
callbackState.receivers.add(listener);
|
|
4359
|
+
},
|
|
4360
|
+
emit: emit
|
|
4361
|
+
});
|
|
4362
|
+
return () => {
|
|
4363
|
+
instanceStates.delete(self);
|
|
4364
|
+
callbackState.receivers?.clear();
|
|
4365
|
+
callbackState.dispose?.();
|
|
4366
|
+
};
|
|
4367
|
+
});
|
|
4368
|
+
}
|
|
4369
|
+
});
|
|
4370
|
+
}
|
|
4371
|
+
|
|
4372
|
+
const XSTATE_OBSERVABLE_NEXT = 'xstate.observable.next';
|
|
4373
|
+
const XSTATE_OBSERVABLE_ERROR = 'xstate.observable.error';
|
|
4374
|
+
const XSTATE_OBSERVABLE_COMPLETE = 'xstate.observable.complete';
|
|
4375
|
+
|
|
4376
|
+
/**
|
|
4377
|
+
* Represents an actor created by `createObservableLogic` or
|
|
4378
|
+
* `createEventObservableLogic`.
|
|
4379
|
+
*
|
|
4380
|
+
* The type of `self` within the actor's logic.
|
|
4381
|
+
*
|
|
4382
|
+
* @example
|
|
4383
|
+
*
|
|
4384
|
+
* ```ts
|
|
4385
|
+
* import { createObservableLogic, createActor } from 'xstate';
|
|
4386
|
+
* import { interval } from 'rxjs';
|
|
4387
|
+
*
|
|
4388
|
+
* // The type of the value observed by the actor's logic.
|
|
4389
|
+
* type Context = number;
|
|
4390
|
+
* // The actor's input.
|
|
4391
|
+
* type Input = { period?: number };
|
|
4392
|
+
*
|
|
4393
|
+
* // Actor logic that observes a number incremented every `input.period`
|
|
4394
|
+
* // milliseconds (default: 1_000).
|
|
4395
|
+
* const logic = createObservableLogic<Context, Input>(
|
|
4396
|
+
* ({ input, self }) => {
|
|
4397
|
+
* self;
|
|
4398
|
+
* // ^? ObservableActor<Event, Input>
|
|
4399
|
+
*
|
|
4400
|
+
* return interval(input.period ?? 1_000);
|
|
4401
|
+
* }
|
|
4402
|
+
* );
|
|
4403
|
+
*
|
|
4404
|
+
* const actor = createActor(logic, { input: { period: 2_000 } });
|
|
4405
|
+
* // ^? ObservableActor<Event, Input>
|
|
4406
|
+
* ```
|
|
4407
|
+
*
|
|
4408
|
+
* @see {@link createObservableLogic}
|
|
4409
|
+
* @see {@link createEventObservableLogic}
|
|
4410
|
+
*/
|
|
4411
|
+
|
|
4412
|
+
/**
|
|
4413
|
+
* Observable actor logic is described by an observable stream of values. Actors
|
|
4414
|
+
* created from observable logic (“observable actors”) can:
|
|
4415
|
+
*
|
|
4416
|
+
* - Emit snapshots of the observable’s emitted value
|
|
4417
|
+
*
|
|
4418
|
+
* The observable’s emitted value is used as its observable actor’s `context`.
|
|
4419
|
+
*
|
|
4420
|
+
* Sending events to observable actors will have no effect.
|
|
4421
|
+
*
|
|
4422
|
+
* @example
|
|
4423
|
+
*
|
|
4424
|
+
* ```ts
|
|
4425
|
+
* import { createObservableLogic, createActor } from 'xstate';
|
|
4426
|
+
* import { interval } from 'rxjs';
|
|
4427
|
+
*
|
|
4428
|
+
* const logic = createObservableLogic((obj) => interval(1000));
|
|
4429
|
+
*
|
|
4430
|
+
* const actor = createActor(logic);
|
|
4431
|
+
*
|
|
4432
|
+
* actor.subscribe((snapshot) => {
|
|
4433
|
+
* console.log(snapshot.context);
|
|
4434
|
+
* });
|
|
4435
|
+
*
|
|
4436
|
+
* actor.start();
|
|
4437
|
+
* // At every second:
|
|
4438
|
+
* // Logs 0
|
|
4439
|
+
* // Logs 1
|
|
4440
|
+
* // Logs 2
|
|
4441
|
+
* // ...
|
|
4442
|
+
* ```
|
|
4443
|
+
*
|
|
4444
|
+
* @param observableCreator A function that creates an observable. It receives
|
|
4445
|
+
* one argument, an object with the following properties:
|
|
4446
|
+
*
|
|
4447
|
+
* - `input` - Data that was provided to the observable actor
|
|
4448
|
+
* - `self` - The parent actor
|
|
4449
|
+
* - `system` - The actor system to which the observable actor belongs
|
|
4450
|
+
*
|
|
4451
|
+
* It should return a {@link Subscribable}, which is compatible with an RxJS
|
|
4452
|
+
* Observable, although RxJS is not required to create them.
|
|
4453
|
+
* @see {@link https://rxjs.dev} for documentation on RxJS Observable and observable creators.
|
|
4454
|
+
* @see {@link Subscribable} interface in XState, which is based on and compatible with RxJS Observable.
|
|
4455
|
+
*/
|
|
4456
|
+
|
|
4457
|
+
function createObservableLogic(observableCreatorOrConfig) {
|
|
4458
|
+
const observableCreator = typeof observableCreatorOrConfig === 'function' ? observableCreatorOrConfig : observableCreatorOrConfig.run;
|
|
4459
|
+
const schemas = typeof observableCreatorOrConfig === 'function' ? undefined : observableCreatorOrConfig.schemas;
|
|
4460
|
+
return createLogic({
|
|
4461
|
+
schemas,
|
|
4462
|
+
context: undefined,
|
|
4463
|
+
run: (args, enq) => {
|
|
4464
|
+
const {
|
|
4465
|
+
event,
|
|
4466
|
+
input,
|
|
4467
|
+
self,
|
|
4468
|
+
system
|
|
4469
|
+
} = args;
|
|
4470
|
+
const emit = args.emit;
|
|
4471
|
+
switch (event.type) {
|
|
4472
|
+
case XSTATE_OBSERVABLE_NEXT:
|
|
4473
|
+
{
|
|
4474
|
+
return {
|
|
4475
|
+
context: event.data
|
|
4476
|
+
};
|
|
4477
|
+
}
|
|
4478
|
+
case XSTATE_OBSERVABLE_ERROR:
|
|
4479
|
+
return {
|
|
4480
|
+
status: 'error',
|
|
4481
|
+
error: event.data,
|
|
4482
|
+
input: undefined,
|
|
4483
|
+
effects: {
|
|
4484
|
+
observable: {
|
|
4485
|
+
status: 'error',
|
|
4486
|
+
error: event.data
|
|
4487
|
+
}
|
|
4488
|
+
}
|
|
4489
|
+
};
|
|
4490
|
+
case XSTATE_OBSERVABLE_COMPLETE:
|
|
4491
|
+
return {
|
|
4492
|
+
status: 'done',
|
|
4493
|
+
input: undefined,
|
|
4494
|
+
effects: {
|
|
4495
|
+
observable: {
|
|
4496
|
+
status: 'done'
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
};
|
|
4500
|
+
}
|
|
4501
|
+
enq.effect('observable', () => {
|
|
4502
|
+
const actorSelf = self;
|
|
4503
|
+
const subscription = observableCreator({
|
|
4504
|
+
input,
|
|
4505
|
+
system,
|
|
4506
|
+
self: self,
|
|
4507
|
+
emit: emit
|
|
4508
|
+
}).subscribe({
|
|
4509
|
+
next: value => {
|
|
4510
|
+
system._relay(actorSelf, actorSelf, {
|
|
4511
|
+
type: XSTATE_OBSERVABLE_NEXT,
|
|
4512
|
+
data: value
|
|
4513
|
+
});
|
|
4514
|
+
},
|
|
4515
|
+
error: err => {
|
|
4516
|
+
system._relay(actorSelf, actorSelf, {
|
|
4517
|
+
type: XSTATE_OBSERVABLE_ERROR,
|
|
4518
|
+
data: err
|
|
4519
|
+
});
|
|
4520
|
+
},
|
|
4521
|
+
complete: () => {
|
|
4522
|
+
system._relay(actorSelf, actorSelf, {
|
|
4523
|
+
type: XSTATE_OBSERVABLE_COMPLETE
|
|
4524
|
+
});
|
|
4525
|
+
}
|
|
4526
|
+
});
|
|
4527
|
+
return () => subscription.unsubscribe();
|
|
4528
|
+
});
|
|
4529
|
+
}
|
|
4530
|
+
});
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
/**
|
|
4534
|
+
* Creates event observable logic that listens to an observable that delivers
|
|
4535
|
+
* event objects.
|
|
4536
|
+
*
|
|
4537
|
+
* Event observable actor logic is described by an observable stream of
|
|
4538
|
+
* {@link https://stately.ai/docs/transitions#event-objects | event objects}.
|
|
4539
|
+
* Actors created from event observable logic (“event observable actors”) can:
|
|
4540
|
+
*
|
|
4541
|
+
* - Implicitly send events to its parent actor
|
|
4542
|
+
* - Emit snapshots of its emitted event objects
|
|
4543
|
+
*
|
|
4544
|
+
* Sending events to event observable actors will have no effect.
|
|
4545
|
+
*
|
|
4546
|
+
* @example
|
|
4547
|
+
*
|
|
4548
|
+
* ```ts
|
|
4549
|
+
* import {
|
|
4550
|
+
* createEventObservableLogic,
|
|
4551
|
+
* Subscribable,
|
|
4552
|
+
* EventObject,
|
|
4553
|
+
* createMachine,
|
|
4554
|
+
* createActor
|
|
4555
|
+
* } from 'xstate';
|
|
4556
|
+
* import { fromEvent } from 'rxjs';
|
|
4557
|
+
*
|
|
4558
|
+
* const mouseClickLogic = createEventObservableLogic(
|
|
4559
|
+
* () => fromEvent(document.body, 'click') as Subscribable<EventObject>
|
|
4560
|
+
* );
|
|
4561
|
+
*
|
|
4562
|
+
* const canvasMachine = createMachine({
|
|
4563
|
+
* invoke: {
|
|
4564
|
+
* // Will send mouse `click` events to the canvas actor
|
|
4565
|
+
* src: mouseClickLogic
|
|
4566
|
+
* }
|
|
4567
|
+
* });
|
|
4568
|
+
*
|
|
4569
|
+
* const canvasActor = createActor(canvasMachine);
|
|
4570
|
+
* canvasActor.start();
|
|
4571
|
+
* ```
|
|
4572
|
+
*
|
|
4573
|
+
* @param lazyObservable A function that creates an observable that delivers
|
|
4574
|
+
* event objects. It receives one argument, an object with the following
|
|
4575
|
+
* properties:
|
|
4576
|
+
*
|
|
4577
|
+
* - `input` - Data that was provided to the event observable actor
|
|
4578
|
+
* - `self` - The parent actor
|
|
4579
|
+
* - `system` - The actor system to which the event observable actor belongs.
|
|
4580
|
+
*
|
|
4581
|
+
* It should return a {@link Subscribable}, which is compatible with an RxJS
|
|
4582
|
+
* Observable, although RxJS is not required to create them.
|
|
4583
|
+
*/
|
|
4584
|
+
|
|
4585
|
+
function createEventObservableLogic(lazyObservableOrConfig) {
|
|
4586
|
+
const lazyObservable = typeof lazyObservableOrConfig === 'function' ? lazyObservableOrConfig : lazyObservableOrConfig.run;
|
|
4587
|
+
|
|
4588
|
+
// TODO: event types
|
|
4589
|
+
const logic = {
|
|
4590
|
+
config: lazyObservable,
|
|
4591
|
+
transition: (state, event) => {
|
|
4592
|
+
if (state.status !== 'active') {
|
|
4593
|
+
return state;
|
|
4594
|
+
}
|
|
4595
|
+
switch (event.type) {
|
|
4596
|
+
case XSTATE_OBSERVABLE_ERROR:
|
|
4597
|
+
return {
|
|
4598
|
+
...state,
|
|
4599
|
+
status: 'error',
|
|
4600
|
+
error: event.data,
|
|
4601
|
+
input: undefined,
|
|
4602
|
+
_subscription: undefined
|
|
4603
|
+
};
|
|
4604
|
+
case XSTATE_OBSERVABLE_COMPLETE:
|
|
4605
|
+
return {
|
|
4606
|
+
...state,
|
|
4607
|
+
status: 'done',
|
|
4608
|
+
input: undefined,
|
|
4609
|
+
_subscription: undefined
|
|
4610
|
+
};
|
|
4611
|
+
case XSTATE_STOP:
|
|
4612
|
+
state._subscription.unsubscribe();
|
|
4613
|
+
return {
|
|
4614
|
+
...state,
|
|
4615
|
+
status: 'stopped',
|
|
4616
|
+
input: undefined,
|
|
4617
|
+
_subscription: undefined
|
|
4618
|
+
};
|
|
4619
|
+
default:
|
|
4620
|
+
return state;
|
|
4621
|
+
}
|
|
4622
|
+
},
|
|
4623
|
+
getInitialSnapshot: (_, input) => {
|
|
4624
|
+
return {
|
|
4625
|
+
status: 'active',
|
|
4626
|
+
output: undefined,
|
|
4627
|
+
error: undefined,
|
|
4628
|
+
context: undefined,
|
|
4629
|
+
input,
|
|
4630
|
+
_subscription: undefined
|
|
4631
|
+
};
|
|
4632
|
+
},
|
|
4633
|
+
start: (state, {
|
|
4634
|
+
self,
|
|
4635
|
+
system,
|
|
4636
|
+
emit
|
|
4637
|
+
}) => {
|
|
4638
|
+
if (state.status === 'done') {
|
|
4639
|
+
// Do not restart a completed observable
|
|
4640
|
+
return;
|
|
4641
|
+
}
|
|
4642
|
+
state._subscription = lazyObservable({
|
|
4643
|
+
input: state.input,
|
|
4644
|
+
system,
|
|
4645
|
+
self,
|
|
4646
|
+
emit
|
|
4647
|
+
}).subscribe({
|
|
4648
|
+
next: value => {
|
|
4649
|
+
if (self._parent) {
|
|
4650
|
+
system._relay(self, self._parent, value);
|
|
4651
|
+
}
|
|
4652
|
+
},
|
|
4653
|
+
error: err => {
|
|
4654
|
+
system._relay(self, self, {
|
|
4655
|
+
type: XSTATE_OBSERVABLE_ERROR,
|
|
4656
|
+
data: err
|
|
4657
|
+
});
|
|
4658
|
+
},
|
|
4659
|
+
complete: () => {
|
|
4660
|
+
system._relay(self, self, {
|
|
4661
|
+
type: XSTATE_OBSERVABLE_COMPLETE
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
});
|
|
4665
|
+
},
|
|
4666
|
+
getPersistedSnapshot: ({
|
|
4667
|
+
_subscription,
|
|
4668
|
+
...snapshot
|
|
4669
|
+
}) => snapshot,
|
|
4670
|
+
restoreSnapshot: snapshot => ({
|
|
4671
|
+
...snapshot,
|
|
4672
|
+
_subscription: undefined
|
|
4673
|
+
})
|
|
4674
|
+
};
|
|
4675
|
+
return logic;
|
|
4676
|
+
}
|
|
4677
|
+
|
|
4678
|
+
const XSTATE_ASYNC_RESOLVE = 'xstate.async.resolve';
|
|
4679
|
+
const XSTATE_ASYNC_REJECT = 'xstate.async.reject';
|
|
4680
|
+
class TimeoutError extends Error {
|
|
4681
|
+
constructor(timeout) {
|
|
4682
|
+
super(`Async logic timed out after ${timeout}.`);
|
|
4683
|
+
this.name = 'TimeoutError';
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
|
|
4687
|
+
/**
|
|
4688
|
+
* Represents an actor created by `createAsyncLogic`.
|
|
4689
|
+
*
|
|
4690
|
+
* The type of `self` within the actor's logic.
|
|
4691
|
+
*
|
|
4692
|
+
* @example
|
|
4693
|
+
*
|
|
4694
|
+
* ```ts
|
|
4695
|
+
* import { createAsyncLogic, createActor } from 'xstate';
|
|
4696
|
+
*
|
|
4697
|
+
* // The actor's resolved output
|
|
4698
|
+
* type Output = string;
|
|
4699
|
+
* // The actor's input.
|
|
4700
|
+
* type Input = { message: string };
|
|
4701
|
+
*
|
|
4702
|
+
* // Actor logic that fetches the url of an image of a cat saying `input.message`.
|
|
4703
|
+
* const logic = createAsyncLogic<Output, Input>({
|
|
4704
|
+
* run: async ({ input, self }, enq) => {
|
|
4705
|
+
* self;
|
|
4706
|
+
* // ^? AsyncActor<Output, Input>
|
|
4707
|
+
* enq.emit({ type: 'request.start' });
|
|
4708
|
+
*
|
|
4709
|
+
* const data = await fetch(
|
|
4710
|
+
* `https://cataas.com/cat/says/${input.message}`
|
|
4711
|
+
* );
|
|
4712
|
+
* const url = await data.json();
|
|
4713
|
+
* return url;
|
|
4714
|
+
* }
|
|
4715
|
+
* });
|
|
4716
|
+
*
|
|
4717
|
+
* const actor = createActor(logic, { input: { message: 'hello world' } });
|
|
4718
|
+
* // ^? AsyncActor<Output, Input>
|
|
4719
|
+
* ```
|
|
4720
|
+
*
|
|
4721
|
+
* @see {@link createAsyncLogic}
|
|
4722
|
+
*/
|
|
4723
|
+
|
|
4724
|
+
/**
|
|
4725
|
+
* An actor logic creator which returns async logic as defined by an async
|
|
4726
|
+
* process that resolves or rejects after some time.
|
|
4727
|
+
*
|
|
4728
|
+
* Actors created from async actor logic can:
|
|
4729
|
+
*
|
|
4730
|
+
* - Output the resolved value of the async process
|
|
4731
|
+
* - Error with the rejected value of the async process
|
|
4732
|
+
* - Abort when stopped or timed out
|
|
4733
|
+
*
|
|
4734
|
+
* Sending events to async actors will have no effect.
|
|
4735
|
+
*
|
|
4736
|
+
* @example
|
|
4737
|
+
*
|
|
4738
|
+
* ```ts
|
|
4739
|
+
* const asyncLogic = createAsyncLogic({
|
|
4740
|
+
* id: 'fetch-user',
|
|
4741
|
+
* timeout: '30s',
|
|
4742
|
+
* run: async ({ signal }, enq) => {
|
|
4743
|
+
* enq.emit({ type: 'request.start' });
|
|
4744
|
+
* const result = await fetch('https://example.com/...', {
|
|
4745
|
+
* signal
|
|
4746
|
+
* }).then((data) => data.json());
|
|
4747
|
+
*
|
|
4748
|
+
* return result;
|
|
4749
|
+
* }
|
|
4750
|
+
* });
|
|
4751
|
+
*
|
|
4752
|
+
* const asyncActor = createActor(asyncLogic);
|
|
4753
|
+
* asyncActor.subscribe((snapshot) => {
|
|
4754
|
+
* console.log(snapshot);
|
|
4755
|
+
* });
|
|
4756
|
+
* asyncActor.start();
|
|
4757
|
+
* // => {
|
|
4758
|
+
* // output: undefined,
|
|
4759
|
+
* // status: 'active'
|
|
4760
|
+
* // ...
|
|
4761
|
+
* // }
|
|
4762
|
+
*
|
|
4763
|
+
* // After promise resolves
|
|
4764
|
+
* // => {
|
|
4765
|
+
* // output: { ... },
|
|
4766
|
+
* // status: 'done',
|
|
4767
|
+
* // ...
|
|
4768
|
+
* // }
|
|
4769
|
+
* ```
|
|
4770
|
+
*
|
|
4771
|
+
* @param asyncLogic A config object with a `run` function which returns a
|
|
4772
|
+
* Promise, and accepts an object with the following properties:
|
|
4773
|
+
*
|
|
4774
|
+
* - `input` - Data that was provided to the async actor
|
|
4775
|
+
* - `self` - The async actor ref
|
|
4776
|
+
* - `system` - The actor system to which the async actor belongs
|
|
4777
|
+
* - `signal` - An abort signal for cancellation
|
|
4778
|
+
*
|
|
4779
|
+
* `run` also receives an `enq` object with the following properties:
|
|
4780
|
+
*
|
|
4781
|
+
* - `emit` - Emits an event that can be observed with `actor.on(...)`
|
|
4782
|
+
*
|
|
4783
|
+
* @see {@link https://stately.ai/docs/input | Input docs} for more information about how input is passed
|
|
4784
|
+
*/
|
|
4785
|
+
|
|
4786
|
+
function createAsyncLogic(asyncLogic) {
|
|
4787
|
+
const config = asyncLogic;
|
|
4788
|
+
function waitForEffect(self, key) {
|
|
4789
|
+
return new Promise((resolve, reject) => {
|
|
4790
|
+
const subscription = self.subscribe(snapshot => {
|
|
4791
|
+
const effect = snapshot.effects?.[key];
|
|
4792
|
+
if (effect?.status === 'done') {
|
|
4793
|
+
subscription.unsubscribe();
|
|
4794
|
+
resolve(effect.output);
|
|
4795
|
+
} else if (effect?.status === 'error') {
|
|
4796
|
+
subscription.unsubscribe();
|
|
4797
|
+
reject(effect.error);
|
|
4798
|
+
}
|
|
4799
|
+
});
|
|
4800
|
+
});
|
|
4801
|
+
}
|
|
4802
|
+
return createLogic({
|
|
4803
|
+
id: config.id,
|
|
4804
|
+
schemas: config.schemas,
|
|
4805
|
+
context: undefined,
|
|
4806
|
+
run: ({
|
|
4807
|
+
event,
|
|
4808
|
+
input,
|
|
4809
|
+
self,
|
|
4810
|
+
system
|
|
4811
|
+
}, enq) => {
|
|
4812
|
+
switch (event.type) {
|
|
4813
|
+
case XSTATE_ASYNC_RESOLVE:
|
|
4814
|
+
{
|
|
4815
|
+
const resolvedValue = event.data ?? event.output;
|
|
4816
|
+
return {
|
|
4817
|
+
status: 'done',
|
|
4818
|
+
output: resolvedValue,
|
|
4819
|
+
input: undefined,
|
|
4820
|
+
effects: {
|
|
4821
|
+
async: {
|
|
4822
|
+
status: 'done',
|
|
4823
|
+
output: resolvedValue
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
case XSTATE_ASYNC_REJECT:
|
|
4829
|
+
{
|
|
4830
|
+
const error = event.data ?? event.error;
|
|
4831
|
+
return {
|
|
4832
|
+
status: 'error',
|
|
4833
|
+
error,
|
|
4834
|
+
input: undefined,
|
|
4835
|
+
effects: {
|
|
4836
|
+
async: {
|
|
4837
|
+
status: 'error',
|
|
4838
|
+
error
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
};
|
|
4842
|
+
}
|
|
4843
|
+
}
|
|
4844
|
+
if (event.type !== XSTATE_INIT) {
|
|
4845
|
+
return;
|
|
4846
|
+
}
|
|
4847
|
+
enq.effect(() => {
|
|
4848
|
+
const actorSelf = self;
|
|
4849
|
+
const controller = new AbortController();
|
|
4850
|
+
const timeout = config.timeout;
|
|
4851
|
+
const timeoutMs = parseDelayToMilliseconds(timeout);
|
|
4852
|
+
const timeoutId = timeoutMs === undefined ? undefined : system._clock.setTimeout(() => {
|
|
4853
|
+
if (self.getSnapshot().status !== 'active') {
|
|
4854
|
+
return;
|
|
4855
|
+
}
|
|
4856
|
+
controller.abort();
|
|
4857
|
+
system._relay(actorSelf, actorSelf, {
|
|
4858
|
+
type: XSTATE_ASYNC_REJECT,
|
|
4859
|
+
data: new TimeoutError(timeout)
|
|
4860
|
+
});
|
|
4861
|
+
}, timeoutMs);
|
|
4862
|
+
const clearTimeout = () => {
|
|
4863
|
+
if (timeoutId !== undefined) {
|
|
4864
|
+
system._clock.clearTimeout(timeoutId);
|
|
4865
|
+
}
|
|
4866
|
+
};
|
|
4867
|
+
const resolvedPromise = Promise.resolve(config.run({
|
|
4868
|
+
input,
|
|
4869
|
+
system,
|
|
4870
|
+
self: self,
|
|
4871
|
+
signal: controller.signal
|
|
4872
|
+
}, {
|
|
4873
|
+
emit: enq.emit,
|
|
4874
|
+
step: async (key, exec) => {
|
|
4875
|
+
const effect = self.getSnapshot().effects?.[key];
|
|
4876
|
+
if (effect?.status === 'done') {
|
|
4877
|
+
return effect.output;
|
|
4878
|
+
}
|
|
4879
|
+
if (effect?.status === 'error') {
|
|
4880
|
+
throw effect.error;
|
|
4881
|
+
}
|
|
4882
|
+
if (effect?.status === 'active') {
|
|
4883
|
+
return waitForEffect(self, key);
|
|
4884
|
+
}
|
|
4885
|
+
system._relay(actorSelf, actorSelf, {
|
|
4886
|
+
type: XSTATE_LOGIC_EFFECT_START,
|
|
4887
|
+
key
|
|
4888
|
+
});
|
|
4889
|
+
try {
|
|
4890
|
+
const output = await exec();
|
|
4891
|
+
system._relay(actorSelf, actorSelf, {
|
|
4892
|
+
type: XSTATE_LOGIC_EFFECT_RESOLVE,
|
|
4893
|
+
key,
|
|
4894
|
+
output
|
|
4895
|
+
});
|
|
4896
|
+
return output;
|
|
4897
|
+
} catch (error) {
|
|
4898
|
+
system._relay(actorSelf, actorSelf, {
|
|
4899
|
+
type: XSTATE_LOGIC_EFFECT_REJECT,
|
|
4900
|
+
key,
|
|
4901
|
+
error
|
|
4902
|
+
});
|
|
4903
|
+
throw error;
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
}));
|
|
4907
|
+
resolvedPromise.then(response => {
|
|
4908
|
+
clearTimeout();
|
|
4909
|
+
if (self.getSnapshot().status !== 'active') {
|
|
4910
|
+
return;
|
|
4911
|
+
}
|
|
4912
|
+
system._relay(actorSelf, actorSelf, {
|
|
4913
|
+
type: XSTATE_ASYNC_RESOLVE,
|
|
4914
|
+
data: response
|
|
4915
|
+
});
|
|
4916
|
+
}, errorData => {
|
|
4917
|
+
clearTimeout();
|
|
4918
|
+
if (self.getSnapshot().status !== 'active') {
|
|
4919
|
+
return;
|
|
4920
|
+
}
|
|
4921
|
+
system._relay(actorSelf, actorSelf, {
|
|
4922
|
+
type: XSTATE_ASYNC_REJECT,
|
|
4923
|
+
data: errorData
|
|
4924
|
+
});
|
|
4925
|
+
});
|
|
4926
|
+
return () => {
|
|
4927
|
+
controller.abort();
|
|
4928
|
+
clearTimeout();
|
|
4929
|
+
};
|
|
4930
|
+
});
|
|
4931
|
+
return {
|
|
4932
|
+
effects: {
|
|
4933
|
+
async: {
|
|
4934
|
+
status: 'active'
|
|
4935
|
+
}
|
|
4936
|
+
}
|
|
4937
|
+
};
|
|
4938
|
+
}
|
|
4939
|
+
});
|
|
4940
|
+
}
|
|
4941
|
+
|
|
4942
|
+
const emptyLogic = /* #__PURE__ */createLogic({
|
|
4943
|
+
context: undefined,
|
|
4944
|
+
run: () => undefined
|
|
4945
|
+
});
|
|
4946
|
+
function createEmptyActor() {
|
|
4947
|
+
return createActor(emptyLogic);
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4950
|
+
exports.$$ACTOR_TYPE = $$ACTOR_TYPE;
|
|
4951
|
+
exports.Actor = Actor;
|
|
4952
|
+
exports.NULL_EVENT = NULL_EVENT;
|
|
4953
|
+
exports.ProcessingStatus = ProcessingStatus;
|
|
4954
|
+
exports.STATE_DELIMITER = STATE_DELIMITER;
|
|
4955
|
+
exports.TimeoutError = TimeoutError;
|
|
4956
|
+
exports.XSTATE_INIT = XSTATE_INIT;
|
|
4957
|
+
exports.checkStateIn = checkStateIn;
|
|
4958
|
+
exports.createActor = createActor;
|
|
4959
|
+
exports.createAsyncAtom = createAsyncAtom;
|
|
4960
|
+
exports.createAsyncLogic = createAsyncLogic;
|
|
4961
|
+
exports.createAtom = createAtom;
|
|
4962
|
+
exports.createAtomConfig = createAtomConfig;
|
|
4963
|
+
exports.createCallbackLogic = createCallbackLogic;
|
|
4964
|
+
exports.createEmptyActor = createEmptyActor;
|
|
4965
|
+
exports.createEventObservableLogic = createEventObservableLogic;
|
|
4966
|
+
exports.createInitEvent = createInitEvent;
|
|
4967
|
+
exports.createInvokeId = createInvokeId;
|
|
4968
|
+
exports.createInvokeTimeoutEvent = createInvokeTimeoutEvent;
|
|
4969
|
+
exports.createListenerLogic = createListenerLogic;
|
|
4970
|
+
exports.createLogic = createLogic;
|
|
4971
|
+
exports.createMachineSnapshot = createMachineSnapshot;
|
|
4972
|
+
exports.createObservableLogic = createObservableLogic;
|
|
4973
|
+
exports.createReducerAtom = createReducerAtom;
|
|
4974
|
+
exports.createSubscriptionLogic = createSubscriptionLogic;
|
|
4975
|
+
exports.evaluateCandidate = evaluateCandidate;
|
|
4976
|
+
exports.formatRouteTransitions = formatRouteTransitions;
|
|
4977
|
+
exports.formatTransition = formatTransition;
|
|
4978
|
+
exports.getAllOwnEventDescriptors = getAllOwnEventDescriptors;
|
|
4979
|
+
exports.getAllStateNodes = getAllStateNodes;
|
|
4980
|
+
exports.getCandidates = getCandidates;
|
|
4981
|
+
exports.getDelayedTransitions = getDelayedTransitions;
|
|
4982
|
+
exports.getPersistedSnapshot = getPersistedSnapshot;
|
|
4983
|
+
exports.getProperAncestors = getProperAncestors;
|
|
4984
|
+
exports.getStateNodeByPath = getStateNodeByPath;
|
|
4985
|
+
exports.getStateNodes = getStateNodes;
|
|
4986
|
+
exports.initialMicrostep = initialMicrostep;
|
|
4987
|
+
exports.isAtom = isAtom;
|
|
4988
|
+
exports.isAtomicStateNode = isAtomicStateNode;
|
|
4989
|
+
exports.isInFinalState = isInFinalState;
|
|
4990
|
+
exports.isMachineSnapshot = isMachineSnapshot;
|
|
4991
|
+
exports.isStateId = isStateId;
|
|
4992
|
+
exports.listenerLogic = listenerLogic;
|
|
4993
|
+
exports.macrostep = macrostep;
|
|
4994
|
+
exports.mapValues = mapValues;
|
|
4995
|
+
exports.matchesEventDescriptor = matchesEventDescriptor;
|
|
4996
|
+
exports.matchesState = matchesState;
|
|
4997
|
+
exports.parseDelayToMilliseconds = parseDelayToMilliseconds;
|
|
4998
|
+
exports.pathToStateValue = pathToStateValue;
|
|
4999
|
+
exports.resolveAndExecuteActionsWithContext = resolveAndExecuteActionsWithContext;
|
|
5000
|
+
exports.resolveReferencedActor = resolveReferencedActor;
|
|
5001
|
+
exports.resolveStateValue = resolveStateValue;
|
|
5002
|
+
exports.subscriptionLogic = subscriptionLogic;
|
|
5003
|
+
exports.toArray = toArray;
|
|
5004
|
+
exports.toObserver = toObserver;
|
|
5005
|
+
exports.toStatePath = toStatePath;
|
|
5006
|
+
exports.toTransitionConfigArray = toTransitionConfigArray;
|
|
5007
|
+
exports.transitionNode = transitionNode;
|