xstate 6.0.0-alpha.47 → 6.0.0-alpha.49
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 +30 -1
- package/dist/{StateMachine-7046f677.cjs.js → StateMachine-0adb3a70.cjs.js} +15 -12
- package/dist/{StateMachine-70c06bdd.development.cjs.js → StateMachine-29450e6d.development.cjs.js} +15 -12
- package/dist/{StateMachine-74929087.esm.js → StateMachine-2af31c11.esm.js} +10 -7
- package/dist/{StateMachine-ee10f67e.development.esm.js → StateMachine-faf50c5c.development.esm.js} +10 -7
- package/dist/{createActor-915ea40c.esm.js → createActor-268165d4.esm.js} +79 -23
- package/dist/{createActor-ff132afa.development.esm.js → createActor-88709f74.development.esm.js} +79 -23
- package/dist/{createActor-6736cbbe.development.cjs.js → createActor-a1e0dffb.development.cjs.js} +78 -25
- package/dist/{createActor-bfe8b6bd.cjs.js → createActor-b4dd05ae.cjs.js} +78 -25
- package/dist/declarations/src/createMachineFromConfig.d.ts +5 -77
- package/dist/declarations/src/fsm/createFSMActor.d.ts +67 -0
- package/dist/declarations/src/fsm/index.d.ts +2 -0
- package/dist/declarations/src/fsm.d.ts +5 -4
- package/dist/declarations/src/scxml/index.d.ts +1 -0
- package/dist/declarations/src/scxml/runtime.d.ts +251 -0
- package/dist/declarations/src/scxml/scxml.d.ts +19 -0
- package/dist/declarations/src/types.d.ts +1 -0
- package/dist/{effectDescriptor-8be60443.esm.js → effectDescriptor-30c9a744.esm.js} +1 -1
- package/dist/{effectDescriptor-fbe67d5a.development.esm.js → effectDescriptor-39d22100.development.esm.js} +1 -1
- package/dist/{effectDescriptor-3c699328.development.cjs.js → effectDescriptor-7ff6802f.development.cjs.js} +1 -1
- package/dist/{effectDescriptor-a7e2d4d1.cjs.js → effectDescriptor-be348e50.cjs.js} +1 -1
- package/dist/fsm-7a2e9697.cjs.js +777 -0
- package/dist/fsm-b804b3e9.esm.js +775 -0
- package/dist/fsm-c1316948.development.cjs.js +777 -0
- package/dist/fsm-e18a7cb4.development.esm.js +775 -0
- package/dist/types-3c3455f7.esm.js +185 -0
- package/dist/types-adda3842.development.esm.js +185 -0
- package/dist/types-b3fea5bd.development.cjs.js +189 -0
- package/dist/types-fbabaeee.cjs.js +189 -0
- package/dist/validation-05f1d517.esm.js +8 -0
- package/dist/validation-4561b43c.cjs.js +10 -0
- package/dist/validation-e69470ad.development.esm.js +8 -0
- package/dist/validation-f2714a35.development.cjs.js +10 -0
- package/dist/xstate-actors.cjs.js +934 -9
- package/dist/xstate-actors.development.cjs.js +934 -9
- package/dist/xstate-actors.development.esm.js +932 -2
- package/dist/xstate-actors.esm.js +932 -2
- package/dist/xstate-durable.cjs.js +2 -2
- package/dist/xstate-durable.development.cjs.js +2 -2
- package/dist/xstate-durable.development.esm.js +2 -2
- package/dist/xstate-durable.esm.js +2 -2
- package/dist/xstate-fsm.cjs.d.mts +2 -0
- package/dist/xstate-fsm.cjs.d.ts +2 -0
- package/dist/xstate-fsm.cjs.js +409 -0
- package/dist/xstate-fsm.cjs.mjs +5 -0
- package/dist/xstate-fsm.development.cjs.js +409 -0
- package/dist/xstate-fsm.development.cjs.mjs +5 -0
- package/dist/xstate-fsm.development.esm.js +403 -0
- package/dist/xstate-fsm.esm.js +403 -0
- package/dist/xstate-fsm.umd.min.js +2 -0
- package/dist/xstate-fsm.umd.min.js.map +1 -0
- package/dist/xstate-graph.cjs.js +4 -3
- package/dist/xstate-graph.development.cjs.js +4 -3
- package/dist/xstate-graph.development.esm.js +4 -3
- package/dist/xstate-graph.esm.js +4 -3
- package/dist/xstate-graph.umd.min.js +1 -1
- package/dist/xstate-graph.umd.min.js.map +1 -1
- package/dist/xstate-scxml.cjs.d.mts +2 -0
- package/dist/xstate-scxml.cjs.d.ts +2 -0
- package/dist/xstate-scxml.cjs.js +2387 -0
- package/dist/xstate-scxml.cjs.mjs +3 -0
- package/dist/xstate-scxml.development.cjs.js +2387 -0
- package/dist/xstate-scxml.development.cjs.mjs +3 -0
- package/dist/xstate-scxml.development.esm.js +2383 -0
- package/dist/xstate-scxml.esm.js +2383 -0
- package/dist/xstate-scxml.umd.min.js +26 -0
- package/dist/xstate-scxml.umd.min.js.map +1 -0
- package/dist/xstate-validation.cjs.js +1 -1
- package/dist/xstate-validation.development.cjs.js +1 -1
- package/dist/xstate-validation.development.esm.js +1 -1
- package/dist/xstate-validation.esm.js +1 -1
- package/dist/xstate.cjs.js +143 -1412
- package/dist/xstate.development.cjs.js +143 -1412
- package/dist/xstate.development.esm.js +143 -1411
- package/dist/xstate.esm.js +143 -1411
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/fsm/package.json +8 -0
- package/package.json +37 -3
- package/scxml/package.json +8 -0
- package/dist/index-0f498ef2.development.cjs.js +0 -946
- package/dist/index-70d56425.development.esm.js +0 -937
- package/dist/index-b051c924.esm.js +0 -937
- package/dist/index-b3fe59bc.cjs.js +0 -946
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var createActor = require('./createActor-a1e0dffb.development.cjs.js');
|
|
4
|
+
|
|
5
|
+
const allocations = new WeakMap();
|
|
6
|
+
function beginFSMEffects(scope, snapshot) {
|
|
7
|
+
allocations.set(scope, {
|
|
8
|
+
ids: new Set(Object.keys(snapshot.children)),
|
|
9
|
+
counters: {
|
|
10
|
+
...snapshot._nextActorIds
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function execute(runtime = this.source?.system ?? this.actor?.system) {
|
|
15
|
+
switch (this.type) {
|
|
16
|
+
case '@xstate.spawn':
|
|
17
|
+
return runtime.spawnActor(this.source, this.actor);
|
|
18
|
+
case '@xstate.start':
|
|
19
|
+
return runtime.startActor(this.actor);
|
|
20
|
+
case '@xstate.stop':
|
|
21
|
+
return runtime.stopActor(this.actor);
|
|
22
|
+
case '@xstate.terminate':
|
|
23
|
+
return runtime.terminateActor(this.actor, this);
|
|
24
|
+
case '@xstate.raise':
|
|
25
|
+
case '@xstate.sendTo':
|
|
26
|
+
{
|
|
27
|
+
if (this.delay === undefined) {
|
|
28
|
+
return runtime.sendEvent(this.source, this.target, this.event);
|
|
29
|
+
}
|
|
30
|
+
const timer = this.source.getSnapshot().timers[this.id];
|
|
31
|
+
if (timer && !runtime._clock.now) {
|
|
32
|
+
timer.startedAt = Date.now();
|
|
33
|
+
}
|
|
34
|
+
return runtime.scheduleTimer(this.source, this.id, this.delay);
|
|
35
|
+
}
|
|
36
|
+
case '@xstate.cancel':
|
|
37
|
+
return runtime.cancelTimer(this.source, this.id);
|
|
38
|
+
case '@xstate.emit':
|
|
39
|
+
return runtime.emitEvent(this.source, this.event);
|
|
40
|
+
default:
|
|
41
|
+
return this.action?.(...this.args);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function effect(type, fields = {}) {
|
|
45
|
+
return {
|
|
46
|
+
kind: 'builtin',
|
|
47
|
+
type,
|
|
48
|
+
params: undefined,
|
|
49
|
+
args: [],
|
|
50
|
+
exec: execute,
|
|
51
|
+
...fields
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function createFSMEnqueue(scope, effects, internalEvents) {
|
|
55
|
+
const enqueue = (action, ...args) => {
|
|
56
|
+
effects.push(effect(action.name || '(anonymous)', {
|
|
57
|
+
kind: 'action',
|
|
58
|
+
action,
|
|
59
|
+
args
|
|
60
|
+
}));
|
|
61
|
+
};
|
|
62
|
+
enqueue.cancel = id => {
|
|
63
|
+
effects.push(effect('@xstate.cancel', {
|
|
64
|
+
source: scope.self,
|
|
65
|
+
id
|
|
66
|
+
}));
|
|
67
|
+
};
|
|
68
|
+
enqueue.emit = event => {
|
|
69
|
+
effects.push(effect('@xstate.emit', {
|
|
70
|
+
kind: 'emit',
|
|
71
|
+
type: '@xstate.emit',
|
|
72
|
+
source: scope.self,
|
|
73
|
+
event
|
|
74
|
+
}));
|
|
75
|
+
};
|
|
76
|
+
enqueue.log = (...args) => {
|
|
77
|
+
enqueue(scope.logger, ...args);
|
|
78
|
+
};
|
|
79
|
+
enqueue.raise = (event, options) => {
|
|
80
|
+
if (typeof event === 'string') {
|
|
81
|
+
throw new Error('Only event objects may be used with raise');
|
|
82
|
+
}
|
|
83
|
+
if (options?.delay === undefined) {
|
|
84
|
+
internalEvents.push(event);
|
|
85
|
+
} else {
|
|
86
|
+
effects.push(effect('@xstate.raise', {
|
|
87
|
+
source: scope.self,
|
|
88
|
+
event,
|
|
89
|
+
id: options.id,
|
|
90
|
+
delay: options.delay
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
enqueue.sendTo = (target, event, options) => {
|
|
95
|
+
if (!target) {
|
|
96
|
+
internalEvents.push({
|
|
97
|
+
type: 'xstate.error.communication',
|
|
98
|
+
error: {
|
|
99
|
+
message: 'Unable to send event to an undefined actor',
|
|
100
|
+
event
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
effects.push(effect('@xstate.sendTo', {
|
|
106
|
+
source: scope.self,
|
|
107
|
+
target,
|
|
108
|
+
event,
|
|
109
|
+
id: options?.id,
|
|
110
|
+
delay: options?.delay
|
|
111
|
+
}));
|
|
112
|
+
};
|
|
113
|
+
const spawn = (logic, options = {}) => {
|
|
114
|
+
if (typeof logic === 'string') {
|
|
115
|
+
throw new Error(`Actor source "${logic}" not found`);
|
|
116
|
+
}
|
|
117
|
+
const allocation = allocations.get(scope);
|
|
118
|
+
let id = options.id;
|
|
119
|
+
let prefix;
|
|
120
|
+
let next;
|
|
121
|
+
if (id === undefined) {
|
|
122
|
+
prefix = logic.id ?? 'x';
|
|
123
|
+
if (prefix === '(machine)') {
|
|
124
|
+
prefix = 'x';
|
|
125
|
+
}
|
|
126
|
+
next = allocation.counters[prefix] ?? 0;
|
|
127
|
+
do {
|
|
128
|
+
id = `${prefix}:${next++}`;
|
|
129
|
+
} while (allocation.ids.has(id));
|
|
130
|
+
allocation.counters[prefix] = next;
|
|
131
|
+
}
|
|
132
|
+
if (allocation.ids.has(id)) {
|
|
133
|
+
throw new Error(`Actor with id "${id}" already exists`);
|
|
134
|
+
}
|
|
135
|
+
allocation.ids.add(id);
|
|
136
|
+
const actor = scope.system.createActorRef(logic, {
|
|
137
|
+
...options,
|
|
138
|
+
id,
|
|
139
|
+
parent: scope.self
|
|
140
|
+
});
|
|
141
|
+
effects.push(effect('@xstate.spawn', {
|
|
142
|
+
source: scope.self,
|
|
143
|
+
actor,
|
|
144
|
+
id,
|
|
145
|
+
logic,
|
|
146
|
+
src: logic,
|
|
147
|
+
input: options.input,
|
|
148
|
+
childUpdate: {
|
|
149
|
+
type: 'add',
|
|
150
|
+
actor,
|
|
151
|
+
id,
|
|
152
|
+
prefix,
|
|
153
|
+
next
|
|
154
|
+
}
|
|
155
|
+
}));
|
|
156
|
+
return actor;
|
|
157
|
+
};
|
|
158
|
+
enqueue.spawn = spawn;
|
|
159
|
+
enqueue.stop = actor => {
|
|
160
|
+
if (actor) {
|
|
161
|
+
allocations.get(scope)?.ids.delete(actor.id);
|
|
162
|
+
effects.push(effect('@xstate.stop', {
|
|
163
|
+
source: scope.self,
|
|
164
|
+
actor,
|
|
165
|
+
id: actor.id,
|
|
166
|
+
childUpdate: {
|
|
167
|
+
type: 'remove',
|
|
168
|
+
actor: actor
|
|
169
|
+
}
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const attach = (logic, input) => {
|
|
174
|
+
const actor = scope.system.createActorRef(logic, {
|
|
175
|
+
input,
|
|
176
|
+
parent: scope.self
|
|
177
|
+
});
|
|
178
|
+
effects.push(effect('@xstate.spawn', {
|
|
179
|
+
source: scope.self,
|
|
180
|
+
actor,
|
|
181
|
+
id: actor.id,
|
|
182
|
+
logic,
|
|
183
|
+
src: logic,
|
|
184
|
+
input
|
|
185
|
+
}));
|
|
186
|
+
return actor;
|
|
187
|
+
};
|
|
188
|
+
enqueue.listen = (actor, eventType, mapper) => attach(createActor.listenerLogic, {
|
|
189
|
+
actor,
|
|
190
|
+
eventType,
|
|
191
|
+
mapper
|
|
192
|
+
});
|
|
193
|
+
enqueue.subscribeTo = (actor, mappers) => attach(createActor.subscriptionLogic, {
|
|
194
|
+
actor,
|
|
195
|
+
mappers: typeof mappers === 'function' ? {
|
|
196
|
+
snapshot: mappers
|
|
197
|
+
} : mappers
|
|
198
|
+
});
|
|
199
|
+
return enqueue;
|
|
200
|
+
}
|
|
201
|
+
function resolveFSMEffects(snapshot, effects, scope) {
|
|
202
|
+
let nextSnapshot = snapshot;
|
|
203
|
+
for (const current of effects) {
|
|
204
|
+
const update = current.childUpdate;
|
|
205
|
+
if (update?.type === 'add') {
|
|
206
|
+
nextSnapshot = {
|
|
207
|
+
...nextSnapshot,
|
|
208
|
+
children: {
|
|
209
|
+
...nextSnapshot.children,
|
|
210
|
+
[update.id]: update.actor
|
|
211
|
+
},
|
|
212
|
+
...(update.prefix && {
|
|
213
|
+
_nextActorIds: {
|
|
214
|
+
...nextSnapshot._nextActorIds,
|
|
215
|
+
[update.prefix]: Math.max(nextSnapshot._nextActorIds?.[update.prefix] ?? 0, update.next)
|
|
216
|
+
}
|
|
217
|
+
})
|
|
218
|
+
};
|
|
219
|
+
} else if (update?.type === 'remove') {
|
|
220
|
+
const children = {
|
|
221
|
+
...nextSnapshot.children
|
|
222
|
+
};
|
|
223
|
+
let owned = update.actor._parent === scope.self;
|
|
224
|
+
for (const id of Object.keys(children)) {
|
|
225
|
+
if (children[id] === update.actor) {
|
|
226
|
+
owned = true;
|
|
227
|
+
delete children[id];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!owned) {
|
|
231
|
+
throw new Error(`Cannot stop non-child actor ${update.actor.id}`);
|
|
232
|
+
}
|
|
233
|
+
nextSnapshot = {
|
|
234
|
+
...nextSnapshot,
|
|
235
|
+
children
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (current.type === '@xstate.cancel') {
|
|
239
|
+
if (nextSnapshot.timers[current.id]) {
|
|
240
|
+
const timers = {
|
|
241
|
+
...nextSnapshot.timers
|
|
242
|
+
};
|
|
243
|
+
delete timers[current.id];
|
|
244
|
+
nextSnapshot = {
|
|
245
|
+
...nextSnapshot,
|
|
246
|
+
timers
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
} else if ((current.type === '@xstate.raise' || current.type === '@xstate.sendTo') && current.delay !== undefined) {
|
|
250
|
+
const timer = current;
|
|
251
|
+
const generated = timer.id === undefined;
|
|
252
|
+
const id = timer.id ?? `xstate.timer.auto.${nextSnapshot._nextTimerId}`;
|
|
253
|
+
timer.id = id;
|
|
254
|
+
nextSnapshot = {
|
|
255
|
+
...nextSnapshot,
|
|
256
|
+
timers: {
|
|
257
|
+
...nextSnapshot.timers,
|
|
258
|
+
[id]: {
|
|
259
|
+
id,
|
|
260
|
+
delay: timer.delay,
|
|
261
|
+
type: timer.type,
|
|
262
|
+
event: timer.event,
|
|
263
|
+
target: timer.type === '@xstate.raise' || timer.target === scope.self ? 'self' : timer.target
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
_nextTimerId: generated ? nextSnapshot._nextTimerId + 1 : nextSnapshot._nextTimerId
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
return [nextSnapshot, effects];
|
|
271
|
+
}
|
|
272
|
+
function appendFSMStarts(effects) {
|
|
273
|
+
const ordered = [];
|
|
274
|
+
const attached = [];
|
|
275
|
+
const children = [];
|
|
276
|
+
for (const current of effects) {
|
|
277
|
+
if (current.type === '@xstate.stop') {
|
|
278
|
+
for (const starts of [attached, children]) {
|
|
279
|
+
const index = starts.findIndex(start => start.actor === current.actor);
|
|
280
|
+
if (index !== -1) {
|
|
281
|
+
ordered.push(starts.splice(index, 1)[0]);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
ordered.push(current);
|
|
286
|
+
if (current.type !== '@xstate.spawn') {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const start = effect('@xstate.start', {
|
|
290
|
+
actor: current.actor,
|
|
291
|
+
id: current.id,
|
|
292
|
+
source: current.source
|
|
293
|
+
});
|
|
294
|
+
(current.logic === createActor.listenerLogic || current.logic === createActor.subscriptionLogic ? attached : children).push(start);
|
|
295
|
+
}
|
|
296
|
+
return ordered.concat(attached, children);
|
|
297
|
+
}
|
|
298
|
+
function createFSMSendEffect(scope, target, event) {
|
|
299
|
+
return effect('@xstate.sendTo', {
|
|
300
|
+
source: scope.self,
|
|
301
|
+
target,
|
|
302
|
+
event
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function finalizeFSMEffects(scope, previous, result) {
|
|
306
|
+
const [snapshot, effects] = result;
|
|
307
|
+
if ((snapshot.status === 'done' || snapshot.status === 'error') && previous?.status !== 'done' && previous?.status !== 'error') {
|
|
308
|
+
effects.push(effect('@xstate.terminate', {
|
|
309
|
+
source: scope.self,
|
|
310
|
+
actor: scope.self,
|
|
311
|
+
id: scope.self.id,
|
|
312
|
+
status: snapshot.status,
|
|
313
|
+
output: snapshot.output,
|
|
314
|
+
error: snapshot.error
|
|
315
|
+
}));
|
|
316
|
+
}
|
|
317
|
+
return [snapshot, effects];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const emptySources = {
|
|
321
|
+
actions: {},
|
|
322
|
+
actors: {},
|
|
323
|
+
guards: {},
|
|
324
|
+
delays: {}
|
|
325
|
+
};
|
|
326
|
+
const emptyExecutableActions = [];
|
|
327
|
+
const emptyFSMActions = [];
|
|
328
|
+
function assertPersistableFSM(children, timers) {
|
|
329
|
+
if (Object.keys(children).length) {
|
|
330
|
+
throw new Error('FSM child persistence requires registered sources.');
|
|
331
|
+
}
|
|
332
|
+
for (const id in timers) {
|
|
333
|
+
if (timers[id].target !== 'self') {
|
|
334
|
+
throw new Error(`FSM timer '${id}' must target self to be persisted.`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function toArray(value) {
|
|
339
|
+
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
340
|
+
}
|
|
341
|
+
function resolveContext(context, input) {
|
|
342
|
+
return typeof context === 'function' ? context({
|
|
343
|
+
input
|
|
344
|
+
}) : context ?? {};
|
|
345
|
+
}
|
|
346
|
+
function resolveTransitionContext(context, args) {
|
|
347
|
+
return typeof context === 'function' ? context(args) : context;
|
|
348
|
+
}
|
|
349
|
+
function resolveInput(input, context, event) {
|
|
350
|
+
return typeof input === 'function' ? input({
|
|
351
|
+
context,
|
|
352
|
+
event
|
|
353
|
+
}) : input;
|
|
354
|
+
}
|
|
355
|
+
function mergeContextPatch(context, patch) {
|
|
356
|
+
for (const key of Object.keys(patch)) {
|
|
357
|
+
if (!Object.prototype.hasOwnProperty.call(context, key) || !Object.is(context[key], patch[key])) {
|
|
358
|
+
return {
|
|
359
|
+
...context,
|
|
360
|
+
...patch
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return context;
|
|
365
|
+
}
|
|
366
|
+
function createSnapshot(value, context, input, machine, stateInput) {
|
|
367
|
+
return {
|
|
368
|
+
status: 'active',
|
|
369
|
+
output: undefined,
|
|
370
|
+
error: undefined,
|
|
371
|
+
value,
|
|
372
|
+
context,
|
|
373
|
+
input,
|
|
374
|
+
children: {},
|
|
375
|
+
timers: {},
|
|
376
|
+
_nextTimerId: 0,
|
|
377
|
+
_stateInput: stateInput,
|
|
378
|
+
machine
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function cloneSnapshot(snapshot, value, context, stateInput) {
|
|
382
|
+
return {
|
|
383
|
+
status: snapshot.status,
|
|
384
|
+
output: snapshot.output,
|
|
385
|
+
error: snapshot.error,
|
|
386
|
+
value,
|
|
387
|
+
context,
|
|
388
|
+
input: snapshot.input,
|
|
389
|
+
children: snapshot.children,
|
|
390
|
+
timers: snapshot.timers,
|
|
391
|
+
_nextTimerId: snapshot._nextTimerId,
|
|
392
|
+
// Carried across state changes: dropping these would let a later spawn
|
|
393
|
+
// reuse the id of a still-running child.
|
|
394
|
+
_nextActorIds: snapshot._nextActorIds,
|
|
395
|
+
_stateInput: stateInput,
|
|
396
|
+
machine: snapshot.machine
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
function stopSnapshot(snapshot) {
|
|
400
|
+
return {
|
|
401
|
+
status: 'stopped',
|
|
402
|
+
output: undefined,
|
|
403
|
+
error: undefined,
|
|
404
|
+
value: snapshot.value,
|
|
405
|
+
context: snapshot.context,
|
|
406
|
+
input: undefined,
|
|
407
|
+
children: snapshot.children,
|
|
408
|
+
timers: snapshot.timers,
|
|
409
|
+
_nextTimerId: snapshot._nextTimerId,
|
|
410
|
+
_nextActorIds: snapshot._nextActorIds,
|
|
411
|
+
_stateInput: snapshot._stateInput,
|
|
412
|
+
machine: snapshot.machine
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function cleanupFSM(snapshot, actorScope) {
|
|
416
|
+
const effects = [];
|
|
417
|
+
const enqueue = createFSMEnqueue(actorScope, effects, []);
|
|
418
|
+
for (const child of Object.values(snapshot.children)) {
|
|
419
|
+
enqueue.stop(child);
|
|
420
|
+
}
|
|
421
|
+
for (const id of Object.keys(snapshot.timers)) {
|
|
422
|
+
enqueue.cancel(id);
|
|
423
|
+
}
|
|
424
|
+
return resolveFSMEffects(snapshot, effects, actorScope);
|
|
425
|
+
}
|
|
426
|
+
function completeFinalState(snapshot, event, actorScope, internalQueue, states, runStateActions) {
|
|
427
|
+
const completed = {
|
|
428
|
+
...snapshot,
|
|
429
|
+
status: 'done',
|
|
430
|
+
output: undefined,
|
|
431
|
+
error: undefined
|
|
432
|
+
};
|
|
433
|
+
const [exited, exitEffects] = runStateActions(completed, event, actorScope, states[completed.value]?.exit, completed._stateInput, internalQueue);
|
|
434
|
+
const [cleaned, cleanupEffects] = cleanupFSM(exited, actorScope);
|
|
435
|
+
return [cleaned, [...exitEffects, ...cleanupEffects]];
|
|
436
|
+
}
|
|
437
|
+
function invalidStringTarget(path, target) {
|
|
438
|
+
throw new Error(`Invalid transition for "${path}": use { target: "${target}" } instead of a string target.`);
|
|
439
|
+
}
|
|
440
|
+
function assertNoStringTransitions(config) {
|
|
441
|
+
for (const [stateKey, stateConfig] of Object.entries(config.states)) {
|
|
442
|
+
for (const [eventType, transitionConfig] of Object.entries(stateConfig.on ?? {})) {
|
|
443
|
+
for (const transition of toArray(transitionConfig)) {
|
|
444
|
+
if (typeof transition === 'string') {
|
|
445
|
+
invalidStringTarget(`${stateKey}.${eventType}`, transition);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (stateConfig.always) {
|
|
450
|
+
for (const transition of toArray(stateConfig.always)) {
|
|
451
|
+
if (typeof transition === 'string') {
|
|
452
|
+
invalidStringTarget(`${stateKey}.always`, transition);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function createFSM(config) {
|
|
459
|
+
const machine = {
|
|
460
|
+
id: config.id ?? '(fsm)',
|
|
461
|
+
sources: emptySources
|
|
462
|
+
};
|
|
463
|
+
assertNoStringTransitions(config);
|
|
464
|
+
const runActions = (snapshot, event, actorScope, actionsConfig, internalQueue) => {
|
|
465
|
+
if (!actionsConfig) {
|
|
466
|
+
return [snapshot, emptyExecutableActions];
|
|
467
|
+
}
|
|
468
|
+
const effects = [];
|
|
469
|
+
const enqueue = createFSMEnqueue(actorScope, effects, internalQueue);
|
|
470
|
+
let context = snapshot.context;
|
|
471
|
+
for (const action of toArray(actionsConfig)) {
|
|
472
|
+
const result = action(createActor.withActorScope({
|
|
473
|
+
context,
|
|
474
|
+
event: event,
|
|
475
|
+
input: snapshot._stateInput,
|
|
476
|
+
value: snapshot.value,
|
|
477
|
+
children: snapshot.children
|
|
478
|
+
}, actorScope), enqueue);
|
|
479
|
+
if (result?.context) {
|
|
480
|
+
context = mergeContextPatch(context, result.context);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return resolveFSMEffects(context === snapshot.context ? snapshot : cloneSnapshot(snapshot, snapshot.value, context, snapshot._stateInput), effects, actorScope);
|
|
484
|
+
};
|
|
485
|
+
const runStateActions = (snapshot, event, actorScope, actionsConfig, stateInput, internalQueue) => {
|
|
486
|
+
if (!actionsConfig) {
|
|
487
|
+
return [snapshot, emptyExecutableActions];
|
|
488
|
+
}
|
|
489
|
+
const actions = [];
|
|
490
|
+
const enq = createFSMEnqueue(actorScope, actions, internalQueue);
|
|
491
|
+
let context;
|
|
492
|
+
const actionCount = Array.isArray(actionsConfig) ? actionsConfig.length : 1;
|
|
493
|
+
for (let i = 0; i < actionCount; i++) {
|
|
494
|
+
const action = Array.isArray(actionsConfig) ? actionsConfig[i] : actionsConfig;
|
|
495
|
+
const result = action(createActor.withActorScope({
|
|
496
|
+
context: context ?? snapshot.context,
|
|
497
|
+
event: event,
|
|
498
|
+
input: stateInput,
|
|
499
|
+
value: snapshot.value,
|
|
500
|
+
children: snapshot.children
|
|
501
|
+
}, actorScope), enq);
|
|
502
|
+
if (result?.context !== undefined) {
|
|
503
|
+
const currentContext = context ?? snapshot.context;
|
|
504
|
+
const nextContext = mergeContextPatch(currentContext, result.context);
|
|
505
|
+
if (nextContext !== currentContext) {
|
|
506
|
+
context = nextContext;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const nextSnapshot = context !== undefined ? cloneSnapshot(snapshot, snapshot.value, context, snapshot._stateInput) : snapshot;
|
|
511
|
+
return resolveFSMEffects(nextSnapshot, actions, actorScope);
|
|
512
|
+
};
|
|
513
|
+
const selectTransition = (snapshot, event, actorScope, internalQueue) => {
|
|
514
|
+
const state = config.states[snapshot.value];
|
|
515
|
+
const transitionsConfig = event.type === '' ? state?.always : state?.on?.[event.type];
|
|
516
|
+
if (!transitionsConfig) {
|
|
517
|
+
return undefined;
|
|
518
|
+
}
|
|
519
|
+
const transitionCount = Array.isArray(transitionsConfig) ? transitionsConfig.length : 1;
|
|
520
|
+
for (let i = 0; i < transitionCount; i++) {
|
|
521
|
+
const transition = Array.isArray(transitionsConfig) ? transitionsConfig[i] : transitionsConfig;
|
|
522
|
+
const args = createActor.withActorScope({
|
|
523
|
+
context: snapshot.context,
|
|
524
|
+
event,
|
|
525
|
+
input: snapshot.input,
|
|
526
|
+
value: snapshot.value,
|
|
527
|
+
children: snapshot.children
|
|
528
|
+
}, actorScope);
|
|
529
|
+
if (typeof transition === 'function') {
|
|
530
|
+
const actions = [];
|
|
531
|
+
const enq = createFSMEnqueue(actorScope, actions, internalQueue);
|
|
532
|
+
const result = transition(args, enq);
|
|
533
|
+
if (!result) {
|
|
534
|
+
if (actions.length) {
|
|
535
|
+
return {
|
|
536
|
+
effects: actions
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
return {
|
|
542
|
+
target: result.target,
|
|
543
|
+
context: result.context,
|
|
544
|
+
input: result.input,
|
|
545
|
+
effects: actions
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
if ('guard' in transition && transition.guard && !transition.guard(args)) {
|
|
549
|
+
continue;
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
target: transition.target,
|
|
553
|
+
context: resolveTransitionContext(transition.context, args),
|
|
554
|
+
input: transition.input,
|
|
555
|
+
actions: 'actions' in transition && transition.actions ? toArray(transition.actions) : emptyFSMActions
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
return undefined;
|
|
559
|
+
};
|
|
560
|
+
const transitionCore = (snapshot, event, actorScope) => {
|
|
561
|
+
if (snapshot.status !== 'active') {
|
|
562
|
+
return [snapshot, []];
|
|
563
|
+
}
|
|
564
|
+
if (event.type === createActor.XSTATE_STOP) {
|
|
565
|
+
const [cleaned, cleanupEffects] = cleanupFSM(snapshot, actorScope);
|
|
566
|
+
return [stopSnapshot(cleaned), cleanupEffects];
|
|
567
|
+
}
|
|
568
|
+
if (event.type === createActor.XSTATE_TIMER) {
|
|
569
|
+
const timer = snapshot.timers[event.id];
|
|
570
|
+
if (!timer) {
|
|
571
|
+
return [snapshot, emptyExecutableActions];
|
|
572
|
+
}
|
|
573
|
+
const timers = {
|
|
574
|
+
...snapshot.timers
|
|
575
|
+
};
|
|
576
|
+
delete timers[timer.id];
|
|
577
|
+
const nextSnapshot = {
|
|
578
|
+
...snapshot,
|
|
579
|
+
timers
|
|
580
|
+
};
|
|
581
|
+
if (timer.type === '@xstate.raise') {
|
|
582
|
+
return transitionCore(nextSnapshot, timer.event, actorScope);
|
|
583
|
+
}
|
|
584
|
+
const target = timer.target === 'self' ? actorScope.self : timer.target;
|
|
585
|
+
return [nextSnapshot, [createFSMSendEffect(actorScope, target, timer.event)]];
|
|
586
|
+
}
|
|
587
|
+
const stateConfig = config.states[snapshot.value];
|
|
588
|
+
const directTransition = event.type === '' ? stateConfig?.always : stateConfig?.on?.[event.type];
|
|
589
|
+
if (!directTransition) {
|
|
590
|
+
return [snapshot, emptyExecutableActions];
|
|
591
|
+
}
|
|
592
|
+
if (!Array.isArray(directTransition) && typeof directTransition !== 'function' && !('guard' in directTransition) && !('actions' in directTransition && directTransition.actions) && typeof directTransition.input !== 'function') {
|
|
593
|
+
const target = directTransition.target ?? snapshot.value;
|
|
594
|
+
const targetState = config.states[target];
|
|
595
|
+
const stateChanged = target !== snapshot.value;
|
|
596
|
+
if (targetState?.always || stateChanged && (stateConfig.exit || targetState?.entry || targetState?.type === 'final')) ; else {
|
|
597
|
+
const hasContext = directTransition.context !== undefined;
|
|
598
|
+
const hasInput = directTransition.input !== undefined;
|
|
599
|
+
const resolvedContext = !createActor.isLazyActorScope(actorScope) ? resolveTransitionContext(directTransition.context, {
|
|
600
|
+
context: snapshot.context,
|
|
601
|
+
event,
|
|
602
|
+
input: snapshot.input,
|
|
603
|
+
value: snapshot.value,
|
|
604
|
+
self: actorScope.self,
|
|
605
|
+
system: actorScope.system,
|
|
606
|
+
parent: actorScope.self._parent,
|
|
607
|
+
children: snapshot.children
|
|
608
|
+
}) : typeof directTransition.context === 'function' ? directTransition.context(createActor.withActorScope({
|
|
609
|
+
context: snapshot.context,
|
|
610
|
+
event,
|
|
611
|
+
input: snapshot.input,
|
|
612
|
+
value: snapshot.value,
|
|
613
|
+
children: snapshot.children
|
|
614
|
+
}, actorScope)) : directTransition.context;
|
|
615
|
+
const context = hasContext && resolvedContext ? mergeContextPatch(snapshot.context, resolvedContext) : snapshot.context;
|
|
616
|
+
if (!stateChanged && context === snapshot.context && !hasInput && snapshot._stateInput === undefined) {
|
|
617
|
+
return [snapshot, emptyExecutableActions];
|
|
618
|
+
}
|
|
619
|
+
return [cloneSnapshot(snapshot, target, context, hasInput ? resolveInput(directTransition.input, context, event) : undefined), emptyExecutableActions];
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
let selectedTransition;
|
|
623
|
+
if (typeof directTransition === 'function' && directTransition.length < 2 && !stateConfig?.exit) {
|
|
624
|
+
const result = directTransition(createActor.withActorScope({
|
|
625
|
+
context: snapshot.context,
|
|
626
|
+
event,
|
|
627
|
+
input: snapshot.input,
|
|
628
|
+
value: snapshot.value,
|
|
629
|
+
children: snapshot.children
|
|
630
|
+
}, actorScope), undefined);
|
|
631
|
+
if (!result) {
|
|
632
|
+
return [snapshot, emptyExecutableActions];
|
|
633
|
+
}
|
|
634
|
+
const target = result.target ?? snapshot.value;
|
|
635
|
+
const targetState = config.states[target];
|
|
636
|
+
if (!targetState?.entry && !targetState?.always && targetState?.type !== 'final') {
|
|
637
|
+
const hasContext = result.context !== undefined;
|
|
638
|
+
const hasInput = result.input !== undefined;
|
|
639
|
+
const context = hasContext && result.context ? mergeContextPatch(snapshot.context, result.context) : snapshot.context;
|
|
640
|
+
if (target === snapshot.value && context === snapshot.context && !hasInput && snapshot._stateInput === undefined) {
|
|
641
|
+
return [snapshot, emptyExecutableActions];
|
|
642
|
+
}
|
|
643
|
+
return [cloneSnapshot(snapshot, target, context, hasInput ? resolveInput(result.input, context, event) : undefined), emptyExecutableActions];
|
|
644
|
+
}
|
|
645
|
+
selectedTransition = {
|
|
646
|
+
target: result.target,
|
|
647
|
+
context: result.context,
|
|
648
|
+
input: result.input,
|
|
649
|
+
actions: emptyFSMActions
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
let nextSnapshot = snapshot;
|
|
653
|
+
const executableActions = [];
|
|
654
|
+
const internalQueue = [event];
|
|
655
|
+
let iterations = 0;
|
|
656
|
+
while (internalQueue.length) {
|
|
657
|
+
if (++iterations > 1000) {
|
|
658
|
+
throw new Error('FSM microstep count exceeded 1000');
|
|
659
|
+
}
|
|
660
|
+
const nextEvent = internalQueue.shift();
|
|
661
|
+
const selected = selectedTransition ?? selectTransition(nextSnapshot, nextEvent, actorScope, internalQueue);
|
|
662
|
+
selectedTransition = undefined;
|
|
663
|
+
if (!selected) {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
const nextValue = selected.target ?? nextSnapshot.value;
|
|
667
|
+
const nextState = config.states[nextValue];
|
|
668
|
+
const stateChanged = nextValue !== nextSnapshot.value;
|
|
669
|
+
if (stateChanged) {
|
|
670
|
+
const [exited, exitActions] = runStateActions(nextSnapshot, nextEvent, actorScope, config.states[nextSnapshot.value]?.exit, nextSnapshot._stateInput, internalQueue);
|
|
671
|
+
nextSnapshot = exited;
|
|
672
|
+
executableActions.push(...exitActions);
|
|
673
|
+
}
|
|
674
|
+
let context = nextSnapshot.context;
|
|
675
|
+
if (selected.context !== undefined) {
|
|
676
|
+
context = mergeContextPatch(context, selected.context);
|
|
677
|
+
}
|
|
678
|
+
const hasInput = selected.input !== undefined;
|
|
679
|
+
const stateInput = hasInput ? resolveInput(selected.input, context, nextEvent) : undefined;
|
|
680
|
+
if (stateChanged || context !== nextSnapshot.context || hasInput || nextSnapshot._stateInput !== undefined) {
|
|
681
|
+
nextSnapshot = cloneSnapshot(nextSnapshot, nextValue, context, stateInput);
|
|
682
|
+
}
|
|
683
|
+
const transitionResult = selected.effects ? resolveFSMEffects(nextSnapshot, selected.effects, actorScope) : runActions(nextSnapshot, nextEvent, actorScope, selected.actions, internalQueue);
|
|
684
|
+
const [afterTransition, transitionActions] = transitionResult;
|
|
685
|
+
nextSnapshot = afterTransition;
|
|
686
|
+
executableActions.push(...transitionActions);
|
|
687
|
+
if (stateChanged) {
|
|
688
|
+
const [entered, entryActions] = runStateActions(nextSnapshot, nextEvent, actorScope, nextState?.entry, stateInput, internalQueue);
|
|
689
|
+
nextSnapshot = entered;
|
|
690
|
+
executableActions.push(...entryActions);
|
|
691
|
+
}
|
|
692
|
+
if (nextState?.type === 'final') {
|
|
693
|
+
const [completed, completionEffects] = completeFinalState(nextSnapshot, nextEvent, actorScope, internalQueue, config.states, runStateActions);
|
|
694
|
+
nextSnapshot = completed;
|
|
695
|
+
executableActions.push(...completionEffects);
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
if (nextState?.always) {
|
|
699
|
+
internalQueue.unshift({
|
|
700
|
+
type: ''
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return [nextSnapshot, executableActions];
|
|
705
|
+
};
|
|
706
|
+
const transition = (...args) => {
|
|
707
|
+
beginFSMEffects(args[2], args[0]);
|
|
708
|
+
const [nextSnapshot, effects] = transitionCore(...args);
|
|
709
|
+
return finalizeFSMEffects(args[2], args[0], [nextSnapshot, appendFSMStarts(effects)]);
|
|
710
|
+
};
|
|
711
|
+
const logic = {
|
|
712
|
+
id: config.id,
|
|
713
|
+
config,
|
|
714
|
+
transition,
|
|
715
|
+
initialTransition: (input, actorScope) => {
|
|
716
|
+
const initialState = config.states[config.initial];
|
|
717
|
+
const context = resolveContext(config.context, input);
|
|
718
|
+
const snapshot = createSnapshot(config.initial, context, input, machine);
|
|
719
|
+
beginFSMEffects(actorScope, snapshot);
|
|
720
|
+
const internalQueue = [];
|
|
721
|
+
let [nextSnapshot, actions] = runStateActions(snapshot, {
|
|
722
|
+
type: createActor.XSTATE_INIT
|
|
723
|
+
}, actorScope, initialState?.entry, undefined, internalQueue);
|
|
724
|
+
if (!actions.length) {
|
|
725
|
+
actions = [];
|
|
726
|
+
}
|
|
727
|
+
if (initialState?.type === 'final') {
|
|
728
|
+
const [completed, completionEffects] = completeFinalState(nextSnapshot, {
|
|
729
|
+
type: createActor.XSTATE_INIT
|
|
730
|
+
}, actorScope, internalQueue, config.states, runStateActions);
|
|
731
|
+
nextSnapshot = completed;
|
|
732
|
+
actions.push(...completionEffects);
|
|
733
|
+
} else {
|
|
734
|
+
if (initialState?.always) {
|
|
735
|
+
internalQueue.unshift({
|
|
736
|
+
type: ''
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
while (internalQueue.length) {
|
|
740
|
+
const [raisedSnapshot, raisedActions] = transitionCore(nextSnapshot, internalQueue.shift(), actorScope);
|
|
741
|
+
nextSnapshot = raisedSnapshot;
|
|
742
|
+
actions.push(...raisedActions);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return finalizeFSMEffects(actorScope, undefined, [nextSnapshot, appendFSMStarts(actions)]);
|
|
746
|
+
},
|
|
747
|
+
getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
|
|
748
|
+
getPersistedSnapshot: currentSnapshot => {
|
|
749
|
+
const {
|
|
750
|
+
machine: _,
|
|
751
|
+
children,
|
|
752
|
+
...snapshot
|
|
753
|
+
} = currentSnapshot;
|
|
754
|
+
assertPersistableFSM(children, snapshot.timers);
|
|
755
|
+
return {
|
|
756
|
+
...snapshot,
|
|
757
|
+
children: {}
|
|
758
|
+
};
|
|
759
|
+
},
|
|
760
|
+
restoreSnapshot: snapshot => {
|
|
761
|
+
const persisted = snapshot;
|
|
762
|
+
const children = persisted.children ?? {};
|
|
763
|
+
const timers = persisted.timers ?? {};
|
|
764
|
+
assertPersistableFSM(children, timers);
|
|
765
|
+
const restored = {
|
|
766
|
+
...persisted,
|
|
767
|
+
children: {},
|
|
768
|
+
timers,
|
|
769
|
+
machine: machine
|
|
770
|
+
};
|
|
771
|
+
return restored;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
return logic;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
exports.createFSM = createFSM;
|