xstate 6.0.0-alpha.4 → 6.0.0-alpha.41
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 +19 -17
- package/bin/xstate.js +36 -0
- package/dist/StateMachine-8e73d782.cjs.js +1200 -0
- package/dist/StateMachine-db4e6d4b.development.cjs.js +1232 -0
- package/dist/StateMachine-ee89d49f.esm.js +1197 -0
- package/dist/StateMachine-f60abe05.development.esm.js +1229 -0
- package/dist/createActor-20615484.development.cjs.js +5717 -0
- package/dist/createActor-a3f7750f.esm.js +5578 -0
- package/dist/createActor-c97c04c9.development.esm.js +5620 -0
- package/dist/createActor-f3afd774.cjs.js +5675 -0
- package/dist/declarations/src/State.d.ts +17 -4
- package/dist/declarations/src/StateMachine.d.ts +37 -16
- package/dist/declarations/src/StateNode.d.ts +5 -2
- package/dist/declarations/src/actions.d.ts +10 -7
- package/dist/declarations/src/actors/callback.d.ts +2 -0
- package/dist/declarations/src/actors/logic.d.ts +7 -24
- package/dist/declarations/src/actors/observable.d.ts +3 -0
- package/dist/declarations/src/actors/promise.d.ts +6 -2
- package/dist/declarations/src/createActor.d.ts +64 -20
- package/dist/declarations/src/createMachine.d.ts +21 -13
- package/dist/declarations/src/createMachineFromConfig.d.ts +64 -30
- package/dist/declarations/src/durable/index.d.ts +133 -0
- package/dist/declarations/src/effectDescriptor.d.ts +81 -0
- package/dist/declarations/src/fsm.d.ts +77 -0
- package/dist/declarations/src/index.d.ts +15 -8
- package/dist/declarations/src/inspection.d.ts +11 -1
- package/dist/declarations/src/machineVersion.types.d.ts +38 -0
- package/dist/declarations/src/machineVersions.d.ts +100 -0
- package/dist/declarations/src/runtimeHelpers.d.ts +35 -0
- package/dist/declarations/src/schema.types.d.ts +5 -0
- package/dist/declarations/src/serialize.d.ts +9 -22
- package/dist/declarations/src/setup.d.ts +317 -64
- package/dist/declarations/src/spawn.d.ts +5 -5
- package/dist/declarations/src/stateUtils.d.ts +19 -7
- package/dist/declarations/src/system.d.ts +74 -11
- package/dist/declarations/src/transitionActions.d.ts +14 -2
- package/dist/declarations/src/types.d.ts +318 -134
- package/dist/declarations/src/types.v6.d.ts +306 -52
- package/dist/declarations/src/utils.d.ts +5 -3
- package/dist/declarations/src/validation/index.d.ts +37 -0
- package/dist/declarations/src/validation.types.d.ts +22 -0
- package/dist/effectDescriptor-57ae11fa.esm.js +250 -0
- package/dist/effectDescriptor-b7c48ceb.development.esm.js +250 -0
- package/dist/effectDescriptor-ba9e14f0.development.cjs.js +257 -0
- package/dist/effectDescriptor-e2bb61e2.cjs.js +257 -0
- package/dist/index-7fd92683.cjs.js +941 -0
- package/dist/index-a8ce72f1.esm.js +932 -0
- package/dist/index-b5b1b2bc.development.esm.js +932 -0
- package/dist/index-d42efc07.development.cjs.js +941 -0
- package/dist/xstate-actors.cjs.js +6 -5
- package/dist/xstate-actors.development.cjs.js +6 -5
- package/dist/xstate-actors.development.esm.js +2 -1
- package/dist/xstate-actors.esm.js +2 -1
- package/dist/xstate-actors.umd.min.js +1 -1
- package/dist/xstate-actors.umd.min.js.map +1 -1
- package/dist/xstate-durable.cjs.d.mts +2 -0
- package/dist/xstate-durable.cjs.d.ts +2 -0
- package/dist/xstate-durable.cjs.js +327 -0
- package/dist/xstate-durable.cjs.mjs +5 -0
- package/dist/xstate-durable.development.cjs.js +327 -0
- package/dist/xstate-durable.development.cjs.mjs +5 -0
- package/dist/xstate-durable.development.esm.js +321 -0
- package/dist/xstate-durable.esm.js +321 -0
- package/dist/xstate-durable.umd.min.js +2 -0
- package/dist/xstate-durable.umd.min.js.map +1 -0
- package/dist/xstate-graph.cjs.js +16 -19
- package/dist/xstate-graph.development.cjs.js +16 -19
- package/dist/xstate-graph.development.esm.js +13 -16
- package/dist/xstate-graph.esm.js +13 -16
- package/dist/xstate-graph.umd.min.js +1 -1
- package/dist/xstate-graph.umd.min.js.map +1 -1
- package/dist/xstate-validation.cjs.d.mts +2 -0
- package/dist/xstate-validation.cjs.d.ts +2 -0
- package/dist/xstate-validation.cjs.js +277 -0
- package/dist/xstate-validation.cjs.mjs +5 -0
- package/dist/xstate-validation.development.cjs.js +277 -0
- package/dist/xstate-validation.development.cjs.mjs +5 -0
- package/dist/xstate-validation.development.esm.js +271 -0
- package/dist/xstate-validation.esm.js +271 -0
- package/dist/xstate-validation.umd.min.js +2 -0
- package/dist/xstate-validation.umd.min.js.map +1 -0
- package/dist/xstate.cjs.js +1361 -412
- package/dist/xstate.cjs.mjs +9 -0
- package/dist/xstate.development.cjs.js +1361 -412
- package/dist/xstate.development.cjs.mjs +9 -0
- package/dist/xstate.development.esm.js +1332 -392
- package/dist/xstate.esm.js +1332 -392
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/durable/package.json +8 -0
- package/package.json +39 -3
- package/validation/package.json +8 -0
- package/dist/StateMachine-445a8480.cjs.js +0 -732
- package/dist/StateMachine-4deb4090.development.esm.js +0 -746
- package/dist/StateMachine-f310bc72.esm.js +0 -729
- package/dist/StateMachine-f83f01f9.development.cjs.js +0 -749
- package/dist/index-918ab496.cjs.js +0 -4397
- package/dist/index-9cce3480.esm.js +0 -4342
- package/dist/index-ccbe7c0b.development.esm.js +0 -4381
- package/dist/index-ebaf6fd8.development.cjs.js +0 -4436
|
@@ -0,0 +1,1200 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var createActor = require('./createActor-f3afd774.cjs.js');
|
|
4
|
+
var dist_xstateActors = require('./index-7fd92683.cjs.js');
|
|
5
|
+
|
|
6
|
+
function createSpawner(actorScope, actors, spawnedChildren) {
|
|
7
|
+
return (src, options) => {
|
|
8
|
+
const referencedSrc = Object.entries(actors).find(([, logic]) => logic === src)?.[0];
|
|
9
|
+
// Generated ids come from the same transaction allocator as `enq.spawn`,
|
|
10
|
+
// so context-factory allocations persist with the snapshot and never
|
|
11
|
+
// collide with later spawns.
|
|
12
|
+
const id = options?.id ?? createActor.allocateChildId(actorScope, referencedSrc ?? src).id;
|
|
13
|
+
if (options?.id !== undefined) {
|
|
14
|
+
createActor.assertChildIdFree(actorScope, options.id);
|
|
15
|
+
createActor.reserveChildId(actorScope, options.id);
|
|
16
|
+
}
|
|
17
|
+
const actor = actorScope.system.createActorRef(src, {
|
|
18
|
+
id,
|
|
19
|
+
parent: actorScope.self,
|
|
20
|
+
syncSnapshot: options?.syncSnapshot,
|
|
21
|
+
input: options?.input,
|
|
22
|
+
src: referencedSrc ?? src,
|
|
23
|
+
registryKey: options?.registryKey
|
|
24
|
+
});
|
|
25
|
+
spawnedChildren[actor.id] = actor;
|
|
26
|
+
return actor;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const cache = new WeakMap();
|
|
31
|
+
function memo(object, key, fn) {
|
|
32
|
+
let memoizedData = cache.get(object);
|
|
33
|
+
if (!memoizedData) {
|
|
34
|
+
memoizedData = {
|
|
35
|
+
[key]: fn()
|
|
36
|
+
};
|
|
37
|
+
cache.set(object, memoizedData);
|
|
38
|
+
} else if (!(key in memoizedData)) {
|
|
39
|
+
memoizedData[key] = fn();
|
|
40
|
+
}
|
|
41
|
+
return memoizedData[key];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const EMPTY_OBJECT = {};
|
|
45
|
+
class StateNode {
|
|
46
|
+
constructor(/** The raw config used to create the machine. */
|
|
47
|
+
config, options) {
|
|
48
|
+
this.config = config;
|
|
49
|
+
/**
|
|
50
|
+
* The relative key of the state node, which represents its location in the
|
|
51
|
+
* overall state value.
|
|
52
|
+
*/
|
|
53
|
+
this.key = void 0;
|
|
54
|
+
/** The unique ID of the state node. */
|
|
55
|
+
this.id = void 0;
|
|
56
|
+
/**
|
|
57
|
+
* The type of this state node:
|
|
58
|
+
*
|
|
59
|
+
* - `'atomic'` - no child state nodes
|
|
60
|
+
* - `'compound'` - nested child state nodes (XOR)
|
|
61
|
+
* - `'parallel'` - orthogonal nested child state nodes (AND)
|
|
62
|
+
* - `'history'` - history state node
|
|
63
|
+
* - `'choice'` - pure routing decision state node
|
|
64
|
+
* - `'final'` - final state node
|
|
65
|
+
*/
|
|
66
|
+
this.type = void 0;
|
|
67
|
+
/** The string path from the root machine node to this node. */
|
|
68
|
+
this.path = void 0;
|
|
69
|
+
/** The child state nodes. */
|
|
70
|
+
this.states = void 0;
|
|
71
|
+
/**
|
|
72
|
+
* The type of history on this state node. Can be:
|
|
73
|
+
*
|
|
74
|
+
* - `'shallow'` - recalls only top-level historical state value
|
|
75
|
+
* - `'deep'` - recalls historical state value at all levels
|
|
76
|
+
*/
|
|
77
|
+
this.history = void 0;
|
|
78
|
+
/** The action(s) to be executed upon entering the state node. */
|
|
79
|
+
this.entry = void 0;
|
|
80
|
+
/** The action(s) to be executed upon exiting the state node. */
|
|
81
|
+
this.exit = void 0;
|
|
82
|
+
/** The parent state node. */
|
|
83
|
+
this.parent = void 0;
|
|
84
|
+
/** The root machine node. */
|
|
85
|
+
this.machine = void 0;
|
|
86
|
+
/**
|
|
87
|
+
* The meta data associated with this state node, which will be returned in
|
|
88
|
+
* State instances.
|
|
89
|
+
*/
|
|
90
|
+
this.meta = void 0;
|
|
91
|
+
/**
|
|
92
|
+
* The output data sent with the `xstate.done.state` event if this is a final
|
|
93
|
+
* state node.
|
|
94
|
+
*/
|
|
95
|
+
this.output = void 0;
|
|
96
|
+
/**
|
|
97
|
+
* The order this state node appears. Corresponds to the implicit document
|
|
98
|
+
* order.
|
|
99
|
+
*/
|
|
100
|
+
this.order = -1;
|
|
101
|
+
this.description = void 0;
|
|
102
|
+
this.schemas = void 0;
|
|
103
|
+
this.tags = [];
|
|
104
|
+
this.transitions = void 0;
|
|
105
|
+
this.always = void 0;
|
|
106
|
+
this.invoke = void 0;
|
|
107
|
+
this.on = void 0;
|
|
108
|
+
this.after = void 0;
|
|
109
|
+
this.events = void 0;
|
|
110
|
+
this.ownEvents = void 0;
|
|
111
|
+
this._candidateCache = void 0;
|
|
112
|
+
this.parent = options._parent;
|
|
113
|
+
this.key = options._key;
|
|
114
|
+
this.machine = options._machine;
|
|
115
|
+
this.path = this.parent ? this.parent.path.concat(this.key) : [];
|
|
116
|
+
const firstStateKey = this.config.states ? Object.keys(this.config.states)[0] : undefined;
|
|
117
|
+
this.id = this.config.id || [this.machine.id, ...this.path].join(createActor.STATE_DELIMITER);
|
|
118
|
+
this.type = this.config.type || (firstStateKey !== undefined ? 'compound' : this.config.history ? 'history' : 'atomic');
|
|
119
|
+
this.description = this.config.description;
|
|
120
|
+
this.schemas = this.config.schemas;
|
|
121
|
+
validateStateNodeConfig(this);
|
|
122
|
+
this.order = this.machine.idMap.size;
|
|
123
|
+
this.machine.idMap.set(this.id, this);
|
|
124
|
+
this.states = this.config.states ? createActor.mapValues(this.config.states, (stateConfig, key) => {
|
|
125
|
+
const stateNode = new StateNode(stateConfig, {
|
|
126
|
+
_parent: this,
|
|
127
|
+
_key: key,
|
|
128
|
+
_machine: this.machine
|
|
129
|
+
});
|
|
130
|
+
return stateNode;
|
|
131
|
+
}) : EMPTY_OBJECT;
|
|
132
|
+
if (this.type === 'compound' && !this.config.initial) {
|
|
133
|
+
throw new Error(`No initial state specified for compound state node "#${this.id}".`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// History config
|
|
137
|
+
this.history = this.config.history === true ? 'shallow' : this.config.history || false;
|
|
138
|
+
this.entry = this.config.entry;
|
|
139
|
+
this.exit = this.config.exit;
|
|
140
|
+
if (this.entry) {
|
|
141
|
+
// @ts-expect-error _special is an internal marker not on the Action type
|
|
142
|
+
this.entry._special = true;
|
|
143
|
+
}
|
|
144
|
+
if (this.exit) {
|
|
145
|
+
// @ts-expect-error _special is an internal marker not on the Action type
|
|
146
|
+
this.exit._special = true;
|
|
147
|
+
}
|
|
148
|
+
this.meta = this.config.meta;
|
|
149
|
+
this.output = this.type === 'final' || !this.parent ? this.config.output : undefined;
|
|
150
|
+
this.tags = createActor.toArray(config.tags).slice();
|
|
151
|
+
this.invoke = createActor.toArray(this.config.invoke).map((invokeConfig, i) => {
|
|
152
|
+
const {
|
|
153
|
+
src,
|
|
154
|
+
registryKey
|
|
155
|
+
} = invokeConfig;
|
|
156
|
+
const invokeId = createActor.createInvokeId(this.id, i);
|
|
157
|
+
const resolvedId = invokeConfig.id ?? invokeId;
|
|
158
|
+
// Referenced (string) actors keep their logical name so persisted
|
|
159
|
+
// snapshots reference `src: 'fetchUser'` rather than a positional id;
|
|
160
|
+
// only inline logic gets the synthetic source name.
|
|
161
|
+
const sourceName = typeof src === 'string' ? src : `xstate.invoke.${invokeId}`;
|
|
162
|
+
return {
|
|
163
|
+
...invokeConfig,
|
|
164
|
+
src: sourceName,
|
|
165
|
+
logic: src,
|
|
166
|
+
id: resolvedId,
|
|
167
|
+
registryKey
|
|
168
|
+
};
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** @internal */
|
|
173
|
+
_initialize() {
|
|
174
|
+
this.after = createActor.getDelayedTransitions(this);
|
|
175
|
+
this.transitions = formatTransitions(this);
|
|
176
|
+
if (this.type === 'choice') {
|
|
177
|
+
this.always = formatChoiceTransitions(this);
|
|
178
|
+
} else if (this.config.always) {
|
|
179
|
+
this.always = mapTransitionConfigs(this.config.always, transition => createActor.formatTransition(this, createActor.NULL_EVENT, transition));
|
|
180
|
+
}
|
|
181
|
+
for (const key of Object.keys(this.states)) {
|
|
182
|
+
this.states[key]._initialize();
|
|
183
|
+
}
|
|
184
|
+
this._refreshEventMetadata();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** @internal */
|
|
188
|
+
_refreshEventMetadata() {
|
|
189
|
+
const on = {};
|
|
190
|
+
const ownEvents = [];
|
|
191
|
+
for (const [descriptor, transitions] of this.transitions) {
|
|
192
|
+
on[descriptor] = transitions.slice();
|
|
193
|
+
if (transitions.some(transition => transition.target || transition.reenter || transition.to)) {
|
|
194
|
+
ownEvents.push(descriptor);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
this.on = on;
|
|
198
|
+
this.ownEvents = ownEvents;
|
|
199
|
+
const events = new Set(ownEvents);
|
|
200
|
+
for (const state of Object.values(this.states)) {
|
|
201
|
+
for (const event of state.events) {
|
|
202
|
+
events.add(event);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.events = Array.from(events);
|
|
206
|
+
}
|
|
207
|
+
get initial() {
|
|
208
|
+
return memo(this, 'initial', () => formatInitialTransition(this, this.config.initial));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** @internal */
|
|
212
|
+
next(snapshot, event, actorScope, selectionResults) {
|
|
213
|
+
const descriptorKey = createActor.getEventDescriptorKey(event);
|
|
214
|
+
let candidates = this._candidateCache?.get(descriptorKey);
|
|
215
|
+
if (!candidates) {
|
|
216
|
+
candidates = createActor.getCandidates(this, event);
|
|
217
|
+
(this._candidateCache ??= new Map()).set(descriptorKey, candidates);
|
|
218
|
+
}
|
|
219
|
+
for (const candidate of candidates) {
|
|
220
|
+
const guardPassed = createActor.evaluateCandidate(candidate, event, snapshot, this, actorScope, selectionResults);
|
|
221
|
+
if (guardPassed) {
|
|
222
|
+
return [candidate];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function validateStateNodeConfig(stateNode) {
|
|
229
|
+
const config = stateNode.config;
|
|
230
|
+
if (stateNode.type === 'history' && !(typeof config.target === 'string' && config.target.trim().length > 0 || Array.isArray(config.target) && config.target.length > 0 && config.target.every(target => typeof target === 'string' && target.trim().length > 0))) {
|
|
231
|
+
throw new Error(`Missing history target on "${stateNode.id}"`);
|
|
232
|
+
}
|
|
233
|
+
if (stateNode.type !== 'choice') {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (typeof config.choice !== 'function') {
|
|
237
|
+
throw new Error(`Missing \`choice\` function on "${stateNode.id}"`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function formatChoiceTransitions(stateNode) {
|
|
241
|
+
const choice = stateNode.config.choice;
|
|
242
|
+
const validateChoiceResult = result => {
|
|
243
|
+
if (!result || result.target === undefined) {
|
|
244
|
+
throw new Error(`Choice "${stateNode.id}" has no target`);
|
|
245
|
+
}
|
|
246
|
+
return result;
|
|
247
|
+
};
|
|
248
|
+
return [createActor.formatTransition(stateNode, createActor.NULL_EVENT, {
|
|
249
|
+
to: args => validateChoiceResult(choice(args))
|
|
250
|
+
})];
|
|
251
|
+
}
|
|
252
|
+
function mapTransitionConfigs(transitionsConfig, mapper) {
|
|
253
|
+
const transitionConfigs = createActor.toTransitionConfigArray(transitionsConfig);
|
|
254
|
+
const transitions = new Array(transitionConfigs.length);
|
|
255
|
+
for (let i = 0; i < transitionConfigs.length; i++) {
|
|
256
|
+
transitions[i] = mapper(transitionConfigs[i]);
|
|
257
|
+
}
|
|
258
|
+
return transitions;
|
|
259
|
+
}
|
|
260
|
+
function formatTransitions(stateNode) {
|
|
261
|
+
const transitions = new Map();
|
|
262
|
+
const addTransitions = (descriptor, additions) => {
|
|
263
|
+
transitions.set(descriptor, [...(transitions.get(descriptor) ?? []), ...additions]);
|
|
264
|
+
};
|
|
265
|
+
if (stateNode.config.on) {
|
|
266
|
+
for (const descriptor of Object.keys(stateNode.config.on)) {
|
|
267
|
+
if (descriptor === createActor.NULL_EVENT) {
|
|
268
|
+
throw new Error('Null event transition key');
|
|
269
|
+
}
|
|
270
|
+
const transitionsConfig = stateNode.config.on[descriptor];
|
|
271
|
+
transitions.set(descriptor, mapTransitionConfigs(transitionsConfig, transition => createActor.formatTransition(stateNode, descriptor, transition)));
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (stateNode.config.onDone) {
|
|
275
|
+
const descriptor = 'xstate.done.state';
|
|
276
|
+
addTransitions(descriptor, mapTransitionConfigs(stateNode.config.onDone, transition => createActor.formatTransition(stateNode, descriptor, {
|
|
277
|
+
...transition,
|
|
278
|
+
matches: {
|
|
279
|
+
...transition.matches,
|
|
280
|
+
stateId: stateNode.id
|
|
281
|
+
}
|
|
282
|
+
})));
|
|
283
|
+
}
|
|
284
|
+
if (stateNode.config.onError) {
|
|
285
|
+
const descriptor = 'xstate.error.*';
|
|
286
|
+
transitions.set(descriptor, mapTransitionConfigs(stateNode.config.onError, transition => createActor.formatTransition(stateNode, descriptor, transition)));
|
|
287
|
+
}
|
|
288
|
+
const createCancelInvokeTimeoutTransition = (descriptor, timeoutEventId, actorId) => createActor.formatTransition(stateNode, descriptor, {
|
|
289
|
+
matches: {
|
|
290
|
+
actorId
|
|
291
|
+
},
|
|
292
|
+
_eventMatcher: (event, snapshot) => createActor.matchesActorSession(event, snapshot, actorId),
|
|
293
|
+
to: (_args, enq) => {
|
|
294
|
+
enq.cancel(timeoutEventId);
|
|
295
|
+
return {};
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
const formatInvokeCompletionTransition = (descriptor, transitionConfig, timeoutEventId) => {
|
|
299
|
+
const {
|
|
300
|
+
target,
|
|
301
|
+
to,
|
|
302
|
+
reenter,
|
|
303
|
+
...rest
|
|
304
|
+
} = transitionConfig;
|
|
305
|
+
return createActor.formatTransition(stateNode, descriptor, {
|
|
306
|
+
...rest,
|
|
307
|
+
reenter,
|
|
308
|
+
to: (args, enq) => {
|
|
309
|
+
if (to) {
|
|
310
|
+
let didEnqueue = false;
|
|
311
|
+
const trackingEnqueue = new Proxy(enq, {
|
|
312
|
+
apply(target, thisArg, argArray) {
|
|
313
|
+
didEnqueue = true;
|
|
314
|
+
return Reflect.apply(target, thisArg, argArray);
|
|
315
|
+
},
|
|
316
|
+
get(target, prop, receiver) {
|
|
317
|
+
const value = Reflect.get(target, prop, receiver);
|
|
318
|
+
if (typeof value !== 'function') {
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
return (...argArray) => {
|
|
322
|
+
didEnqueue = true;
|
|
323
|
+
return value.apply(target, argArray);
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
const result = to(args, trackingEnqueue);
|
|
328
|
+
if (result !== undefined || didEnqueue) {
|
|
329
|
+
enq.cancel(timeoutEventId);
|
|
330
|
+
}
|
|
331
|
+
return result;
|
|
332
|
+
}
|
|
333
|
+
enq.cancel(timeoutEventId);
|
|
334
|
+
return {
|
|
335
|
+
target,
|
|
336
|
+
reenter
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
for (const invokeDef of stateNode.invoke) {
|
|
342
|
+
const withInvokeMatch = transition => ({
|
|
343
|
+
...transition,
|
|
344
|
+
matches: {
|
|
345
|
+
...transition.matches,
|
|
346
|
+
actorId: invokeDef.id
|
|
347
|
+
},
|
|
348
|
+
_eventMatcher: (event, snapshot) => createActor.matchesActorSession(event, snapshot, invokeDef.id)
|
|
349
|
+
});
|
|
350
|
+
const invokeTimeoutEventId = invokeDef.timeout !== undefined ? createActor.createInvokeTimeoutEventId(invokeDef.id) : undefined;
|
|
351
|
+
if (invokeDef.onDone) {
|
|
352
|
+
const descriptor = 'xstate.done.actor';
|
|
353
|
+
const invokeDoneTransitions = mapTransitionConfigs(invokeDef.onDone, rawTransition => {
|
|
354
|
+
const transition = withInvokeMatch(rawTransition);
|
|
355
|
+
return invokeTimeoutEventId ? formatInvokeCompletionTransition(descriptor, transition, invokeTimeoutEventId) : createActor.formatTransition(stateNode, descriptor, transition);
|
|
356
|
+
});
|
|
357
|
+
if (invokeTimeoutEventId) {
|
|
358
|
+
invokeDoneTransitions.push(createCancelInvokeTimeoutTransition(descriptor, invokeTimeoutEventId, invokeDef.id));
|
|
359
|
+
}
|
|
360
|
+
addTransitions(descriptor, invokeDoneTransitions);
|
|
361
|
+
} else if (invokeTimeoutEventId) {
|
|
362
|
+
const descriptor = 'xstate.done.actor';
|
|
363
|
+
addTransitions(descriptor, [createCancelInvokeTimeoutTransition(descriptor, invokeTimeoutEventId, invokeDef.id)]);
|
|
364
|
+
}
|
|
365
|
+
if (invokeDef.onError) {
|
|
366
|
+
const descriptor = 'xstate.error.actor';
|
|
367
|
+
addTransitions(descriptor, mapTransitionConfigs(invokeDef.onError, rawTransition => {
|
|
368
|
+
const transition = withInvokeMatch(rawTransition);
|
|
369
|
+
return invokeTimeoutEventId ? formatInvokeCompletionTransition(descriptor, transition, invokeTimeoutEventId) : createActor.formatTransition(stateNode, descriptor, transition);
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
if (invokeDef.onSnapshot) {
|
|
373
|
+
const descriptor = 'xstate.snapshot.actor';
|
|
374
|
+
addTransitions(descriptor, mapTransitionConfigs(invokeDef.onSnapshot, transition => createActor.formatTransition(stateNode, descriptor, withInvokeMatch(transition))));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
for (const delayedTransition of stateNode.after) {
|
|
378
|
+
let existing = transitions.get(delayedTransition.eventType);
|
|
379
|
+
if (!existing) {
|
|
380
|
+
existing = [];
|
|
381
|
+
transitions.set(delayedTransition.eventType, existing);
|
|
382
|
+
}
|
|
383
|
+
existing.push(delayedTransition);
|
|
384
|
+
}
|
|
385
|
+
return transitions;
|
|
386
|
+
}
|
|
387
|
+
function formatInitialTransition(stateNode, _target) {
|
|
388
|
+
const targetString = typeof _target === 'object' && _target !== null ? _target.target : _target;
|
|
389
|
+
const input = typeof _target === 'object' && _target !== null ? _target.input : undefined;
|
|
390
|
+
const resolvedTarget = typeof targetString === 'string' ? stateNode.states[targetString] : undefined;
|
|
391
|
+
if (!resolvedTarget && targetString) {
|
|
392
|
+
throw new Error(`Initial state "${targetString}" not found on "#${stateNode.id}"`);
|
|
393
|
+
}
|
|
394
|
+
const transition = {
|
|
395
|
+
source: stateNode,
|
|
396
|
+
target: resolvedTarget ? [resolvedTarget] : undefined,
|
|
397
|
+
input
|
|
398
|
+
};
|
|
399
|
+
return transition;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const STATE_IDENTIFIER = '#';
|
|
403
|
+
let emptyCanActor;
|
|
404
|
+
let emptyCanActorScope;
|
|
405
|
+
function getEmptyCanActor() {
|
|
406
|
+
// A minimal inert actor used purely as the `self`/`parent` argument when
|
|
407
|
+
// dry-running transitions for `snapshot.can(...)`. Intentionally not built
|
|
408
|
+
// on `createLogic` so `can()` does not pull that machinery into bundles.
|
|
409
|
+
return emptyCanActor ??= createActor.createActor({
|
|
410
|
+
transition: snapshot => [snapshot, []],
|
|
411
|
+
initialTransition: () => [{
|
|
412
|
+
status: 'active',
|
|
413
|
+
output: undefined,
|
|
414
|
+
error: undefined
|
|
415
|
+
}, []],
|
|
416
|
+
getInitialSnapshot: () => ({
|
|
417
|
+
status: 'active',
|
|
418
|
+
output: undefined,
|
|
419
|
+
error: undefined
|
|
420
|
+
}),
|
|
421
|
+
getPersistedSnapshot: snapshot => snapshot
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
function getEmptyCanActorScope() {
|
|
425
|
+
if (emptyCanActorScope) {
|
|
426
|
+
return emptyCanActorScope;
|
|
427
|
+
}
|
|
428
|
+
const actor = getEmptyCanActor();
|
|
429
|
+
emptyCanActorScope = {
|
|
430
|
+
self: actor,
|
|
431
|
+
logger: () => {},
|
|
432
|
+
id: '',
|
|
433
|
+
sessionId: '',
|
|
434
|
+
defer: () => {},
|
|
435
|
+
system: actor.system,
|
|
436
|
+
stopChild: () => {},
|
|
437
|
+
emit: () => {},
|
|
438
|
+
actionExecutor: () => {}
|
|
439
|
+
};
|
|
440
|
+
return emptyCanActorScope;
|
|
441
|
+
}
|
|
442
|
+
class StateMachine {
|
|
443
|
+
constructor(/** The raw config used to create the machine. */
|
|
444
|
+
config, sources, validator) {
|
|
445
|
+
this.config = config;
|
|
446
|
+
this.validator = validator;
|
|
447
|
+
/** The machine's own version. */
|
|
448
|
+
this.version = void 0;
|
|
449
|
+
this.schemas = void 0;
|
|
450
|
+
/** Standard Schema for snapshots persisted by this machine version. */
|
|
451
|
+
this.snapshotSchema = void 0;
|
|
452
|
+
/** Standard Schema for complete events accepted by this machine version. */
|
|
453
|
+
this.eventSchema = void 0;
|
|
454
|
+
this.sources = void 0;
|
|
455
|
+
/** Runtime options for machine execution. */
|
|
456
|
+
this.options = void 0;
|
|
457
|
+
/** @internal */
|
|
458
|
+
this.idMap = new Map();
|
|
459
|
+
this.root = void 0;
|
|
460
|
+
this.id = void 0;
|
|
461
|
+
this.states = void 0;
|
|
462
|
+
this.events = void 0;
|
|
463
|
+
this.internalEventDescriptors = void 0;
|
|
464
|
+
/** @internal Skips eventless-selection scans for machines without `always`. */
|
|
465
|
+
this._hasEventlessTransitions = void 0;
|
|
466
|
+
/**
|
|
467
|
+
* The original JSON definition this machine was created from (set by
|
|
468
|
+
* `createMachineFromConfig`), if any. Used by `serializeMachine` for lossless
|
|
469
|
+
* round-trips.
|
|
470
|
+
*
|
|
471
|
+
* @internal
|
|
472
|
+
*/
|
|
473
|
+
this._json = void 0;
|
|
474
|
+
this.id = config.id || '(machine)';
|
|
475
|
+
this.sources = {
|
|
476
|
+
actors: config.actors ?? {},
|
|
477
|
+
actions: config.actions ?? {},
|
|
478
|
+
delays: config.delays ?? {},
|
|
479
|
+
guards: config.guards ?? {},
|
|
480
|
+
...sources
|
|
481
|
+
};
|
|
482
|
+
this.version = this.config.version;
|
|
483
|
+
this.schemas = this.config.schemas;
|
|
484
|
+
this.snapshotSchema = {
|
|
485
|
+
'~standard': {
|
|
486
|
+
version: 1,
|
|
487
|
+
vendor: 'xstate',
|
|
488
|
+
validate: async value => {
|
|
489
|
+
if (value === null || typeof value !== 'object') {
|
|
490
|
+
return {
|
|
491
|
+
issues: [{
|
|
492
|
+
message: 'Expected a persisted snapshot.'
|
|
493
|
+
}]
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const snapshot = {
|
|
497
|
+
historyValue: {},
|
|
498
|
+
timers: {},
|
|
499
|
+
...value
|
|
500
|
+
};
|
|
501
|
+
const contextSchema = this.schemas?.context;
|
|
502
|
+
let context = snapshot.context;
|
|
503
|
+
if (contextSchema) {
|
|
504
|
+
const result = await contextSchema['~standard'].validate(context);
|
|
505
|
+
if (result.issues) {
|
|
506
|
+
return {
|
|
507
|
+
issues: [{
|
|
508
|
+
message: `Invalid context for machine '${this.id}' version '${this.version}': ${result.issues[0]?.message}`
|
|
509
|
+
}]
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
context = result.value;
|
|
513
|
+
}
|
|
514
|
+
for (const key of ['value', 'children']) {
|
|
515
|
+
if (!(key in snapshot)) {
|
|
516
|
+
return {
|
|
517
|
+
issues: [{
|
|
518
|
+
message: `Persisted snapshot is missing '${key}'.`
|
|
519
|
+
}]
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
if (!['active', 'done', 'error', 'stopped'].includes(snapshot.status)) {
|
|
524
|
+
return {
|
|
525
|
+
issues: [{
|
|
526
|
+
message: 'Persisted snapshot has invalid status.'
|
|
527
|
+
}]
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
for (const key of ['children', 'historyValue', 'timers']) {
|
|
531
|
+
if (snapshot[key] === null || typeof snapshot[key] !== 'object' || Array.isArray(snapshot[key])) {
|
|
532
|
+
return {
|
|
533
|
+
issues: [{
|
|
534
|
+
message: `Persisted snapshot has invalid '${key}'.`
|
|
535
|
+
}]
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
this.resolveState({
|
|
541
|
+
value: snapshot.value,
|
|
542
|
+
context
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
return {
|
|
546
|
+
issues: [{
|
|
547
|
+
message: error instanceof Error ? error.message : 'Persisted snapshot has invalid state value.'
|
|
548
|
+
}]
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
value: {
|
|
553
|
+
...snapshot,
|
|
554
|
+
context
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
this.eventSchema = {
|
|
561
|
+
'~standard': {
|
|
562
|
+
version: 1,
|
|
563
|
+
vendor: 'xstate',
|
|
564
|
+
validate: async value => {
|
|
565
|
+
if (value === null || typeof value !== 'object' || typeof value.type !== 'string') {
|
|
566
|
+
return {
|
|
567
|
+
issues: [{
|
|
568
|
+
message: 'Expected an event object.'
|
|
569
|
+
}]
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const event = value;
|
|
573
|
+
const eventSchemas = this.schemas?.events;
|
|
574
|
+
const isFrameworkEvent = event.type.startsWith('xstate.') || event.type.startsWith('@xstate.');
|
|
575
|
+
const schema = eventSchemas && Object.hasOwn(eventSchemas, event.type) ? eventSchemas[event.type] : undefined;
|
|
576
|
+
if (eventSchemas && !schema && !isFrameworkEvent) {
|
|
577
|
+
return {
|
|
578
|
+
issues: [{
|
|
579
|
+
message: `Unknown event '${event.type}' for machine '${this.id}' version '${this.version}'.`
|
|
580
|
+
}]
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (!schema) {
|
|
584
|
+
return {
|
|
585
|
+
value: event
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
const {
|
|
589
|
+
type,
|
|
590
|
+
...payload
|
|
591
|
+
} = event;
|
|
592
|
+
const result = await schema['~standard'].validate(payload);
|
|
593
|
+
if (result.issues) {
|
|
594
|
+
return result;
|
|
595
|
+
}
|
|
596
|
+
if (result.value === null || typeof result.value !== 'object') {
|
|
597
|
+
return {
|
|
598
|
+
issues: [{
|
|
599
|
+
message: 'Expected an event payload.'
|
|
600
|
+
}]
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
value: {
|
|
605
|
+
...result.value,
|
|
606
|
+
type
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
this.internalEventDescriptors = this.config.internalEvents ?? [];
|
|
613
|
+
this.options = {
|
|
614
|
+
maxIterations: Infinity,
|
|
615
|
+
...this.config.options
|
|
616
|
+
};
|
|
617
|
+
this.transition = this.transition.bind(this);
|
|
618
|
+
this.initialTransition = this.initialTransition.bind(this);
|
|
619
|
+
this.getInitialSnapshot = this.getInitialSnapshot.bind(this);
|
|
620
|
+
this.getPersistedSnapshot = this.getPersistedSnapshot.bind(this);
|
|
621
|
+
this.restoreSnapshot = this.restoreSnapshot.bind(this);
|
|
622
|
+
this.start = this.start.bind(this);
|
|
623
|
+
this.root = new StateNode(config, {
|
|
624
|
+
_key: this.id,
|
|
625
|
+
_machine: this
|
|
626
|
+
});
|
|
627
|
+
this.root._initialize();
|
|
628
|
+
createActor.formatRouteTransitions(this.root);
|
|
629
|
+
this.root._refreshEventMetadata();
|
|
630
|
+
this._hasEventlessTransitions = Array.from(this.idMap.values()).some(stateNode => !!stateNode.always?.length);
|
|
631
|
+
this.states = this.root.states; // TODO: remove!
|
|
632
|
+
this.events = this.root.events;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Clones this state machine with the provided sources.
|
|
637
|
+
*
|
|
638
|
+
* @param sources Options (`actions`, `guards`, `actors`, `delays`) to
|
|
639
|
+
* recursively merge with the existing options.
|
|
640
|
+
* @returns A new `StateMachine` instance with the provided sources.
|
|
641
|
+
*/
|
|
642
|
+
provide(sources) {
|
|
643
|
+
const {
|
|
644
|
+
actions,
|
|
645
|
+
guards,
|
|
646
|
+
actors,
|
|
647
|
+
delays
|
|
648
|
+
} = this.sources;
|
|
649
|
+
const provided = new StateMachine(this.config, {
|
|
650
|
+
actions: {
|
|
651
|
+
...actions,
|
|
652
|
+
...sources.actions
|
|
653
|
+
},
|
|
654
|
+
guards: {
|
|
655
|
+
...guards,
|
|
656
|
+
...sources.guards
|
|
657
|
+
},
|
|
658
|
+
actors: {
|
|
659
|
+
...actors,
|
|
660
|
+
...sources.actors
|
|
661
|
+
},
|
|
662
|
+
delays: {
|
|
663
|
+
...delays,
|
|
664
|
+
...sources.delays
|
|
665
|
+
}
|
|
666
|
+
}, this.validator);
|
|
667
|
+
// Providing sources does not change the serializable definition.
|
|
668
|
+
provided._json = this._json;
|
|
669
|
+
return provided;
|
|
670
|
+
}
|
|
671
|
+
resolveState(config) {
|
|
672
|
+
const resolvedStateValue = createActor.resolveStateValue(this.root, config.value);
|
|
673
|
+
const nodeSet = createActor.getAllStateNodes(createActor.getStateNodes(this.root, resolvedStateValue));
|
|
674
|
+
const nodes = [...nodeSet];
|
|
675
|
+
return createActor.createMachineSnapshot({
|
|
676
|
+
_nodes: nodes,
|
|
677
|
+
value: resolvedStateValue,
|
|
678
|
+
context: config.context || {},
|
|
679
|
+
children: {},
|
|
680
|
+
status: createActor.isInFinalState(nodeSet, this.root) ? 'done' : config.status || 'active',
|
|
681
|
+
output: config.output,
|
|
682
|
+
error: config.error,
|
|
683
|
+
historyValue: config.historyValue
|
|
684
|
+
}, this);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Determines the next snapshot given the current `snapshot` and received
|
|
689
|
+
* `event`. Calculates a full macrostep from all microsteps.
|
|
690
|
+
*
|
|
691
|
+
* @param snapshot The current snapshot
|
|
692
|
+
* @param event The received event
|
|
693
|
+
*/
|
|
694
|
+
transition(snapshot, event, actorScope) {
|
|
695
|
+
const usesInertScope = !actorScope;
|
|
696
|
+
const resolvedActorScope = actorScope ?? createActor.createInertActorScope(this, snapshot);
|
|
697
|
+
if (this.validator) {
|
|
698
|
+
dist_xstateActors.assertValid(this.validator, {
|
|
699
|
+
kind: 'event',
|
|
700
|
+
logic: this,
|
|
701
|
+
event,
|
|
702
|
+
eventOrigin: actorScope && actorScope.self._lastSourceRef ? 'actor' : 'external'
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
if (usesInertScope) {
|
|
706
|
+
createActor.setInertActorScopeSnapshot(resolvedActorScope, snapshot, false);
|
|
707
|
+
}
|
|
708
|
+
createActor.beginSpawnAllocation(resolvedActorScope);
|
|
709
|
+
const fastSnapshot = this._transitionFast(snapshot, event, resolvedActorScope);
|
|
710
|
+
if (fastSnapshot) {
|
|
711
|
+
if (usesInertScope) {
|
|
712
|
+
createActor.setInertActorScopeSnapshot(resolvedActorScope, fastSnapshot, false);
|
|
713
|
+
}
|
|
714
|
+
const returnedSnapshot = usesInertScope && fastSnapshot !== snapshot ? createActor.attachSnapshotActorRef(resolvedActorScope, fastSnapshot) : this._attachPureActorRef(fastSnapshot, resolvedActorScope);
|
|
715
|
+
if (this.validator) {
|
|
716
|
+
dist_xstateActors.assertValid(this.validator, {
|
|
717
|
+
kind: 'result',
|
|
718
|
+
logic: this,
|
|
719
|
+
snapshot: returnedSnapshot,
|
|
720
|
+
effects: []
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return [returnedSnapshot, []];
|
|
724
|
+
}
|
|
725
|
+
const {
|
|
726
|
+
snapshot: nextSnapshot,
|
|
727
|
+
microsteps
|
|
728
|
+
} = createActor.macrostep(snapshot, event, resolvedActorScope, []);
|
|
729
|
+
if (usesInertScope) {
|
|
730
|
+
createActor.setInertActorScopeSnapshot(resolvedActorScope, nextSnapshot, false);
|
|
731
|
+
}
|
|
732
|
+
const returnedSnapshot = usesInertScope ? nextSnapshot === snapshot ? nextSnapshot : createActor.attachSnapshotActorRef(resolvedActorScope, nextSnapshot) : this._attachPureActorRef(nextSnapshot, resolvedActorScope);
|
|
733
|
+
const effects = this._collectEffects(microsteps);
|
|
734
|
+
if (this.validator) {
|
|
735
|
+
dist_xstateActors.assertValid(this.validator, {
|
|
736
|
+
kind: 'result',
|
|
737
|
+
logic: this,
|
|
738
|
+
snapshot: returnedSnapshot,
|
|
739
|
+
effects
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
return [returnedSnapshot, effects];
|
|
743
|
+
}
|
|
744
|
+
_collectEffects(microsteps) {
|
|
745
|
+
return microsteps.flatMap(([, actions]) => actions);
|
|
746
|
+
}
|
|
747
|
+
_attachPureActorRef(snapshot, actorScope, skipInitializingActor = false) {
|
|
748
|
+
if (createActor.isInertActorScope(actorScope)) {
|
|
749
|
+
return snapshot;
|
|
750
|
+
}
|
|
751
|
+
if (skipInitializingActor && actorScope.self._actorScope === actorScope && actorScope.self._snapshot === undefined) {
|
|
752
|
+
return snapshot;
|
|
753
|
+
}
|
|
754
|
+
createActor.setSnapshotActorRef(snapshot, actorScope.self, actorScope.system);
|
|
755
|
+
return snapshot;
|
|
756
|
+
}
|
|
757
|
+
_transitionFast(snapshot, event, actorScope) {
|
|
758
|
+
if (snapshot.status !== 'active' || typeof snapshot.value !== 'string' || this.root.always?.length) {
|
|
759
|
+
return undefined;
|
|
760
|
+
}
|
|
761
|
+
const sourceNode = this.root.states[snapshot.value];
|
|
762
|
+
if (!sourceNode || sourceNode.type !== 'atomic' || sourceNode.exit || sourceNode.invoke.length || sourceNode.always?.length || sourceNode.after?.length) {
|
|
763
|
+
return undefined;
|
|
764
|
+
}
|
|
765
|
+
const transitions = sourceNode.transitions.get(event.type);
|
|
766
|
+
if (transitions?.length !== 1) {
|
|
767
|
+
return undefined;
|
|
768
|
+
}
|
|
769
|
+
const selected = transitions[0];
|
|
770
|
+
if (selected.guard || selected.actions || selected.to || selected.reenter || selected.input || typeof selected.context === 'function' || selected.target && selected.target.length !== 1) {
|
|
771
|
+
return undefined;
|
|
772
|
+
}
|
|
773
|
+
const targetNode = selected.target?.[0] ?? sourceNode;
|
|
774
|
+
const stateChanged = targetNode !== sourceNode;
|
|
775
|
+
if (targetNode.parent !== this.root || targetNode.type !== 'atomic' || stateChanged && (targetNode.entry || targetNode.invoke.length || targetNode.always?.length || targetNode.after?.length)) {
|
|
776
|
+
return undefined;
|
|
777
|
+
}
|
|
778
|
+
const context = selected.context !== undefined ? {
|
|
779
|
+
...snapshot.context,
|
|
780
|
+
...selected.context
|
|
781
|
+
} : snapshot.context;
|
|
782
|
+
if (!createActor.isInertActorScope(actorScope) && (actorScope.system._hasInspectionObservers?.() ?? true)) {
|
|
783
|
+
const collectedMicrosteps = actorScope.self._collectedMicrosteps || [];
|
|
784
|
+
collectedMicrosteps.push(selected);
|
|
785
|
+
actorScope.self._collectedMicrosteps = collectedMicrosteps;
|
|
786
|
+
}
|
|
787
|
+
return createActor.cloneMachineSnapshot(snapshot, {
|
|
788
|
+
...(context !== snapshot.context ? {
|
|
789
|
+
context
|
|
790
|
+
} : {}),
|
|
791
|
+
...(stateChanged ? {
|
|
792
|
+
_nodes: [this.root, targetNode]
|
|
793
|
+
} : {})
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Determines the next state given the current `state` and `event`. Calculates
|
|
799
|
+
* a microstep.
|
|
800
|
+
*
|
|
801
|
+
* @param state The current state
|
|
802
|
+
* @param event The received event
|
|
803
|
+
*/
|
|
804
|
+
microstep(snapshot, event, actorScope) {
|
|
805
|
+
const {
|
|
806
|
+
microsteps
|
|
807
|
+
} = createActor.macrostep(snapshot, event, actorScope, []);
|
|
808
|
+
const snapshots = new Array(microsteps.length);
|
|
809
|
+
for (let i = 0; i < microsteps.length; i++) {
|
|
810
|
+
snapshots[i] = microsteps[i][0];
|
|
811
|
+
}
|
|
812
|
+
return snapshots;
|
|
813
|
+
}
|
|
814
|
+
getTransitionData(snapshot, event, actorScope, selectionResults) {
|
|
815
|
+
return createActor.transitionNode(this.root, snapshot.value, snapshot, event, actorScope, selectionResults) || [];
|
|
816
|
+
}
|
|
817
|
+
isInternalEventType(eventType) {
|
|
818
|
+
if (eventType === createActor.XSTATE_TIMER) {
|
|
819
|
+
return true;
|
|
820
|
+
}
|
|
821
|
+
for (const descriptor of this.internalEventDescriptors) {
|
|
822
|
+
if (createActor.matchesEventDescriptor(eventType, descriptor)) {
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return false;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Determines whether sending the `event` to the given snapshot would select a
|
|
831
|
+
* non-forbidden transition. Backs `snapshot.can(...)`; lives here so that
|
|
832
|
+
* non-machine bundles don't pay for the transition-resolution machinery.
|
|
833
|
+
*
|
|
834
|
+
* @internal
|
|
835
|
+
*/
|
|
836
|
+
_canTransition(snapshot, event) {
|
|
837
|
+
const emptyActorScope = getEmptyCanActorScope();
|
|
838
|
+
const transitionData = this.getTransitionData(snapshot, event, emptyActorScope);
|
|
839
|
+
if (!transitionData?.length) {
|
|
840
|
+
return false;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Check that at least one transition is not forbidden
|
|
844
|
+
for (const transition of transitionData) {
|
|
845
|
+
if (transition.target !== undefined) {
|
|
846
|
+
return true;
|
|
847
|
+
}
|
|
848
|
+
const res = createActor.getTransitionResult(transition, snapshot, event, emptyActorScope, {
|
|
849
|
+
resolveActions: false
|
|
850
|
+
});
|
|
851
|
+
if (res.targets?.length || res.context || createActor.hasEffect(transition, snapshot.context, event, snapshot, emptyActorScope)) {
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/**
|
|
859
|
+
* Returns the error event that the actor should transition with to recover
|
|
860
|
+
* from an execution error, if any active state node declares `onError`.
|
|
861
|
+
*
|
|
862
|
+
* @internal
|
|
863
|
+
*/
|
|
864
|
+
getExecutionErrorEvent(snapshot, error) {
|
|
865
|
+
if (snapshot?.status !== 'active' || !snapshot.nodes?.some(stateNode => stateNode.config.onError)) {
|
|
866
|
+
return undefined;
|
|
867
|
+
}
|
|
868
|
+
return createActor.createErrorPlatformEvent('execution', error);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* The initial state _before_ evaluating any microsteps. This "pre-initial"
|
|
873
|
+
* state is provided to initial actions executed in the initial state.
|
|
874
|
+
*
|
|
875
|
+
* @internal
|
|
876
|
+
*/
|
|
877
|
+
_getPreInitialState(actorScope, initEvent) {
|
|
878
|
+
const {
|
|
879
|
+
context
|
|
880
|
+
} = this.config;
|
|
881
|
+
const preInitial = createActor.createMachineSnapshot({
|
|
882
|
+
context: typeof context !== 'function' && context ? context : {},
|
|
883
|
+
_nodes: [this.root],
|
|
884
|
+
children: {},
|
|
885
|
+
status: 'active'
|
|
886
|
+
}, this);
|
|
887
|
+
if (typeof context === 'function') {
|
|
888
|
+
const children = {};
|
|
889
|
+
const spawn = createSpawner(actorScope, this.sources.actors, children);
|
|
890
|
+
const resolvedContext = context(createActor.withActorSelf({
|
|
891
|
+
spawn,
|
|
892
|
+
input: initEvent.input,
|
|
893
|
+
actors: this.sources.actors
|
|
894
|
+
}, actorScope));
|
|
895
|
+
const [nextState] = createActor.resolveActionsWithContext(preInitial, initEvent, actorScope, []);
|
|
896
|
+
if (resolvedContext) {
|
|
897
|
+
nextState.context = resolvedContext;
|
|
898
|
+
}
|
|
899
|
+
if (Object.keys(children).length > 0) {
|
|
900
|
+
nextState.children = {
|
|
901
|
+
...nextState.children,
|
|
902
|
+
...children
|
|
903
|
+
};
|
|
904
|
+
// Commit the transaction counters so context-factory allocations
|
|
905
|
+
// persist with the snapshot: a freed id is never handed out again
|
|
906
|
+
// after a restore or in a fresh replay process. (The spawner already
|
|
907
|
+
// registered each child for string-id resolution.)
|
|
908
|
+
const counters = createActor.takeSpawnAllocationCounters(actorScope);
|
|
909
|
+
if (counters) {
|
|
910
|
+
nextState._nextActorIds = createActor.mergeActorIdCounters(nextState._nextActorIds, counters);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
return nextState;
|
|
914
|
+
}
|
|
915
|
+
return preInitial;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Returns the initial `State` instance, with reference to `self` as an
|
|
920
|
+
* `ActorRef`.
|
|
921
|
+
*/
|
|
922
|
+
getInitialSnapshot(actorScope, input) {
|
|
923
|
+
return this.initialTransition(input, actorScope)[0];
|
|
924
|
+
}
|
|
925
|
+
initialTransition(input, actorScope) {
|
|
926
|
+
if (this.validator) {
|
|
927
|
+
dist_xstateActors.assertValid(this.validator, {
|
|
928
|
+
kind: 'input',
|
|
929
|
+
logic: this,
|
|
930
|
+
input
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
const usesInertScope = !actorScope;
|
|
934
|
+
const resolvedActorScope = actorScope ?? createActor.createInertActorScope(this);
|
|
935
|
+
createActor.beginSpawnAllocation(resolvedActorScope);
|
|
936
|
+
const initEvent = createActor.createInitEvent(input); // TODO: fix;
|
|
937
|
+
const internalQueue = [];
|
|
938
|
+
const preInitialState = this._getPreInitialState(resolvedActorScope, initEvent);
|
|
939
|
+
const contextSpawnEffects = Object.values(preInitialState.children).filter(Boolean).map(actor => createActor.createSpawnEffect(actor));
|
|
940
|
+
const finalizeInitialResult = (macroState, microsteps) => {
|
|
941
|
+
if (usesInertScope) {
|
|
942
|
+
createActor.setInertActorScopeSnapshot(resolvedActorScope, macroState, false);
|
|
943
|
+
}
|
|
944
|
+
const returnedSnapshot = usesInertScope ? createActor.attachSnapshotActorRef(resolvedActorScope, macroState) : this._attachPureActorRef(macroState, resolvedActorScope, true);
|
|
945
|
+
const effects = this._collectEffects(microsteps);
|
|
946
|
+
if (this.validator) {
|
|
947
|
+
dist_xstateActors.assertValid(this.validator, {
|
|
948
|
+
kind: 'result',
|
|
949
|
+
logic: this,
|
|
950
|
+
snapshot: returnedSnapshot,
|
|
951
|
+
effects
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
return [returnedSnapshot, effects];
|
|
955
|
+
};
|
|
956
|
+
try {
|
|
957
|
+
const [nextState, initialActions] = createActor.initialMicrostep(this.root, preInitialState, resolvedActorScope, initEvent, internalQueue);
|
|
958
|
+
const {
|
|
959
|
+
snapshot: macroState,
|
|
960
|
+
microsteps
|
|
961
|
+
} = createActor.macrostep(nextState, initEvent, resolvedActorScope, internalQueue, [[nextState, [...contextSpawnEffects, ...initialActions]]]);
|
|
962
|
+
return finalizeInitialResult(macroState, microsteps);
|
|
963
|
+
} catch (err) {
|
|
964
|
+
if (!this.root.config.onError) {
|
|
965
|
+
throw err;
|
|
966
|
+
}
|
|
967
|
+
const errorEvent = createActor.createErrorPlatformEvent('execution', err);
|
|
968
|
+
const errorMacrostep = createActor.macrostep(preInitialState, errorEvent, resolvedActorScope, [], [[preInitialState, contextSpawnEffects]]);
|
|
969
|
+
return finalizeInitialResult(errorMacrostep.snapshot, errorMacrostep.microsteps);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
start(snapshot) {
|
|
973
|
+
// Start rehydrated children that were active when persisted. Freshly
|
|
974
|
+
// invoked/spawned children are NOT started here — they start via deferred
|
|
975
|
+
// `@xstate.start` actions so sync start errors route to `onError`.
|
|
976
|
+
if (!snapshot?.children) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const children = snapshot.children;
|
|
980
|
+
for (const childId in children) {
|
|
981
|
+
if (!Object.hasOwn(children, childId)) {
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
const child = children[childId];
|
|
985
|
+
if (child._rehydrated && child.getSnapshot?.().status === 'active') {
|
|
986
|
+
child.start();
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
getStateNodeById(stateId) {
|
|
991
|
+
const fullPath = createActor.toStatePath(stateId);
|
|
992
|
+
const relativePath = fullPath.slice(1);
|
|
993
|
+
const resolvedStateId = createActor.isStateId(fullPath[0]) ? fullPath[0].slice(STATE_IDENTIFIER.length) : fullPath[0];
|
|
994
|
+
const stateNode = this.idMap.get(resolvedStateId);
|
|
995
|
+
if (!stateNode) {
|
|
996
|
+
throw new Error(`Child state node '#${resolvedStateId}' does not exist on machine '${this.id}'`);
|
|
997
|
+
}
|
|
998
|
+
return createActor.getStateNodeByPath(stateNode, relativePath);
|
|
999
|
+
}
|
|
1000
|
+
getPersistedSnapshot(snapshot, options) {
|
|
1001
|
+
return createActor.getPersistedSnapshot(snapshot, options);
|
|
1002
|
+
}
|
|
1003
|
+
restoreSnapshot(snapshot, actorScope) {
|
|
1004
|
+
const usesInertScope = !actorScope;
|
|
1005
|
+
const resolvedActorScope = actorScope ?? createActor.createInertActorScope(this);
|
|
1006
|
+
const persistedMachine = snapshot.machine;
|
|
1007
|
+
const legacyPersistedVersion = snapshot.version;
|
|
1008
|
+
const persistedVersion = typeof persistedMachine?.version === 'string' ? persistedMachine.version : legacyPersistedVersion;
|
|
1009
|
+
if (legacyPersistedVersion !== undefined && persistedMachine?.version !== undefined && persistedMachine.version !== legacyPersistedVersion) {
|
|
1010
|
+
throw new Error(`Persisted snapshot version '${legacyPersistedVersion}' conflicts with machine version '${persistedMachine.version}'.`);
|
|
1011
|
+
}
|
|
1012
|
+
if (persistedMachine && typeof persistedMachine.id === 'string' && persistedMachine.id !== this.id) {
|
|
1013
|
+
throw new Error(`Machine ID mismatch: persisted snapshot machine '${persistedMachine.id}' does not match '${this.id}'.`);
|
|
1014
|
+
}
|
|
1015
|
+
if (persistedVersion !== this.version) {
|
|
1016
|
+
const migrate = this.config.migrate;
|
|
1017
|
+
if (typeof migrate !== 'function') {
|
|
1018
|
+
throw new Error(`Persisted snapshot version '${persistedVersion}' does not match machine version '${this.version}'.`);
|
|
1019
|
+
}
|
|
1020
|
+
snapshot = migrate(snapshot, persistedVersion);
|
|
1021
|
+
}
|
|
1022
|
+
const snapshotData = snapshot;
|
|
1023
|
+
const children = {};
|
|
1024
|
+
const snapshotChildren = snapshotData.children;
|
|
1025
|
+
for (const actorId of Object.keys(snapshotChildren)) {
|
|
1026
|
+
const actorData = snapshotChildren[actorId];
|
|
1027
|
+
if (actorData.remote === true && actorData.address !== undefined) {
|
|
1028
|
+
if (typeof actorData.src !== 'string') {
|
|
1029
|
+
// Fail loudly instead of fabricating a source key that hosts would
|
|
1030
|
+
// route by.
|
|
1031
|
+
throw new Error(`Unable to restore remote child '${actorId}': a child referenced by address requires a registered source key.`);
|
|
1032
|
+
}
|
|
1033
|
+
// The child's state lives with another runtime; restore a
|
|
1034
|
+
// location-transparent handle constructed from its identity alone.
|
|
1035
|
+
// The handle keeps its persisted address verbatim: the owning
|
|
1036
|
+
// runtime's identity for the child wins over the local parent chain.
|
|
1037
|
+
const handle = createActor.createRemoteActorRef(resolvedActorScope.system, {
|
|
1038
|
+
id: actorId,
|
|
1039
|
+
address: actorData.address,
|
|
1040
|
+
src: actorData.src,
|
|
1041
|
+
parent: resolvedActorScope.self,
|
|
1042
|
+
registryKey: actorData.registryKey,
|
|
1043
|
+
syncSnapshot: actorData.syncSnapshot,
|
|
1044
|
+
incarnation: actorData.incarnation
|
|
1045
|
+
});
|
|
1046
|
+
if (actorData.registryKey) {
|
|
1047
|
+
resolvedActorScope.system._set(actorData.registryKey, handle);
|
|
1048
|
+
}
|
|
1049
|
+
children[actorId] = handle;
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
const childState = actorData.snapshot;
|
|
1053
|
+
const src = actorData.src;
|
|
1054
|
+
const logic = typeof src === 'string' ? createActor.resolveReferencedActor(this, src) : src;
|
|
1055
|
+
if (!logic) {
|
|
1056
|
+
const sourceId = typeof src === 'string' ? src : '<unknown>';
|
|
1057
|
+
throw new Error(`Unable to restore child actor '${actorId}': child source '${sourceId}' is not provided in machine '${this.id}'.`);
|
|
1058
|
+
}
|
|
1059
|
+
const actor = resolvedActorScope.system.createActorRef(logic, {
|
|
1060
|
+
id: actorId,
|
|
1061
|
+
parent: resolvedActorScope.self,
|
|
1062
|
+
syncSnapshot: actorData.syncSnapshot,
|
|
1063
|
+
snapshot: childState,
|
|
1064
|
+
src,
|
|
1065
|
+
registryKey: actorData.registryKey
|
|
1066
|
+
});
|
|
1067
|
+
// Mark so `start()` knows to start this child (freshly invoked/spawned
|
|
1068
|
+
// children are started via deferred `@xstate.start` actions instead).
|
|
1069
|
+
actor._rehydrated = true;
|
|
1070
|
+
children[actorId] = actor;
|
|
1071
|
+
}
|
|
1072
|
+
const timers = {};
|
|
1073
|
+
const persistedTimers = snapshotData.timers ?? {};
|
|
1074
|
+
for (const [id, timer] of Object.entries(persistedTimers)) {
|
|
1075
|
+
let event = timer.event;
|
|
1076
|
+
if (event.type === 'xstate.timeout.actor') {
|
|
1077
|
+
const actorId = event.actorId;
|
|
1078
|
+
const child = children[actorId];
|
|
1079
|
+
if (child) {
|
|
1080
|
+
event = createActor.createInvokeTimeoutEvent(actorId, child.sessionId);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
const target = typeof timer.target === 'string' ? timer.target === 'self' ? 'self' : children[timer.target] : resolvedActorScope.self._parent;
|
|
1084
|
+
if (!target) {
|
|
1085
|
+
const targetDescription = typeof timer.target === 'string' ? timer.target : timer.target.type;
|
|
1086
|
+
throw new Error(`Unable to restore timer '${id}': target actor '${targetDescription}' is unavailable.`);
|
|
1087
|
+
}
|
|
1088
|
+
timers[id] = {
|
|
1089
|
+
...timer,
|
|
1090
|
+
event,
|
|
1091
|
+
target
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
const reviveHistoryValue = historyValue => {
|
|
1095
|
+
if (!historyValue || typeof historyValue !== 'object') {
|
|
1096
|
+
return {};
|
|
1097
|
+
}
|
|
1098
|
+
const revived = {};
|
|
1099
|
+
for (const key of Object.keys(historyValue)) {
|
|
1100
|
+
const arr = historyValue[key];
|
|
1101
|
+
for (const item of arr) {
|
|
1102
|
+
let resolved;
|
|
1103
|
+
if (item instanceof StateNode) {
|
|
1104
|
+
resolved = item;
|
|
1105
|
+
} else {
|
|
1106
|
+
try {
|
|
1107
|
+
resolved = this.root.machine.getStateNodeById(item.id);
|
|
1108
|
+
} catch {
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (!resolved) {
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
revived[key] ??= [];
|
|
1115
|
+
revived[key].push(resolved);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return revived;
|
|
1119
|
+
};
|
|
1120
|
+
const revivedHistoryValue = reviveHistoryValue(snapshotData.historyValue);
|
|
1121
|
+
const validateStateValue = (stateValue, node, path) => {
|
|
1122
|
+
const missingStateError = statePath => new Error(`Persisted snapshot references state '${statePath.join('.')}' which does not exist on machine '${this.id}'.`);
|
|
1123
|
+
if (typeof stateValue === 'string') {
|
|
1124
|
+
if (!node.states[stateValue]) {
|
|
1125
|
+
throw missingStateError(path.concat(stateValue));
|
|
1126
|
+
}
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (!stateValue || typeof stateValue !== 'object') {
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
for (const key of Object.keys(stateValue)) {
|
|
1133
|
+
const childNode = node.states[key];
|
|
1134
|
+
if (!childNode) {
|
|
1135
|
+
throw missingStateError(path.concat(key));
|
|
1136
|
+
}
|
|
1137
|
+
validateStateValue(stateValue[key], childNode, path.concat(key));
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
validateStateValue(snapshotData.value, this.root, []);
|
|
1141
|
+
const nodes = Array.from(createActor.getAllStateNodes(createActor.getStateNodes(this.root, snapshotData.value)));
|
|
1142
|
+
const {
|
|
1143
|
+
version: _persistedSnapshotVersion,
|
|
1144
|
+
// The legacy system-wide counter: superseded by per-snapshot
|
|
1145
|
+
// `_nextActorIds` plus the child-id fold below; dropped so old and new
|
|
1146
|
+
// snapshots round-trip to the same shape.
|
|
1147
|
+
_nextActorId: _legacyNextActorId,
|
|
1148
|
+
...persistedRest
|
|
1149
|
+
} = snapshot;
|
|
1150
|
+
// Fold generated-shaped child ids into the counters as a floor: snapshots
|
|
1151
|
+
// persisted before per-actor counters (or hand-crafted ones) still must
|
|
1152
|
+
// never reuse a live child's id.
|
|
1153
|
+
let restoredCounters = persistedRest._nextActorIds;
|
|
1154
|
+
for (const childId of Object.keys(snapshotChildren)) {
|
|
1155
|
+
const generated = createActor.parseGeneratedActorId(childId);
|
|
1156
|
+
if (generated && (restoredCounters?.[generated.prefix] ?? 0) <= generated.index) {
|
|
1157
|
+
restoredCounters = {
|
|
1158
|
+
...restoredCounters,
|
|
1159
|
+
[generated.prefix]: generated.index + 1
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const restoredSnapshot = createActor.createMachineSnapshot({
|
|
1164
|
+
...persistedRest,
|
|
1165
|
+
children,
|
|
1166
|
+
timers,
|
|
1167
|
+
_nextActorIds: restoredCounters,
|
|
1168
|
+
_nodes: nodes,
|
|
1169
|
+
value: snapshotData.value,
|
|
1170
|
+
historyValue: revivedHistoryValue,
|
|
1171
|
+
_stateInputs: snapshotData.stateInputs ?? {}
|
|
1172
|
+
}, this, resolvedActorScope.self);
|
|
1173
|
+
const seen = new WeakSet();
|
|
1174
|
+
function reviveContext(contextPart) {
|
|
1175
|
+
if (seen.has(contextPart)) {
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
seen.add(contextPart);
|
|
1179
|
+
for (const key of Object.keys(contextPart)) {
|
|
1180
|
+
const value = contextPart[key];
|
|
1181
|
+
if (value && typeof value === 'object') {
|
|
1182
|
+
if ('xstate$type' in value && value.xstate$type === createActor.ACTOR_REF_TYPE) {
|
|
1183
|
+
contextPart[key] = children[value.id];
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
reviveContext(value);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
reviveContext(restoredSnapshot.context);
|
|
1191
|
+
if (usesInertScope) {
|
|
1192
|
+
createActor.setInertActorScopeSnapshot(resolvedActorScope, restoredSnapshot, false);
|
|
1193
|
+
return createActor.attachSnapshotActorRef(resolvedActorScope, restoredSnapshot);
|
|
1194
|
+
}
|
|
1195
|
+
return this._attachPureActorRef(restoredSnapshot, resolvedActorScope);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
exports.StateMachine = StateMachine;
|
|
1200
|
+
exports.StateNode = StateNode;
|