@systemfsoftware/xstate 0.0.0-stage → 6.0.0-beta.65
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/CHANGELOG.md +57 -0
- package/LICENSE +22 -0
- package/README.md +640 -2
- package/dist/actors-LxMBvgxO.mjs +472 -0
- package/dist/actors.d.ts +3 -0
- package/dist/actors.mjs +3 -0
- package/dist/base.types-DF8ZPiXj.d.ts +210 -0
- package/dist/createActor-BGHd7i8Q.mjs +4851 -0
- package/dist/durable.d.ts +211 -0
- package/dist/durable.mjs +283 -0
- package/dist/effectDescriptor-EvNuy6U3.d.ts +91 -0
- package/dist/fsm.d.ts +139 -0
- package/dist/fsm.mjs +60 -0
- package/dist/graph.d.ts +160 -0
- package/dist/graph.mjs +667 -0
- package/dist/index-CKUyiMZC.d.ts +1832 -0
- package/dist/index-DDWoKIF5.d.ts +512 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +7 -0
- package/dist/schema.types-tsIbgQd-.mjs +46 -0
- package/dist/src-zLWug7SF.mjs +2641 -0
- package/dist/transition-ZMXyPI-x.mjs +258 -0
- package/dist/types-B2EAwOk-.d.ts +3511 -0
- package/dist/utils-CXBxxp3Z.mjs +146 -0
- package/dist/validation.d.ts +49 -0
- package/dist/validation.mjs +216 -0
- package/package.json +83 -3
|
@@ -0,0 +1,2641 @@
|
|
|
1
|
+
import { c as assertValid, n as TimeoutError, r as createAsyncLogic } from "./actors-LxMBvgxO.mjs";
|
|
2
|
+
import { a as isActorRefLike, c as matchesEventDescriptor, d as resolveReferencedActor, f as toArray, h as defaultWarn } from "./utils-CXBxxp3Z.mjs";
|
|
3
|
+
import { Ct as createErrorPlatformEvent, D as createSpawnEffect, F as resolveActionsWithContext, G as isInertActorScope, H as systemLogicMetadata, I as takeSpawnAllocationCounters, J as createMachineSnapshot, K as setInertActorScopeSnapshot, L as resolveRegisteredActorSource, N as mergeActorIdCounters, O as createStartEffect, P as reserveChildId, S as beginSpawnAllocation, St as createErrorActorEvent, Tt as createInvokeTimeoutEvent, U as attachSnapshotActorRef, V as listenerLogic, W as createInertActorScope, X as isMachineSnapshot, Y as getPersistedSnapshot, _ as transitionNode, at as parseDelayToMilliseconds, b as allocateChildId, ct as encodeAddressSegment, d as getTransitionResult, dt as parseGeneratedActorId, f as hasEffect, g as resolveStateValue, h as macrostep, i as StateNode, it as isAtomicStateNode, j as finalizeTransitionResult, l as getStateNodeByPath, lt as getActorIdPrefix, m as isInFinalState, mt as setSnapshotActorRef, nt as createRemoteActorRef, o as formatRouteTransitions, ot as withActorSelf, p as initialMicrostep, q as cloneMachineSnapshot, r as createActor, s as getAllStateNodes, tt as toStatePath, u as getStateNodes, w as createDeadLetterEffect, wt as createInitEvent, x as assertChildIdFree, xt as createDoneActorEvent, y as isStateId, z as subscriptionLogic } from "./createActor-BGHd7i8Q.mjs";
|
|
4
|
+
// src/types.ts
|
|
5
|
+
/** @public */
|
|
6
|
+
let SpecialTargets = /* @__PURE__ */ function(SpecialTargets) {
|
|
7
|
+
SpecialTargets["Internal"] = "#_internal";
|
|
8
|
+
return SpecialTargets;
|
|
9
|
+
}({});
|
|
10
|
+
// src/assert.ts
|
|
11
|
+
const describeTypes = (types) => types.length === 1 ? `type matching "${types[0]}"` : `one of types matching "${types.join("\", \"")}"`;
|
|
12
|
+
const assertEventOf = (event, type) => {
|
|
13
|
+
const types = toArray(type);
|
|
14
|
+
if (!types.some((descriptor) => matchesEventDescriptor(event.type, descriptor))) throw new Error(`Expected event ${JSON.stringify(event)} to have ${describeTypes(types)}`);
|
|
15
|
+
};
|
|
16
|
+
function assertEvent(...args) {
|
|
17
|
+
if (args.length === 1) {
|
|
18
|
+
const [type] = args;
|
|
19
|
+
return (event) => assertEventOf(event, type);
|
|
20
|
+
}
|
|
21
|
+
assertEventOf(args[0], args[1]);
|
|
22
|
+
}
|
|
23
|
+
// src/spawn.ts
|
|
24
|
+
function createSpawner(actorScope, actors, spawnedChildren) {
|
|
25
|
+
return ((src, options) => {
|
|
26
|
+
const referencedSrc = resolveRegisteredActorSource(actors, src);
|
|
27
|
+
const id = options?.id ?? allocateChildId(actorScope, referencedSrc ?? src).id;
|
|
28
|
+
if (options?.id !== void 0) {
|
|
29
|
+
assertChildIdFree(actorScope, options.id);
|
|
30
|
+
reserveChildId(actorScope, options.id);
|
|
31
|
+
}
|
|
32
|
+
const actor = actorScope.system.createActorRef(src, {
|
|
33
|
+
id,
|
|
34
|
+
parent: actorScope.self,
|
|
35
|
+
syncSnapshot: options?.syncSnapshot,
|
|
36
|
+
input: options?.input,
|
|
37
|
+
src: referencedSrc ?? src,
|
|
38
|
+
registryKey: options?.registryKey
|
|
39
|
+
});
|
|
40
|
+
spawnedChildren[actor.id] = actor;
|
|
41
|
+
return actor;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// src/StateMachine.ts
|
|
45
|
+
function findEventSchema(schemas, eventType) {
|
|
46
|
+
if (!schemas) return;
|
|
47
|
+
if (Object.hasOwn(schemas, eventType)) return schemas[eventType];
|
|
48
|
+
const descriptor = Object.keys(schemas).find((key) => matchesEventDescriptor(eventType, key));
|
|
49
|
+
return descriptor === void 0 ? void 0 : schemas[descriptor];
|
|
50
|
+
}
|
|
51
|
+
let emptyCanActor;
|
|
52
|
+
let emptyCanActorScope;
|
|
53
|
+
function getEmptyCanActor() {
|
|
54
|
+
return emptyCanActor ??= createActor({
|
|
55
|
+
transition: (snapshot) => [snapshot, []],
|
|
56
|
+
initialTransition: () => [{
|
|
57
|
+
status: "active",
|
|
58
|
+
output: void 0,
|
|
59
|
+
error: void 0
|
|
60
|
+
}, []],
|
|
61
|
+
getInitialSnapshot: () => ({
|
|
62
|
+
status: "active",
|
|
63
|
+
output: void 0,
|
|
64
|
+
error: void 0
|
|
65
|
+
}),
|
|
66
|
+
getPersistedSnapshot: (snapshot) => snapshot
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function getEmptyCanActorScope() {
|
|
70
|
+
if (emptyCanActorScope) return emptyCanActorScope;
|
|
71
|
+
const actor = getEmptyCanActor();
|
|
72
|
+
emptyCanActorScope = {
|
|
73
|
+
self: actor,
|
|
74
|
+
logger: () => {},
|
|
75
|
+
warn: defaultWarn,
|
|
76
|
+
id: "",
|
|
77
|
+
sessionId: "",
|
|
78
|
+
defer: () => {},
|
|
79
|
+
system: actor.system,
|
|
80
|
+
stopChild: () => {},
|
|
81
|
+
emit: () => {},
|
|
82
|
+
actionExecutor: () => {}
|
|
83
|
+
};
|
|
84
|
+
return emptyCanActorScope;
|
|
85
|
+
}
|
|
86
|
+
/** @public */
|
|
87
|
+
var StateMachine = class StateMachine {
|
|
88
|
+
config;
|
|
89
|
+
validator;
|
|
90
|
+
/**
|
|
91
|
+
* Type-only marker for the actor's internal event protocol. `actor.send` and
|
|
92
|
+
* `actor.trigger` read it to drop internal events from the public protocol,
|
|
93
|
+
* so it must survive `stripInternal` into the published declarations. Not
|
|
94
|
+
* `declare` (the build's babel pipeline rejects declare class fields); the
|
|
95
|
+
* one `undefined` property this emits per machine instance is inert.
|
|
96
|
+
*/
|
|
97
|
+
_internalEventType;
|
|
98
|
+
/** Type-only marker for transition metadata. Never assigned at runtime. */
|
|
99
|
+
_transitionMetaType;
|
|
100
|
+
/**
|
|
101
|
+
* Type-only marker for the declared input type. `getInitialSnapshot` takes
|
|
102
|
+
* input optionally, so inferring input from it always adds `undefined`;
|
|
103
|
+
* `InputFrom` reads this carrier instead. Method-shaped so it stays
|
|
104
|
+
* bivariant like `getInitialSnapshot`. Never assigned at runtime.
|
|
105
|
+
*/
|
|
106
|
+
_inputType;
|
|
107
|
+
/**
|
|
108
|
+
* Type-level carriers for the machine's source maps and state schema. Type
|
|
109
|
+
* helpers in integration packages read these to walk a machine's declared
|
|
110
|
+
* actions, actors and invoked sources. They are never assigned at runtime.
|
|
111
|
+
*/
|
|
112
|
+
_actionMap;
|
|
113
|
+
_actorMap;
|
|
114
|
+
_stateSchema;
|
|
115
|
+
/** The machine's own version. */
|
|
116
|
+
version;
|
|
117
|
+
schemas;
|
|
118
|
+
/** Standard Schema for snapshots persisted by this machine version. */
|
|
119
|
+
snapshotSchema;
|
|
120
|
+
/** Standard Schema for complete events accepted by this machine version. */
|
|
121
|
+
eventSchema;
|
|
122
|
+
sources;
|
|
123
|
+
/** Runtime options for machine execution. */
|
|
124
|
+
options;
|
|
125
|
+
/** @internal */
|
|
126
|
+
idMap = /* @__PURE__ */ new Map();
|
|
127
|
+
root;
|
|
128
|
+
id;
|
|
129
|
+
states;
|
|
130
|
+
events;
|
|
131
|
+
internalEventDescriptors;
|
|
132
|
+
/** @internal Skips eventless-selection scans for machines without `always`. */
|
|
133
|
+
_hasEventlessTransitions;
|
|
134
|
+
/**
|
|
135
|
+
* Adapter hooks for actor-local transition evaluation state. Used by
|
|
136
|
+
* `@xstate/scxml`; not part of the stable API.
|
|
137
|
+
*
|
|
138
|
+
* @experimental
|
|
139
|
+
*/
|
|
140
|
+
_microstepHooks;
|
|
141
|
+
constructor(config, sources, validator) {
|
|
142
|
+
this.config = config;
|
|
143
|
+
this.validator = validator;
|
|
144
|
+
this.id = config.id || "(machine)";
|
|
145
|
+
this.sources = {
|
|
146
|
+
actors: config.actors ?? {},
|
|
147
|
+
actions: config.actions ?? {},
|
|
148
|
+
delays: config.delays ?? {},
|
|
149
|
+
guards: config.guards ?? {},
|
|
150
|
+
...sources
|
|
151
|
+
};
|
|
152
|
+
this.version = this.config.version;
|
|
153
|
+
this.schemas = this.config.schemas;
|
|
154
|
+
this.snapshotSchema = { "~standard": {
|
|
155
|
+
version: 1,
|
|
156
|
+
vendor: "xstate",
|
|
157
|
+
validate: async (value) => {
|
|
158
|
+
if (value === null || typeof value !== "object") return { issues: [{ message: "Expected a persisted snapshot." }] };
|
|
159
|
+
const snapshot = {
|
|
160
|
+
historyValue: {},
|
|
161
|
+
timers: {},
|
|
162
|
+
...value
|
|
163
|
+
};
|
|
164
|
+
const contextSchema = this.schemas?.context;
|
|
165
|
+
let context = snapshot["context"];
|
|
166
|
+
if (contextSchema) {
|
|
167
|
+
const result = await contextSchema["~standard"].validate(context);
|
|
168
|
+
if (result.issues) return { issues: [{ message: `Invalid context for machine '${this.id}' version '${this.version}': ${result.issues[0]?.message}` }] };
|
|
169
|
+
context = result.value;
|
|
170
|
+
}
|
|
171
|
+
for (const key of ["value", "children"]) if (!(key in snapshot)) return { issues: [{ message: `Persisted snapshot is missing '${key}'.` }] };
|
|
172
|
+
if (![
|
|
173
|
+
"active",
|
|
174
|
+
"done",
|
|
175
|
+
"error",
|
|
176
|
+
"stopped"
|
|
177
|
+
].includes(snapshot["status"])) return { issues: [{ message: "Persisted snapshot has invalid status." }] };
|
|
178
|
+
for (const key of [
|
|
179
|
+
"children",
|
|
180
|
+
"historyValue",
|
|
181
|
+
"timers"
|
|
182
|
+
]) if (snapshot[key] === null || typeof snapshot[key] !== "object" || Array.isArray(snapshot[key])) return { issues: [{ message: `Persisted snapshot has invalid '${key}'.` }] };
|
|
183
|
+
try {
|
|
184
|
+
this.resolveState({
|
|
185
|
+
value: snapshot["value"],
|
|
186
|
+
context
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
return { issues: [{ message: error instanceof Error ? error.message : "Persisted snapshot has invalid state value." }] };
|
|
190
|
+
}
|
|
191
|
+
return { value: {
|
|
192
|
+
...snapshot,
|
|
193
|
+
context
|
|
194
|
+
} };
|
|
195
|
+
}
|
|
196
|
+
} };
|
|
197
|
+
this.eventSchema = { "~standard": {
|
|
198
|
+
version: 1,
|
|
199
|
+
vendor: "xstate",
|
|
200
|
+
validate: async (value) => {
|
|
201
|
+
if (value === null || typeof value !== "object" || typeof value.type !== "string") return { issues: [{ message: "Expected an event object." }] };
|
|
202
|
+
const event = value;
|
|
203
|
+
const eventSchemas = this.schemas?.events;
|
|
204
|
+
const internalEventSchemas = this.schemas?.internalEvents;
|
|
205
|
+
const isFrameworkEvent = event.type.startsWith("xstate.") || event.type.startsWith("@xstate.");
|
|
206
|
+
const schema = findEventSchema(internalEventSchemas, event.type) ?? findEventSchema(eventSchemas, event.type);
|
|
207
|
+
if ((eventSchemas || internalEventSchemas) && !schema && !isFrameworkEvent) return { issues: [{ message: `Unknown event '${event.type}' for machine '${this.id}' version '${this.version}'.` }] };
|
|
208
|
+
if (!schema) return { value: event };
|
|
209
|
+
const { type, ...payload } = event;
|
|
210
|
+
const result = await schema["~standard"].validate(payload);
|
|
211
|
+
if (result.issues) return result;
|
|
212
|
+
if (result.value === null || typeof result.value !== "object") return { issues: [{ message: "Expected an event payload." }] };
|
|
213
|
+
return { value: {
|
|
214
|
+
...result.value,
|
|
215
|
+
type
|
|
216
|
+
} };
|
|
217
|
+
}
|
|
218
|
+
} };
|
|
219
|
+
this.internalEventDescriptors = Object.keys(this.schemas?.internalEvents ?? {});
|
|
220
|
+
this.options = { ...this.config.options };
|
|
221
|
+
this.transition = this.transition.bind(this);
|
|
222
|
+
this.initialTransition = this.initialTransition.bind(this);
|
|
223
|
+
this.getInitialSnapshot = this.getInitialSnapshot.bind(this);
|
|
224
|
+
this.getPersistedSnapshot = this.getPersistedSnapshot.bind(this);
|
|
225
|
+
this.restoreSnapshot = this.restoreSnapshot.bind(this);
|
|
226
|
+
this.start = this.start.bind(this);
|
|
227
|
+
this.root = new StateNode(config, {
|
|
228
|
+
_key: this.id,
|
|
229
|
+
_machine: this
|
|
230
|
+
});
|
|
231
|
+
this.root._initialize();
|
|
232
|
+
formatRouteTransitions(this.root);
|
|
233
|
+
this.root._refreshEventMetadata();
|
|
234
|
+
this._hasEventlessTransitions = Array.from(this.idMap.values()).some((stateNode) => !!stateNode.always?.length);
|
|
235
|
+
this.states = this.root.states;
|
|
236
|
+
this.events = this.root.events;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Clones this state machine with the provided sources.
|
|
240
|
+
*
|
|
241
|
+
* @param sources Options (`actions`, `guards`, `actors`, `delays`) to
|
|
242
|
+
* recursively merge with the existing options.
|
|
243
|
+
* @returns A new `StateMachine` instance with the provided sources.
|
|
244
|
+
*/
|
|
245
|
+
provide(sources) {
|
|
246
|
+
const { actions, guards, actors, delays } = this.sources;
|
|
247
|
+
const provided = new StateMachine(this.config, {
|
|
248
|
+
actions: {
|
|
249
|
+
...actions,
|
|
250
|
+
...sources.actions
|
|
251
|
+
},
|
|
252
|
+
guards: {
|
|
253
|
+
...guards,
|
|
254
|
+
...sources.guards
|
|
255
|
+
},
|
|
256
|
+
actors: {
|
|
257
|
+
...actors,
|
|
258
|
+
...sources.actors
|
|
259
|
+
},
|
|
260
|
+
delays: {
|
|
261
|
+
...delays,
|
|
262
|
+
...sources.delays
|
|
263
|
+
}
|
|
264
|
+
}, this.validator);
|
|
265
|
+
provided._json = this._json;
|
|
266
|
+
provided.internalEventDescriptors = this.internalEventDescriptors;
|
|
267
|
+
provided._microstepHooks = this._microstepHooks;
|
|
268
|
+
return provided;
|
|
269
|
+
}
|
|
270
|
+
resolveState(config) {
|
|
271
|
+
const resolvedStateValue = resolveStateValue(this.root, config.value);
|
|
272
|
+
const nodeSet = getAllStateNodes(getStateNodes(this.root, resolvedStateValue));
|
|
273
|
+
const nodes = [...nodeSet];
|
|
274
|
+
return createMachineSnapshot({
|
|
275
|
+
_nodes: nodes,
|
|
276
|
+
value: resolvedStateValue,
|
|
277
|
+
context: config.context || {},
|
|
278
|
+
children: {},
|
|
279
|
+
status: isInFinalState(nodeSet, this.root) ? "done" : config.status || "active",
|
|
280
|
+
output: config.output,
|
|
281
|
+
error: config.error,
|
|
282
|
+
historyValue: config.historyValue
|
|
283
|
+
}, this);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Determines the next snapshot given the current `snapshot` and received
|
|
287
|
+
* `event`. Calculates a full macrostep from all microsteps.
|
|
288
|
+
*
|
|
289
|
+
* @param snapshot The current snapshot
|
|
290
|
+
* @param event The received event
|
|
291
|
+
*/
|
|
292
|
+
transition(snapshot, event, actorScope) {
|
|
293
|
+
const usesInertScope = !actorScope;
|
|
294
|
+
const resolvedActorScope = actorScope ?? createInertActorScope(this, snapshot);
|
|
295
|
+
if (usesInertScope) setInertActorScopeSnapshot(resolvedActorScope, snapshot, false);
|
|
296
|
+
if (this.validator) {
|
|
297
|
+
const sourceRef = actorScope && actorScope.self._lastSourceRef;
|
|
298
|
+
const eventOrigin = sourceRef ? "actor" : "external";
|
|
299
|
+
const error = this.validator.check({
|
|
300
|
+
kind: "event",
|
|
301
|
+
logic: this,
|
|
302
|
+
event,
|
|
303
|
+
eventOrigin
|
|
304
|
+
});
|
|
305
|
+
if (error) return [snapshot, [createDeadLetterEffect(resolvedActorScope, sourceRef, event, "invalidEvent", {
|
|
306
|
+
issues: error.issues,
|
|
307
|
+
error
|
|
308
|
+
})]];
|
|
309
|
+
}
|
|
310
|
+
beginSpawnAllocation(resolvedActorScope);
|
|
311
|
+
const fastSnapshot = this._transitionFast(snapshot, event, resolvedActorScope);
|
|
312
|
+
if (fastSnapshot) {
|
|
313
|
+
if (usesInertScope) setInertActorScopeSnapshot(resolvedActorScope, fastSnapshot, false);
|
|
314
|
+
const returnedSnapshot = usesInertScope && fastSnapshot !== snapshot ? attachSnapshotActorRef(resolvedActorScope, fastSnapshot) : this._attachPureActorRef(fastSnapshot, resolvedActorScope);
|
|
315
|
+
if (this.validator) assertValid(this.validator, {
|
|
316
|
+
kind: "result",
|
|
317
|
+
logic: this,
|
|
318
|
+
snapshot: returnedSnapshot,
|
|
319
|
+
effects: []
|
|
320
|
+
});
|
|
321
|
+
return [returnedSnapshot, []];
|
|
322
|
+
}
|
|
323
|
+
const { snapshot: nextSnapshot, microsteps } = macrostep(snapshot, event, resolvedActorScope, []);
|
|
324
|
+
if (usesInertScope) setInertActorScopeSnapshot(resolvedActorScope, nextSnapshot, false);
|
|
325
|
+
const returnedSnapshot = usesInertScope ? nextSnapshot === snapshot ? nextSnapshot : attachSnapshotActorRef(resolvedActorScope, nextSnapshot) : this._attachPureActorRef(nextSnapshot, resolvedActorScope);
|
|
326
|
+
const effects = this._collectEffects(microsteps);
|
|
327
|
+
if (this.validator) assertValid(this.validator, {
|
|
328
|
+
kind: "result",
|
|
329
|
+
logic: this,
|
|
330
|
+
snapshot: returnedSnapshot,
|
|
331
|
+
effects
|
|
332
|
+
});
|
|
333
|
+
return [returnedSnapshot, effects];
|
|
334
|
+
}
|
|
335
|
+
_collectEffects(microsteps) {
|
|
336
|
+
return microsteps.flatMap(([, actions]) => actions);
|
|
337
|
+
}
|
|
338
|
+
_attachPureActorRef(snapshot, actorScope, skipInitializingActor = false) {
|
|
339
|
+
if (isInertActorScope(actorScope)) return snapshot;
|
|
340
|
+
if (skipInitializingActor && actorScope.self._actorScope === actorScope && actorScope.self._snapshot === void 0) return snapshot;
|
|
341
|
+
setSnapshotActorRef(snapshot, actorScope.self, actorScope.system);
|
|
342
|
+
return snapshot;
|
|
343
|
+
}
|
|
344
|
+
_transitionFast(snapshot, event, actorScope) {
|
|
345
|
+
if (snapshot.status !== "active" || typeof snapshot.value !== "string" || this.root.always?.length) return;
|
|
346
|
+
const sourceNode = this.root.states[snapshot.value];
|
|
347
|
+
if (!sourceNode || sourceNode.type !== "atomic" || sourceNode.exit || sourceNode.invoke.length || sourceNode.always?.length || sourceNode.after?.length) return;
|
|
348
|
+
const transitions = sourceNode.transitions.get(event.type);
|
|
349
|
+
if (transitions?.length !== 1) return;
|
|
350
|
+
const selected = transitions[0];
|
|
351
|
+
if (!selected) return;
|
|
352
|
+
if (selected.guard || selected.actions || selected.to || selected.reenter || selected.input || typeof selected.context === "function" || selected.target && selected.target.length !== 1) return;
|
|
353
|
+
const targetNode = selected.target?.[0] ?? sourceNode;
|
|
354
|
+
const stateChanged = targetNode !== sourceNode;
|
|
355
|
+
if (targetNode.parent !== this.root || targetNode.type !== "atomic" || stateChanged && (targetNode.entry || targetNode.invoke.length || targetNode.always?.length || targetNode.after?.length)) return;
|
|
356
|
+
const context = selected.context !== void 0 ? {
|
|
357
|
+
...snapshot.context,
|
|
358
|
+
...selected.context
|
|
359
|
+
} : snapshot.context;
|
|
360
|
+
if (!isInertActorScope(actorScope) && (actorScope.system._hasInspectionObservers?.() ?? true)) {
|
|
361
|
+
const collectedMicrosteps = actorScope.self._collectedMicrosteps || [];
|
|
362
|
+
collectedMicrosteps.push(selected);
|
|
363
|
+
actorScope.self._collectedMicrosteps = collectedMicrosteps;
|
|
364
|
+
}
|
|
365
|
+
return cloneMachineSnapshot(snapshot, {
|
|
366
|
+
...context !== snapshot.context ? { context } : {},
|
|
367
|
+
...stateChanged ? { _nodes: [this.root, targetNode] } : {}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Determines the next state given the current `state` and `event`. Calculates
|
|
372
|
+
* a microstep.
|
|
373
|
+
*
|
|
374
|
+
* @param state The current state
|
|
375
|
+
* @param event The received event
|
|
376
|
+
*/
|
|
377
|
+
microstep(snapshot, event, actorScope) {
|
|
378
|
+
const { microsteps } = macrostep(snapshot, event, actorScope, []);
|
|
379
|
+
const snapshots = new Array(microsteps.length);
|
|
380
|
+
for (const [i, microstep] of microsteps.entries()) snapshots[i] = microstep[0];
|
|
381
|
+
return snapshots;
|
|
382
|
+
}
|
|
383
|
+
getTransitionData(snapshot, event, actorScope, selectionResults) {
|
|
384
|
+
this._microstepHooks?.begin(actorScope.self);
|
|
385
|
+
return transitionNode(this.root, snapshot.value, snapshot, event, actorScope, selectionResults) || [];
|
|
386
|
+
}
|
|
387
|
+
isInternalEventType(eventType) {
|
|
388
|
+
if (eventType === "xstate.timer") return true;
|
|
389
|
+
for (const descriptor of this.internalEventDescriptors) if (matchesEventDescriptor(eventType, descriptor)) return true;
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Determines whether sending the `event` to the given snapshot would select a
|
|
394
|
+
* non-forbidden transition. Backs `snapshot.can(...)`; lives here so that
|
|
395
|
+
* non-machine bundles don't pay for the transition-resolution machinery.
|
|
396
|
+
*
|
|
397
|
+
* @internal
|
|
398
|
+
*/
|
|
399
|
+
_canTransition(snapshot, event) {
|
|
400
|
+
const emptyActorScope = getEmptyCanActorScope();
|
|
401
|
+
const transitionData = this.getTransitionData(snapshot, event, emptyActorScope);
|
|
402
|
+
if (!transitionData?.length) return false;
|
|
403
|
+
for (const transition of transitionData) {
|
|
404
|
+
if (transition.target !== void 0) return true;
|
|
405
|
+
const res = getTransitionResult(transition, snapshot, event, emptyActorScope, { resolveActions: false });
|
|
406
|
+
if (res.targets?.length || res.context || hasEffect(transition, snapshot.context, event, snapshot, emptyActorScope)) return true;
|
|
407
|
+
}
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Returns the error event that the actor should transition with to recover
|
|
412
|
+
* from an execution error, if any active state node declares `onError`.
|
|
413
|
+
*
|
|
414
|
+
* @internal
|
|
415
|
+
*/
|
|
416
|
+
getExecutionErrorEvent(snapshot, error) {
|
|
417
|
+
if (snapshot?.status !== "active" || !snapshot.nodes?.some((stateNode) => stateNode.config.onError)) return;
|
|
418
|
+
return createErrorPlatformEvent("execution", error);
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* The initial state _before_ evaluating any microsteps. This "pre-initial"
|
|
422
|
+
* state is provided to initial actions executed in the initial state.
|
|
423
|
+
*
|
|
424
|
+
* @internal
|
|
425
|
+
*/
|
|
426
|
+
_getPreInitialState(actorScope, initEvent) {
|
|
427
|
+
const { context } = this.config;
|
|
428
|
+
const preInitial = createMachineSnapshot({
|
|
429
|
+
context: typeof context !== "function" && context ? context : {},
|
|
430
|
+
_nodes: [this.root],
|
|
431
|
+
children: {},
|
|
432
|
+
status: "active"
|
|
433
|
+
}, this);
|
|
434
|
+
if (typeof context === "function") {
|
|
435
|
+
const children = {};
|
|
436
|
+
const spawn = createSpawner(actorScope, this.sources.actors, children);
|
|
437
|
+
const resolvedContext = context(withActorSelf({
|
|
438
|
+
spawn,
|
|
439
|
+
input: initEvent.input,
|
|
440
|
+
actors: this.sources.actors
|
|
441
|
+
}, actorScope));
|
|
442
|
+
const [nextState] = resolveActionsWithContext(preInitial, initEvent, actorScope, []);
|
|
443
|
+
if (resolvedContext) nextState.context = resolvedContext;
|
|
444
|
+
if (Object.keys(children).length > 0) {
|
|
445
|
+
nextState.children = {
|
|
446
|
+
...nextState.children,
|
|
447
|
+
...children
|
|
448
|
+
};
|
|
449
|
+
const counters = takeSpawnAllocationCounters(actorScope);
|
|
450
|
+
if (counters) nextState._nextActorIds = mergeActorIdCounters(nextState._nextActorIds, counters);
|
|
451
|
+
}
|
|
452
|
+
return nextState;
|
|
453
|
+
}
|
|
454
|
+
return preInitial;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Returns the initial `State` instance, with reference to `self` as an
|
|
458
|
+
* `ActorRef`.
|
|
459
|
+
*/
|
|
460
|
+
getInitialSnapshot(actorScope, input) {
|
|
461
|
+
return this.initialTransition(input, actorScope)[0];
|
|
462
|
+
}
|
|
463
|
+
initialTransition(input, actorScope) {
|
|
464
|
+
if (this.validator) assertValid(this.validator, {
|
|
465
|
+
kind: "input",
|
|
466
|
+
logic: this,
|
|
467
|
+
input
|
|
468
|
+
});
|
|
469
|
+
const usesInertScope = !actorScope;
|
|
470
|
+
const resolvedActorScope = actorScope ?? createInertActorScope(this);
|
|
471
|
+
beginSpawnAllocation(resolvedActorScope);
|
|
472
|
+
const initEvent = createInitEvent(input);
|
|
473
|
+
const internalQueue = [];
|
|
474
|
+
const finalizeInitialResult = (macroState, microsteps) => {
|
|
475
|
+
if (usesInertScope) setInertActorScopeSnapshot(resolvedActorScope, macroState, false);
|
|
476
|
+
const returnedSnapshot = usesInertScope ? attachSnapshotActorRef(resolvedActorScope, macroState) : this._attachPureActorRef(macroState, resolvedActorScope, true);
|
|
477
|
+
const effects = this._collectEffects(microsteps);
|
|
478
|
+
if (this.validator && macroState.status !== "error") assertValid(this.validator, {
|
|
479
|
+
kind: "result",
|
|
480
|
+
logic: this,
|
|
481
|
+
snapshot: returnedSnapshot,
|
|
482
|
+
effects
|
|
483
|
+
});
|
|
484
|
+
return [returnedSnapshot, effects];
|
|
485
|
+
};
|
|
486
|
+
let preInitialState;
|
|
487
|
+
try {
|
|
488
|
+
preInitialState = this._getPreInitialState(resolvedActorScope, initEvent);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
return finalizeInitialResult(cloneMachineSnapshot(createMachineSnapshot({
|
|
491
|
+
context: typeof this.config.context !== "function" && this.config.context ? this.config.context : {},
|
|
492
|
+
_nodes: [this.root],
|
|
493
|
+
children: {},
|
|
494
|
+
status: "active"
|
|
495
|
+
}, this), {
|
|
496
|
+
status: "error",
|
|
497
|
+
error
|
|
498
|
+
}), []);
|
|
499
|
+
}
|
|
500
|
+
const contextSpawnEffects = Object.values(preInitialState.children).filter(Boolean).map((actor) => createSpawnEffect(actor));
|
|
501
|
+
try {
|
|
502
|
+
const [nextState, initialActions] = initialMicrostep(this.root, preInitialState, resolvedActorScope, initEvent, internalQueue);
|
|
503
|
+
const { snapshot: macroState, microsteps } = macrostep(nextState, initEvent, resolvedActorScope, internalQueue, [[nextState, [...contextSpawnEffects, ...initialActions]]]);
|
|
504
|
+
return finalizeInitialResult(macroState, microsteps);
|
|
505
|
+
} catch (err) {
|
|
506
|
+
if (!this.root.config.onError) throw err;
|
|
507
|
+
const errorEvent = createErrorPlatformEvent("execution", err);
|
|
508
|
+
const errorMacrostep = macrostep(preInitialState, errorEvent, resolvedActorScope, [], [[preInitialState, contextSpawnEffects]]);
|
|
509
|
+
return finalizeInitialResult(errorMacrostep.snapshot, errorMacrostep.microsteps);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
start(snapshot, actorScope) {
|
|
513
|
+
if (!snapshot?.children) return;
|
|
514
|
+
const children = snapshot.children;
|
|
515
|
+
for (const child of Object.values(children)) if (child._rehydrated && child.getSnapshot?.().status === "active") {
|
|
516
|
+
if (actorScope) actorScope.actionExecutor(createStartEffect(child));
|
|
517
|
+
else child.system.startActor(child);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
getStateNodeById(stateId) {
|
|
521
|
+
const fullPath = toStatePath(stateId);
|
|
522
|
+
const relativePath = fullPath.slice(1);
|
|
523
|
+
const firstSegment = fullPath[0];
|
|
524
|
+
if (firstSegment === void 0) throw new Error(`Invalid state ID '${stateId}'`);
|
|
525
|
+
const resolvedStateId = isStateId(firstSegment) ? firstSegment.slice(1) : firstSegment;
|
|
526
|
+
const stateNode = this.idMap.get(resolvedStateId);
|
|
527
|
+
if (!stateNode) throw new Error(`Child state node '#${resolvedStateId}' does not exist on machine '${this.id}'`);
|
|
528
|
+
return getStateNodeByPath(stateNode, relativePath);
|
|
529
|
+
}
|
|
530
|
+
getPersistedSnapshot(snapshot, options, warn) {
|
|
531
|
+
return getPersistedSnapshot(snapshot, options, warn);
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* The original JSON definition this machine was created from (set by
|
|
535
|
+
* `createMachineFromConfig`), if any. Used by `serializeMachine` for lossless
|
|
536
|
+
* round-trips.
|
|
537
|
+
*
|
|
538
|
+
* @internal
|
|
539
|
+
*/
|
|
540
|
+
_json;
|
|
541
|
+
/**
|
|
542
|
+
* @internal Builds a machine-shaped `'error'` snapshot (root configuration,
|
|
543
|
+
* no children) for a persisted snapshot that failed to restore, so
|
|
544
|
+
* consumers keep `matches()`, `can()` and friends.
|
|
545
|
+
*/
|
|
546
|
+
_createRestoreErrorSnapshot(persisted, error) {
|
|
547
|
+
const context = persisted?.context;
|
|
548
|
+
return cloneMachineSnapshot(createMachineSnapshot({
|
|
549
|
+
context: context && typeof context === "object" ? context : {},
|
|
550
|
+
_nodes: [this.root],
|
|
551
|
+
children: {},
|
|
552
|
+
status: "active"
|
|
553
|
+
}, this), {
|
|
554
|
+
status: "error",
|
|
555
|
+
error
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
restoreSnapshot(snapshot, actorScope) {
|
|
559
|
+
const usesInertScope = !actorScope;
|
|
560
|
+
const resolvedActorScope = actorScope ?? createInertActorScope(this);
|
|
561
|
+
const snapshotMachine = snapshot.machine;
|
|
562
|
+
const persistedMachine = snapshotMachine && typeof snapshotMachine === "object" && typeof snapshotMachine.transition === "function" && "root" in snapshotMachine ? void 0 : snapshotMachine;
|
|
563
|
+
const legacyPersistedVersion = snapshot.version;
|
|
564
|
+
const persistedVersion = typeof persistedMachine?.version === "string" ? persistedMachine.version : legacyPersistedVersion;
|
|
565
|
+
if (legacyPersistedVersion !== void 0 && persistedMachine?.version !== void 0 && persistedMachine.version !== legacyPersistedVersion) throw new Error(`Persisted snapshot version '${legacyPersistedVersion}' conflicts with machine version '${persistedMachine.version}'.`);
|
|
566
|
+
if (persistedMachine && typeof persistedMachine.id === "string" && persistedMachine.id !== this.id) throw new Error(`Machine ID mismatch: persisted snapshot machine '${persistedMachine.id}' does not match '${this.id}'.`);
|
|
567
|
+
if (persistedVersion !== this.version) {
|
|
568
|
+
const migrate = this.config.migrate;
|
|
569
|
+
if (typeof migrate !== "function") throw new Error(`Persisted snapshot version '${persistedVersion}' does not match machine version '${this.version}'.`);
|
|
570
|
+
snapshot = migrate(snapshot, persistedVersion);
|
|
571
|
+
}
|
|
572
|
+
const snapshotData = snapshot;
|
|
573
|
+
const children = {};
|
|
574
|
+
const snapshotChildren = snapshotData.children;
|
|
575
|
+
for (const [actorId, actorData] of Object.entries(snapshotChildren)) {
|
|
576
|
+
if (actorData.remote === true && actorData.address !== void 0) {
|
|
577
|
+
if (typeof actorData.src !== "string") throw new Error(`Unable to restore remote child '${actorId}': a child referenced by address requires a registered source key.`);
|
|
578
|
+
const handle = createRemoteActorRef(resolvedActorScope.system, {
|
|
579
|
+
id: actorId,
|
|
580
|
+
address: actorData.address,
|
|
581
|
+
src: actorData.src,
|
|
582
|
+
parent: resolvedActorScope.self,
|
|
583
|
+
registryKey: actorData.registryKey,
|
|
584
|
+
syncSnapshot: actorData.syncSnapshot,
|
|
585
|
+
incarnation: actorData.incarnation
|
|
586
|
+
});
|
|
587
|
+
if (actorData.registryKey) resolvedActorScope.system._set(actorData.registryKey, handle);
|
|
588
|
+
children[actorId] = handle;
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
const childState = actorData.snapshot;
|
|
592
|
+
const src = actorData.src;
|
|
593
|
+
const logic = typeof src === "string" ? resolveReferencedActor(this, src) : src;
|
|
594
|
+
if (!logic) throw new Error(`Unable to restore child actor '${actorId}': child source '${typeof src === "string" ? src : "<unknown>"}' is not provided in machine '${this.id}'.`);
|
|
595
|
+
const actor = resolvedActorScope.system.createActorRef(logic, {
|
|
596
|
+
id: actorId,
|
|
597
|
+
parent: resolvedActorScope.self,
|
|
598
|
+
...actorData.syncSnapshot !== void 0 && { syncSnapshot: actorData.syncSnapshot },
|
|
599
|
+
...childState !== void 0 && { snapshot: childState },
|
|
600
|
+
src,
|
|
601
|
+
...actorData.registryKey !== void 0 && { registryKey: actorData.registryKey }
|
|
602
|
+
});
|
|
603
|
+
actor._rehydrated = true;
|
|
604
|
+
children[actorId] = actor;
|
|
605
|
+
}
|
|
606
|
+
const timers = {};
|
|
607
|
+
const persistedTimers = snapshotData.timers ?? {};
|
|
608
|
+
for (const [id, timer] of Object.entries(persistedTimers)) {
|
|
609
|
+
let event = timer.event;
|
|
610
|
+
if (event.type === "xstate.timeout.actor") {
|
|
611
|
+
const actorId = event["actorId"];
|
|
612
|
+
const child = children[actorId];
|
|
613
|
+
if (child) event = createInvokeTimeoutEvent(actorId, child.sessionId);
|
|
614
|
+
}
|
|
615
|
+
const target = typeof timer.target === "string" ? timer.target === "self" ? "self" : children[timer.target] : resolvedActorScope.self._parent;
|
|
616
|
+
if (!target) {
|
|
617
|
+
const targetDescription = typeof timer.target === "string" ? timer.target : timer.target.type;
|
|
618
|
+
throw new Error(`Unable to restore timer '${id}': target actor '${targetDescription}' is unavailable.`);
|
|
619
|
+
}
|
|
620
|
+
timers[id] = {
|
|
621
|
+
...timer,
|
|
622
|
+
event,
|
|
623
|
+
target
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
const reviveHistoryValue = (historyValue) => {
|
|
627
|
+
if (!historyValue || typeof historyValue !== "object") return {};
|
|
628
|
+
const revived = {};
|
|
629
|
+
for (const [key, arr] of Object.entries(historyValue)) for (const item of arr) {
|
|
630
|
+
let resolved;
|
|
631
|
+
if (item instanceof StateNode) resolved = item;
|
|
632
|
+
else try {
|
|
633
|
+
resolved = this.root.machine.getStateNodeById(item.id);
|
|
634
|
+
} catch {}
|
|
635
|
+
if (!resolved) continue;
|
|
636
|
+
revived[key] ??= [];
|
|
637
|
+
revived[key].push(resolved);
|
|
638
|
+
}
|
|
639
|
+
return revived;
|
|
640
|
+
};
|
|
641
|
+
const revivedHistoryValue = reviveHistoryValue(snapshotData.historyValue);
|
|
642
|
+
const validateStateValue = (stateValue, node, path) => {
|
|
643
|
+
const missingStateError = (statePath) => /* @__PURE__ */ new Error(`Persisted snapshot references state '${statePath.join(".")}' which does not exist on machine '${this.id}'.`);
|
|
644
|
+
if (typeof stateValue === "string") {
|
|
645
|
+
if (!node.states[stateValue]) throw missingStateError(path.concat(stateValue));
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (!stateValue || typeof stateValue !== "object") return;
|
|
649
|
+
for (const key of Object.keys(stateValue)) {
|
|
650
|
+
const childNode = node.states[key];
|
|
651
|
+
if (!childNode) throw missingStateError(path.concat(key));
|
|
652
|
+
validateStateValue(stateValue[key], childNode, path.concat(key));
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
validateStateValue(snapshotData.value, this.root, []);
|
|
656
|
+
const nodes = Array.from(getAllStateNodes(getStateNodes(this.root, snapshotData.value)));
|
|
657
|
+
const { version: _persistedSnapshotVersion, _nextActorId: _legacyNextActorId, ...persistedRest } = snapshot;
|
|
658
|
+
let restoredCounters = persistedRest._nextActorIds;
|
|
659
|
+
for (const childId of Object.keys(snapshotChildren)) {
|
|
660
|
+
const generated = parseGeneratedActorId(childId);
|
|
661
|
+
if (generated && (restoredCounters?.[generated.prefix] ?? 0) <= generated.index) restoredCounters = {
|
|
662
|
+
...restoredCounters,
|
|
663
|
+
[generated.prefix]: generated.index + 1
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
const restoredSnapshot = createMachineSnapshot({
|
|
667
|
+
...persistedRest,
|
|
668
|
+
children,
|
|
669
|
+
timers,
|
|
670
|
+
_nextActorIds: restoredCounters,
|
|
671
|
+
_nodes: nodes,
|
|
672
|
+
value: snapshotData.value,
|
|
673
|
+
historyValue: revivedHistoryValue,
|
|
674
|
+
_stateInputs: snapshotData.stateInputs ?? {}
|
|
675
|
+
}, this, resolvedActorScope.self);
|
|
676
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
677
|
+
function reviveContext(contextPart) {
|
|
678
|
+
if (seen.has(contextPart)) return;
|
|
679
|
+
seen.add(contextPart);
|
|
680
|
+
for (const key of Object.keys(contextPart)) {
|
|
681
|
+
const value = contextPart[key];
|
|
682
|
+
if (value && typeof value === "object") {
|
|
683
|
+
if ("xstate$type" in value && value.xstate$type === "actorRef") {
|
|
684
|
+
contextPart[key] = children[value.id];
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
reviveContext(value);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
reviveContext(restoredSnapshot.context);
|
|
692
|
+
if (usesInertScope) {
|
|
693
|
+
setInertActorScopeSnapshot(resolvedActorScope, restoredSnapshot, false);
|
|
694
|
+
return attachSnapshotActorRef(resolvedActorScope, restoredSnapshot);
|
|
695
|
+
}
|
|
696
|
+
return this._attachPureActorRef(restoredSnapshot, resolvedActorScope);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
// src/createMachine.ts
|
|
700
|
+
function createMachine(config) {
|
|
701
|
+
return new StateMachine(config);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Builds a machine from a config produced by a compiler (JSON, SCXML) without
|
|
705
|
+
* running the author-config v5 diagnostics.
|
|
706
|
+
*
|
|
707
|
+
* @experimental Used by `@xstate/scxml`; not part of the stable API.
|
|
708
|
+
*/
|
|
709
|
+
function createMachineFromCompiledConfig(config) {
|
|
710
|
+
return new StateMachine(config);
|
|
711
|
+
}
|
|
712
|
+
/** @public */
|
|
713
|
+
function createStateConfig(config) {
|
|
714
|
+
return config;
|
|
715
|
+
}
|
|
716
|
+
// src/createMachineFromConfig.ts
|
|
717
|
+
function delayToMs(delay) {
|
|
718
|
+
const parsedDelay = parseDelayToMilliseconds(delay);
|
|
719
|
+
if (parsedDelay !== void 0) return parsedDelay;
|
|
720
|
+
return typeof delay === "string" ? parseFloat(delay) || 0 : delay;
|
|
721
|
+
}
|
|
722
|
+
function isExpression(value) {
|
|
723
|
+
return !!value && typeof value === "object" && typeof value["@expr"] === "string";
|
|
724
|
+
}
|
|
725
|
+
function isCode(value) {
|
|
726
|
+
return !!value && typeof value === "object" && typeof value["@code"] === "string";
|
|
727
|
+
}
|
|
728
|
+
function isResolvable(value) {
|
|
729
|
+
return isExpression(value) || isCode(value);
|
|
730
|
+
}
|
|
731
|
+
function isBuiltInActionType(type) {
|
|
732
|
+
return type.startsWith("@xstate.");
|
|
733
|
+
}
|
|
734
|
+
function toPath(parent, key) {
|
|
735
|
+
return typeof key === "number" ? `${parent}[${key}]` : `${parent}.${key}`;
|
|
736
|
+
}
|
|
737
|
+
function extractSerializableDelays(delays) {
|
|
738
|
+
const result = {};
|
|
739
|
+
if (!delays) return result;
|
|
740
|
+
for (const key of Object.keys(delays)) {
|
|
741
|
+
const value = delays[key];
|
|
742
|
+
if (typeof value === "number") result[key] = value;
|
|
743
|
+
else if (value && typeof value === "object" && typeof value.duration === "number") result[key] = value.duration;
|
|
744
|
+
}
|
|
745
|
+
return result;
|
|
746
|
+
}
|
|
747
|
+
function mergeSources(json, sources) {
|
|
748
|
+
return {
|
|
749
|
+
actions: sources.actions ?? {},
|
|
750
|
+
guards: sources.guards ?? {},
|
|
751
|
+
actors: sources.actors ?? {},
|
|
752
|
+
delays: {
|
|
753
|
+
...extractSerializableDelays(json.delays),
|
|
754
|
+
...sources.delays ?? {}
|
|
755
|
+
},
|
|
756
|
+
evaluators: sources.evaluators ?? {}
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function createExpressionResolver(expressionLanguage, sources) {
|
|
760
|
+
function getEvaluator(value, path) {
|
|
761
|
+
const lang = value["@lang"] ?? expressionLanguage;
|
|
762
|
+
if (!lang) throw new Error(`Missing @exprLang for expression at ${path}`);
|
|
763
|
+
const evaluator = sources.evaluators[lang];
|
|
764
|
+
if (!evaluator) throw new Error(`Missing evaluator for @lang '${lang}' at ${path}`);
|
|
765
|
+
return evaluator;
|
|
766
|
+
}
|
|
767
|
+
function evaluateResolvable(value, slot, scope, path) {
|
|
768
|
+
const kind = isExpression(value) ? "expr" : "code";
|
|
769
|
+
const source = isExpression(value) ? value["@expr"] : value["@code"];
|
|
770
|
+
return getEvaluator(value, path)({
|
|
771
|
+
source,
|
|
772
|
+
kind,
|
|
773
|
+
slot,
|
|
774
|
+
scope,
|
|
775
|
+
path
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
function resolveValue(value, slot, scope, path) {
|
|
779
|
+
if (isResolvable(value)) return evaluateResolvable(value, slot, scope, path);
|
|
780
|
+
if (Array.isArray(value)) return value.map((item, index) => resolveValue(item, slot, scope, toPath(path, index)));
|
|
781
|
+
if (!value || typeof value !== "object") return value;
|
|
782
|
+
const result = {};
|
|
783
|
+
for (const key of Object.keys(value)) result[key] = resolveValue(value[key], slot, scope, toPath(path, key));
|
|
784
|
+
return result;
|
|
785
|
+
}
|
|
786
|
+
function makeScope(x, extra) {
|
|
787
|
+
return {
|
|
788
|
+
context: x.context,
|
|
789
|
+
event: x.event,
|
|
790
|
+
input: x.input,
|
|
791
|
+
self: x.self,
|
|
792
|
+
children: x.children,
|
|
793
|
+
params: x.params,
|
|
794
|
+
...extra
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
function getDurationConfig(value, path) {
|
|
798
|
+
if (!isResolvable(value)) return value;
|
|
799
|
+
return (args) => delayToMs(resolveValue(value, "delay", makeScope(args), path));
|
|
800
|
+
}
|
|
801
|
+
function assertResolvable(value, path) {
|
|
802
|
+
if (isResolvable(value)) {
|
|
803
|
+
getEvaluator(value, path);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (Array.isArray(value)) {
|
|
807
|
+
value.forEach((item, index) => assertResolvable(item, toPath(path, index)));
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (!value || typeof value !== "object") return;
|
|
811
|
+
for (const key of Object.keys(value)) assertResolvable(value[key], toPath(path, key));
|
|
812
|
+
}
|
|
813
|
+
return {
|
|
814
|
+
evaluateResolvable,
|
|
815
|
+
resolveValue,
|
|
816
|
+
makeScope,
|
|
817
|
+
getDurationConfig,
|
|
818
|
+
assertResolvable
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
function toActionArray(actions) {
|
|
822
|
+
return actions === void 0 ? [] : Array.isArray(actions) ? actions : [actions];
|
|
823
|
+
}
|
|
824
|
+
function validateChoiceConfig(choice, path) {
|
|
825
|
+
if (!Array.isArray(choice)) return;
|
|
826
|
+
choice.forEach((branch, index) => {
|
|
827
|
+
if (branch.when === void 0 && index !== choice.length - 1) throw new Error(`Choice fallback branch at ${path}[${index}] must be last.`);
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
function assertMachineJSON(json, resolvedSources, expressionResolver) {
|
|
831
|
+
const { assertResolvable } = expressionResolver;
|
|
832
|
+
function assertCondition(condition, path) {
|
|
833
|
+
if (!condition) return;
|
|
834
|
+
if (isResolvable(condition)) {
|
|
835
|
+
assertResolvable(condition, path);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
assertResolvable(condition.params, `${path}.params`);
|
|
839
|
+
const guard = json.guards?.[condition.type];
|
|
840
|
+
if (guard) {
|
|
841
|
+
assertCondition(guard.when, `$.guards.${condition.type}.when`);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
if (!resolvedSources.guards[condition.type] && !["xstate.stateIn", "xstate.not"].includes(condition.type)) throw new Error(`Missing guard source "${condition.type}"`);
|
|
845
|
+
}
|
|
846
|
+
function assertAction(action, path, stack = []) {
|
|
847
|
+
if (isResolvable(action)) {
|
|
848
|
+
assertResolvable(action, path);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (!action || typeof action.type !== "string") throw new Error(`Invalid action at ${path}`);
|
|
852
|
+
assertResolvable(action.params, `${path}.params`);
|
|
853
|
+
if (isBuiltInActionType(action.type)) {
|
|
854
|
+
assertResolvable(action, path);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
const definition = json.actions?.[action.type];
|
|
858
|
+
if (definition) {
|
|
859
|
+
if (stack.includes(action.type)) throw new Error(`Circular action reference: ${stack.concat(action.type).join(" -> ")}`);
|
|
860
|
+
const definitions = Array.isArray(definition) ? definition : [definition];
|
|
861
|
+
definitions.forEach((item, index) => assertAction(item, `${path}.actions.${action.type}${definitions.length > 1 ? `[${index}]` : ""}`, stack.concat(action.type)));
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (!resolvedSources.actions[action.type]) throw new Error(`Missing action source "${action.type}"`);
|
|
865
|
+
}
|
|
866
|
+
function assertActions(actions, path) {
|
|
867
|
+
toActionArray(actions).forEach((action, index) => assertAction(action, `${path}[${index}]`));
|
|
868
|
+
}
|
|
869
|
+
function assertTransition(transition, path) {
|
|
870
|
+
(Array.isArray(transition) ? transition : transition ? [transition] : []).forEach((t, index) => {
|
|
871
|
+
const transitionPath = Array.isArray(transition) ? `${path}[${index}]` : path;
|
|
872
|
+
if (isResolvable(t)) {
|
|
873
|
+
assertResolvable(t, transitionPath);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
assertCondition(t.guard, `${transitionPath}.guard`);
|
|
877
|
+
assertActions(t.actions, `${transitionPath}.actions`);
|
|
878
|
+
assertResolvable(t.context, `${transitionPath}.context`);
|
|
879
|
+
assertResolvable(t.input, `${transitionPath}.input`);
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
function assertStateNode(node, path) {
|
|
883
|
+
if ((node.type === "history" || node.history !== void 0) && !(typeof node.target === "string" && node.target.trim().length > 0 || Array.isArray(node.target) && node.target.length > 0 && node.target.every((target) => target.trim().length > 0))) throw new Error(`History state at ${path} must declare a non-empty target.`);
|
|
884
|
+
assertResolvable(node.context, `${path}.context`);
|
|
885
|
+
assertResolvable(node.input, `${path}.input`);
|
|
886
|
+
assertResolvable(node.output, `${path}.output`);
|
|
887
|
+
assertResolvable(node.timeout, `${path}.timeout`);
|
|
888
|
+
assertActions(node.entry, `${path}.entry`);
|
|
889
|
+
assertActions(node.exit, `${path}.exit`);
|
|
890
|
+
if (node.type === "choice") {
|
|
891
|
+
if (!node.choice) throw new Error(`Choice state at ${path} must declare choice.`);
|
|
892
|
+
if (Array.isArray(node.choice)) {
|
|
893
|
+
validateChoiceConfig(node.choice, `${path}.choice`);
|
|
894
|
+
node.choice.forEach((branch, index) => {
|
|
895
|
+
assertCondition(branch.when, `${path}.choice[${index}].when`);
|
|
896
|
+
assertResolvable(branch.context, `${path}.choice[${index}].context`);
|
|
897
|
+
assertResolvable(branch.input, `${path}.choice[${index}].input`);
|
|
898
|
+
});
|
|
899
|
+
} else assertResolvable(node.choice, `${path}.choice`);
|
|
900
|
+
}
|
|
901
|
+
if (isResolvable(node.route)) assertResolvable(node.route, `${path}.route`);
|
|
902
|
+
if (node.invoke) (Array.isArray(node.invoke) ? node.invoke : [node.invoke]).forEach((invoke, index) => {
|
|
903
|
+
const invokePath = `${path}.invoke${Array.isArray(node.invoke) ? `[${index}]` : ""}`;
|
|
904
|
+
if (!resolvedSources.actors[invoke.src]) throw new Error(`Missing actor source "${invoke.src}"`);
|
|
905
|
+
assertResolvable(invoke.input, `${invokePath}.input`);
|
|
906
|
+
assertResolvable(invoke.timeout, `${invokePath}.timeout`);
|
|
907
|
+
assertTransition(invoke.onDone, `${invokePath}.onDone`);
|
|
908
|
+
assertTransition(invoke.onError, `${invokePath}.onError`);
|
|
909
|
+
assertTransition(invoke.onSnapshot, `${invokePath}.onSnapshot`);
|
|
910
|
+
assertTransition(invoke.onTimeout, `${invokePath}.onTimeout`);
|
|
911
|
+
});
|
|
912
|
+
if (node.on) for (const descriptor of Object.keys(node.on)) assertTransition(node.on[descriptor], `${path}.on.${descriptor}`);
|
|
913
|
+
if (node.after) for (const delay of Object.keys(node.after)) {
|
|
914
|
+
if (Number.isNaN(Number(delay)) && parseDelayToMilliseconds(delay) === void 0 && !json.delays?.[delay] && !resolvedSources.delays[delay]) throw new Error(`Missing delay source "${delay}"`);
|
|
915
|
+
assertTransition(node.after[delay], `${path}.after.${delay}`);
|
|
916
|
+
}
|
|
917
|
+
assertTransition(node.always, `${path}.always`);
|
|
918
|
+
assertTransition(node.onError, `${path}.onError`);
|
|
919
|
+
assertTransition(node.onTimeout, `${path}.onTimeout`);
|
|
920
|
+
if (node.states) for (const [key, state] of Object.entries(node.states)) assertStateNode(state, `${path}.states.${key}`);
|
|
921
|
+
}
|
|
922
|
+
if (json.actions) for (const key of Object.keys(json.actions)) {
|
|
923
|
+
const entry = json.actions[key];
|
|
924
|
+
if (entry === void 0) continue;
|
|
925
|
+
const actions = Array.isArray(entry) ? entry : [entry];
|
|
926
|
+
actions.forEach((action, index) => assertAction(action, `$.actions.${key}${actions.length > 1 ? `[${index}]` : ""}`, [key]));
|
|
927
|
+
}
|
|
928
|
+
if (json.guards) for (const [key, guard] of Object.entries(json.guards)) assertCondition(guard.when, `$.guards.${key}.when`);
|
|
929
|
+
if (json.delays) for (const key of Object.keys(json.delays)) {
|
|
930
|
+
const delay = json.delays[key];
|
|
931
|
+
assertResolvable(delay && typeof delay === "object" && "duration" in delay ? delay.duration : delay, `$.delays.${key}`);
|
|
932
|
+
}
|
|
933
|
+
assertStateNode(json, "$");
|
|
934
|
+
}
|
|
935
|
+
/** @public */
|
|
936
|
+
function createMachineFromConfig(json, sources = {}) {
|
|
937
|
+
const resolvedSources = mergeSources(json, sources);
|
|
938
|
+
const expressionResolver = createExpressionResolver(json["@exprLang"], resolvedSources);
|
|
939
|
+
const { evaluateResolvable, resolveValue, makeScope, getDurationConfig } = expressionResolver;
|
|
940
|
+
function resolveCondition(condition, slot, path) {
|
|
941
|
+
if (!condition) return void 0;
|
|
942
|
+
if (isResolvable(condition)) return (args) => !!evaluateResolvable(condition, slot, makeScope(args), path);
|
|
943
|
+
return (args) => {
|
|
944
|
+
const params = resolveValue(condition.params, slot, makeScope(args), `${path}.params`);
|
|
945
|
+
const declarativeGuard = json.guards?.[condition.type];
|
|
946
|
+
if (declarativeGuard) return !!resolveCondition(declarativeGuard.when, "guard", `$.guards.${condition.type}.when`)?.({
|
|
947
|
+
...args,
|
|
948
|
+
params
|
|
949
|
+
});
|
|
950
|
+
const guardImpl = args.guards?.[condition.type];
|
|
951
|
+
if (!guardImpl) throw new Error(getMissingGuardMessage(condition.type, args.guards ?? {}));
|
|
952
|
+
return guardImpl(args, params);
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function getMissingGuardMessage(type, guards) {
|
|
956
|
+
return `Guard '${type}' is not implemented in machine '${json.id ?? "(machine)"}'. Available guards: ${Object.keys(guards).map((key) => `'${key}'`).join(", ") || "(none)"}.`;
|
|
957
|
+
}
|
|
958
|
+
function makeChoiceConfig(choice, path) {
|
|
959
|
+
if (!choice) return void 0;
|
|
960
|
+
if (isResolvable(choice)) return (args) => evaluateResolvable(choice, "choice", makeScope(args), path);
|
|
961
|
+
validateChoiceConfig(choice, path);
|
|
962
|
+
return (args) => {
|
|
963
|
+
for (let index = 0; index < choice.length; index++) {
|
|
964
|
+
const branch = choice[index];
|
|
965
|
+
if (!branch) continue;
|
|
966
|
+
const guard = resolveCondition(branch.when, "choice", `${path}[${index}].when`);
|
|
967
|
+
if (!guard || guard(args)) return {
|
|
968
|
+
target: branch.target,
|
|
969
|
+
context: branch.context ? resolveValue(branch.context, "transitionContext", makeScope(args), `${path}[${index}].context`) : void 0,
|
|
970
|
+
input: branch.input !== void 0 ? resolveValue(branch.input, "input", makeScope(args), `${path}[${index}].input`) : void 0,
|
|
971
|
+
description: branch.description,
|
|
972
|
+
reenter: branch.reenter,
|
|
973
|
+
meta: branch.meta
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
throw new Error(`Choice state at ${path} did not match any branch.`);
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
function resolveRouteConfig(route, path) {
|
|
980
|
+
if (!route || typeof route === "function") return route;
|
|
981
|
+
if (isResolvable(route)) return (args) => evaluateResolvable(route, "transition", makeScope(args), path);
|
|
982
|
+
const { guard, ...routeConfig } = route;
|
|
983
|
+
if (!guard) return routeConfig;
|
|
984
|
+
const resolvedGuard = resolveCondition({ type: guard }, "guard", path);
|
|
985
|
+
return (args) => resolvedGuard(args) ? routeConfig : void 0;
|
|
986
|
+
}
|
|
987
|
+
function executeActions(actions, x, enq, stack = []) {
|
|
988
|
+
let context;
|
|
989
|
+
for (const action of actions) {
|
|
990
|
+
if (isResolvable(action)) {
|
|
991
|
+
const result = evaluateResolvable(action, "action", makeScope(x, {
|
|
992
|
+
params: x.params,
|
|
993
|
+
enq
|
|
994
|
+
}), "$.actions");
|
|
995
|
+
if (result && typeof result === "object" && "context" in result && result.context) {
|
|
996
|
+
context ??= {};
|
|
997
|
+
Object.assign(context, result.context);
|
|
998
|
+
}
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
if (isBuiltInActionJSON(action)) {
|
|
1002
|
+
switch (action.type) {
|
|
1003
|
+
case "@xstate.raise":
|
|
1004
|
+
enq.raise(resolveValue(action.event, "actionParams", makeScope(x, { params: x.params }), "$.actions.event"), {
|
|
1005
|
+
id: action.id,
|
|
1006
|
+
delay: action.delay
|
|
1007
|
+
});
|
|
1008
|
+
break;
|
|
1009
|
+
case "@xstate.cancel":
|
|
1010
|
+
enq.cancel(action.id);
|
|
1011
|
+
break;
|
|
1012
|
+
case "@xstate.log":
|
|
1013
|
+
enq.log(...action.args);
|
|
1014
|
+
break;
|
|
1015
|
+
case "@xstate.emit":
|
|
1016
|
+
enq.emit(resolveValue(action.event, "actionParams", makeScope(x, { params: x.params }), "$.actions.event"));
|
|
1017
|
+
break;
|
|
1018
|
+
case "@xstate.assign":
|
|
1019
|
+
context ??= {};
|
|
1020
|
+
Object.assign(context, resolveValue(action.context, "transitionContext", makeScope(x, { params: x.params }), "$.actions.context"));
|
|
1021
|
+
}
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const params = resolveValue(action.params, "actionParams", makeScope(x, { params: x.params }), "$.actions.params");
|
|
1025
|
+
const definition = json.actions?.[action.type];
|
|
1026
|
+
if (!definition) {
|
|
1027
|
+
enq(x.actions[action.type], params);
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
if (stack.includes(action.type)) throw new Error(`Circular action reference: ${stack.concat(action.type).join(" -> ")}`);
|
|
1031
|
+
const result = executeActions(Array.isArray(definition) ? definition : [definition], {
|
|
1032
|
+
...x,
|
|
1033
|
+
context: {
|
|
1034
|
+
...x.context,
|
|
1035
|
+
...context
|
|
1036
|
+
},
|
|
1037
|
+
params
|
|
1038
|
+
}, enq, stack.concat(action.type));
|
|
1039
|
+
if (result.context) context = result.context;
|
|
1040
|
+
}
|
|
1041
|
+
return { context: context ? {
|
|
1042
|
+
...x.context,
|
|
1043
|
+
...context
|
|
1044
|
+
} : void 0 };
|
|
1045
|
+
}
|
|
1046
|
+
function iterActions(actions) {
|
|
1047
|
+
const actionArray = toActionArray(actions);
|
|
1048
|
+
return ((x, enq) => executeActions(actionArray, x, enq));
|
|
1049
|
+
}
|
|
1050
|
+
function getTransitionConfig(transition) {
|
|
1051
|
+
const transitions = Array.isArray(transition) ? transition : [transition];
|
|
1052
|
+
return transitions.map((item, index) => {
|
|
1053
|
+
if (isResolvable(item)) return (x, enq) => evaluateResolvable(item, "transition", makeScope(x, { enq }), `$.transition${transitions.length > 1 ? `[${index}]` : ""}`);
|
|
1054
|
+
const context = item.context ? (x) => resolveValue(item.context, "transitionContext", makeScope(x), "$.transition.context") : void 0;
|
|
1055
|
+
const input = item.input !== void 0 ? (x) => resolveValue(item.input, "input", makeScope(x), "$.transition.input") : void 0;
|
|
1056
|
+
const dynamic = !!context || !!item.actions?.length;
|
|
1057
|
+
return {
|
|
1058
|
+
matches: item.matches,
|
|
1059
|
+
target: dynamic ? void 0 : item.target,
|
|
1060
|
+
to: dynamic ? (x, enq) => {
|
|
1061
|
+
const resolvedContext = context?.(x);
|
|
1062
|
+
const result = item.actions?.length ? executeActions(item.actions, resolvedContext ? {
|
|
1063
|
+
...x,
|
|
1064
|
+
context: {
|
|
1065
|
+
...x.context,
|
|
1066
|
+
...resolvedContext
|
|
1067
|
+
}
|
|
1068
|
+
} : x, enq) : void 0;
|
|
1069
|
+
return {
|
|
1070
|
+
target: item.target,
|
|
1071
|
+
context: result?.context ?? resolvedContext,
|
|
1072
|
+
reenter: item.reenter
|
|
1073
|
+
};
|
|
1074
|
+
} : void 0,
|
|
1075
|
+
guard: resolveCondition(item.guard, "guard", "$.transition.guard"),
|
|
1076
|
+
description: item.description,
|
|
1077
|
+
reenter: item.reenter,
|
|
1078
|
+
meta: item.meta,
|
|
1079
|
+
input
|
|
1080
|
+
};
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
function iterInvokeConfigs(invokes) {
|
|
1084
|
+
return (Array.isArray(invokes) ? invokes : [invokes]).map((inv) => ({
|
|
1085
|
+
src: inv.src,
|
|
1086
|
+
id: inv.id,
|
|
1087
|
+
registryKey: inv.registryKey,
|
|
1088
|
+
input: inv.input !== void 0 ? (args) => resolveValue(inv.input, "input", makeScope(args), "$.invoke.input") : void 0,
|
|
1089
|
+
onDone: inv.onDone ? getTransitionConfig(inv.onDone) : void 0,
|
|
1090
|
+
onError: inv.onError ? getTransitionConfig(inv.onError) : void 0,
|
|
1091
|
+
onSnapshot: inv.onSnapshot ? getTransitionConfig(inv.onSnapshot) : void 0,
|
|
1092
|
+
timeout: getDurationConfig(inv.timeout, "$.invoke.timeout"),
|
|
1093
|
+
onTimeout: inv.onTimeout ? getTransitionConfig(inv.onTimeout) : void 0
|
|
1094
|
+
}));
|
|
1095
|
+
}
|
|
1096
|
+
function iterNode(node, nodeKey) {
|
|
1097
|
+
return {
|
|
1098
|
+
id: node.id,
|
|
1099
|
+
initial: node.initial,
|
|
1100
|
+
type: node.type,
|
|
1101
|
+
history: node.history,
|
|
1102
|
+
target: node.target,
|
|
1103
|
+
description: node.description,
|
|
1104
|
+
tags: node.tags,
|
|
1105
|
+
input: node.input,
|
|
1106
|
+
timeout: getDurationConfig(node.timeout, `$.states.${nodeKey}.timeout`),
|
|
1107
|
+
states: node.states ? Object.fromEntries(Object.entries(node.states).map(([key, value]) => [key, iterNode(value, key)])) : void 0,
|
|
1108
|
+
on: node.on ? Object.fromEntries(Object.entries(node.on).map(([key, value]) => [key, getTransitionConfig(value)])) : void 0,
|
|
1109
|
+
always: node.always ? getTransitionConfig(node.always) : void 0,
|
|
1110
|
+
onError: node.onError ? getTransitionConfig(node.onError) : void 0,
|
|
1111
|
+
choice: makeChoiceConfig(node.choice, `$.states.${nodeKey}.choice`),
|
|
1112
|
+
route: resolveRouteConfig(node.route, `$.states.${nodeKey}.route`),
|
|
1113
|
+
after: node.after ? Object.fromEntries(Object.entries(node.after).map(([key, value]) => [key, getTransitionConfig(value)])) : void 0,
|
|
1114
|
+
onTimeout: node.onTimeout ? getTransitionConfig(node.onTimeout) : void 0,
|
|
1115
|
+
entry: node.entry ? iterActions(node.entry) : void 0,
|
|
1116
|
+
exit: node.exit ? iterActions(node.exit) : void 0,
|
|
1117
|
+
invoke: node.invoke ? iterInvokeConfigs(node.invoke) : void 0,
|
|
1118
|
+
meta: node.meta,
|
|
1119
|
+
output: isResolvable(node.output) ? ({ context, event, self }) => evaluateResolvable(node.output, "output", {
|
|
1120
|
+
context,
|
|
1121
|
+
event,
|
|
1122
|
+
self
|
|
1123
|
+
}, `$.states.${nodeKey}.output`) : node.output
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
if (json.delays) {
|
|
1127
|
+
for (const [key, delay] of Object.entries(json.delays)) if (typeof delay === "number") resolvedSources.delays[key] = delay;
|
|
1128
|
+
else if (typeof delay === "string") resolvedSources.delays[key] = delayToMs(delay);
|
|
1129
|
+
else if (delay && typeof delay === "object") resolvedSources.delays[key] = (args) => delayToMs(resolveValue(delay.duration, "delay", makeScope(args), `$.delays.${key}.duration`));
|
|
1130
|
+
}
|
|
1131
|
+
assertMachineJSON(json, resolvedSources, expressionResolver);
|
|
1132
|
+
const contextConfig = json.context ? { context: (args) => resolveValue(json.context, "context", args, "$.context") } : {};
|
|
1133
|
+
const machine = createMachineFromCompiledConfig({
|
|
1134
|
+
...iterNode(json),
|
|
1135
|
+
...contextConfig,
|
|
1136
|
+
version: json.version
|
|
1137
|
+
});
|
|
1138
|
+
if (json.internalEvents?.length) machine.internalEventDescriptors = [...machine.internalEventDescriptors, ...json.internalEvents];
|
|
1139
|
+
const provided = machine.provide({
|
|
1140
|
+
actions: resolvedSources.actions,
|
|
1141
|
+
actors: resolvedSources.actors,
|
|
1142
|
+
guards: {
|
|
1143
|
+
"xstate.stateIn": (args, params) => {
|
|
1144
|
+
const stateId = params?.stateId;
|
|
1145
|
+
const snapshot = args._snapshot;
|
|
1146
|
+
return !!stateId && !!snapshot?.nodes?.some((node) => node.id === stateId.replace(/^#/, ""));
|
|
1147
|
+
},
|
|
1148
|
+
"xstate.not": (args, params) => {
|
|
1149
|
+
const inner = params?.guard;
|
|
1150
|
+
const impl = inner && args.guards?.[inner.type];
|
|
1151
|
+
if (!impl) throw new Error(`Guard '${inner?.type}' referenced by 'xstate.not' is not implemented.`);
|
|
1152
|
+
return !impl(args, inner.params);
|
|
1153
|
+
},
|
|
1154
|
+
...resolvedSources.guards
|
|
1155
|
+
},
|
|
1156
|
+
delays: resolvedSources.delays
|
|
1157
|
+
});
|
|
1158
|
+
provided._json = json;
|
|
1159
|
+
return provided;
|
|
1160
|
+
}
|
|
1161
|
+
function isBuiltInActionJSON(action) {
|
|
1162
|
+
return !isResolvable(action) && action.type.startsWith("@xstate.");
|
|
1163
|
+
}
|
|
1164
|
+
// src/machineVersions.ts
|
|
1165
|
+
function isObject(value) {
|
|
1166
|
+
return value !== null && typeof value === "object";
|
|
1167
|
+
}
|
|
1168
|
+
function isVersionedStateMachine(entry) {
|
|
1169
|
+
return "transition" in entry && typeof entry.transition === "function";
|
|
1170
|
+
}
|
|
1171
|
+
function hasSnapshotSchema(entry) {
|
|
1172
|
+
return "snapshotSchema" in entry && entry.snapshotSchema !== void 0;
|
|
1173
|
+
}
|
|
1174
|
+
function hasEventSchema(entry) {
|
|
1175
|
+
return "eventSchema" in entry && entry.eventSchema !== void 0;
|
|
1176
|
+
}
|
|
1177
|
+
function getSnapshotSource(raw, defaultId) {
|
|
1178
|
+
if (!isObject(raw)) return {};
|
|
1179
|
+
if (isObject(raw["machine"])) return {
|
|
1180
|
+
id: typeof raw["machine"]["id"] === "string" ? raw["machine"]["id"] : void 0,
|
|
1181
|
+
version: typeof raw["machine"]["version"] === "string" ? raw["machine"]["version"] : void 0
|
|
1182
|
+
};
|
|
1183
|
+
return {
|
|
1184
|
+
id: typeof raw["version"] === "string" ? defaultId : void 0,
|
|
1185
|
+
version: typeof raw["version"] === "string" ? raw["version"] : void 0
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
async function validate(schema, value, description) {
|
|
1189
|
+
if (!schema) return value;
|
|
1190
|
+
const result = await schema["~standard"].validate(value);
|
|
1191
|
+
if (result.issues) throw new Error(`Invalid ${description}: ${result.issues[0]?.message}`);
|
|
1192
|
+
return result.value;
|
|
1193
|
+
}
|
|
1194
|
+
async function finalizeSnapshot(snapshot, target) {
|
|
1195
|
+
return await validate(target.snapshotSchema, {
|
|
1196
|
+
...snapshot,
|
|
1197
|
+
machine: {
|
|
1198
|
+
id: target.id,
|
|
1199
|
+
version: target.version
|
|
1200
|
+
},
|
|
1201
|
+
version: target.version
|
|
1202
|
+
}, `snapshot for machine '${target.id}' version '${target.version}'`);
|
|
1203
|
+
}
|
|
1204
|
+
async function validateEvents(events, source) {
|
|
1205
|
+
return Promise.all(events.map(async (event, index) => {
|
|
1206
|
+
const result = await source.eventSchema["~standard"].validate(event);
|
|
1207
|
+
if (result.issues) {
|
|
1208
|
+
const message = result.issues[0]?.message;
|
|
1209
|
+
if (message?.startsWith("Unknown event ")) throw new Error(message);
|
|
1210
|
+
const type = isObject(event) ? event["type"] : void 0;
|
|
1211
|
+
throw new Error(typeof type === "string" ? `Invalid event '${type}' at index ${index}${message ? `: ${message}` : "."}` : `Invalid event at index ${index}${message ? `: ${message}` : "."}`);
|
|
1212
|
+
}
|
|
1213
|
+
const validated = result.value;
|
|
1214
|
+
if (!isObject(validated) || typeof validated.type !== "string") throw new Error(`Invalid event at index ${index}.`);
|
|
1215
|
+
return validated;
|
|
1216
|
+
}));
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Creates migration and adaptation utilities for versions of one machine.
|
|
1220
|
+
*
|
|
1221
|
+
* @public
|
|
1222
|
+
*/
|
|
1223
|
+
function machineVersions(entries, options) {
|
|
1224
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
1225
|
+
const machinesByIdentity = /* @__PURE__ */ new Map();
|
|
1226
|
+
const machineId = entries[0].id;
|
|
1227
|
+
for (const entry of entries) {
|
|
1228
|
+
if (entry.version === void 0) throw new Error(`Machine '${entry.id}' must define a version.`);
|
|
1229
|
+
if (entry.version === "*") throw new Error("Machine version '*' is reserved for wildcard migrations.");
|
|
1230
|
+
if (entry.id !== machineId) throw new Error(`Machine '${entry.id}' does not match machine ID '${machineId}'.`);
|
|
1231
|
+
const key = `${entry.id}\0${entry.version}`;
|
|
1232
|
+
if (byIdentity.has(key)) throw new Error(`Duplicate machine identity '${entry.id}' version '${entry.version}'.`);
|
|
1233
|
+
byIdentity.set(key, entry);
|
|
1234
|
+
if (isVersionedStateMachine(entry)) machinesByIdentity.set(key, entry);
|
|
1235
|
+
}
|
|
1236
|
+
if (options?.unversioned !== void 0 && !byIdentity.has(`${machineId}\0${options.unversioned}`)) throw new Error(`Unversioned snapshot version '${options.unversioned}' is not retained for machine '${machineId}'.`);
|
|
1237
|
+
const parseSnapshot = async (raw) => {
|
|
1238
|
+
if (!isObject(raw)) throw new Error("Persisted snapshot is missing machine identity.");
|
|
1239
|
+
let id;
|
|
1240
|
+
let version;
|
|
1241
|
+
if (isObject(raw["machine"])) {
|
|
1242
|
+
({id, version} = raw["machine"]);
|
|
1243
|
+
if (typeof id !== "string" || typeof version !== "string") throw new Error("Persisted snapshot has an invalid machine identity.");
|
|
1244
|
+
} else if (typeof raw["version"] === "string") {
|
|
1245
|
+
id = machineId;
|
|
1246
|
+
version = raw["version"];
|
|
1247
|
+
} else if (raw["version"] === void 0 && options?.unversioned !== void 0) {
|
|
1248
|
+
id = machineId;
|
|
1249
|
+
version = options.unversioned;
|
|
1250
|
+
} else throw new Error("Persisted snapshot is missing machine identity.");
|
|
1251
|
+
if (raw["version"] !== void 0 && typeof raw["version"] !== "string") throw new Error("Persisted snapshot has an invalid version.");
|
|
1252
|
+
if (raw["version"] !== void 0 && raw["version"] !== version) throw new Error(`Persisted snapshot version '${raw["version"]}' conflicts with machine version '${version}'.`);
|
|
1253
|
+
const source = byIdentity.get(`${id}\0${version}`);
|
|
1254
|
+
if (!source) throw new Error(`Unknown machine identity '${id}' version '${version}'.`);
|
|
1255
|
+
if (!hasSnapshotSchema(source)) throw new Error(`Machine version '${version}' does not define a snapshot schema.`);
|
|
1256
|
+
const snapshot = await validate(source.snapshotSchema, raw, `snapshot for machine '${id}' version '${version}'`);
|
|
1257
|
+
return {
|
|
1258
|
+
source,
|
|
1259
|
+
...isVersionedStateMachine(source) && { machine: source },
|
|
1260
|
+
snapshot: {
|
|
1261
|
+
...snapshot,
|
|
1262
|
+
machine: {
|
|
1263
|
+
id,
|
|
1264
|
+
version
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
};
|
|
1269
|
+
return {
|
|
1270
|
+
parseSnapshot,
|
|
1271
|
+
async adaptEvents(events, adaptationOptions) {
|
|
1272
|
+
const target = machinesByIdentity.get(`${machineId}\0${adaptationOptions.to}`);
|
|
1273
|
+
if (!target) throw new Error(`Target version '${adaptationOptions.to}' is not backed by a machine for '${machineId}'.`);
|
|
1274
|
+
const sourceId = adaptationOptions.from.id ?? machineId;
|
|
1275
|
+
const source = byIdentity.get(`${sourceId}\0${adaptationOptions.from.version}`);
|
|
1276
|
+
let sourceError;
|
|
1277
|
+
let sourceValidationFailed = false;
|
|
1278
|
+
const hasTypedSource = source && hasEventSchema(source);
|
|
1279
|
+
if (hasTypedSource) {
|
|
1280
|
+
let sourceEvents;
|
|
1281
|
+
try {
|
|
1282
|
+
sourceEvents = await validateEvents(events, source);
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
sourceError = error;
|
|
1285
|
+
sourceValidationFailed = true;
|
|
1286
|
+
}
|
|
1287
|
+
if (sourceEvents) {
|
|
1288
|
+
if (source === target) return sourceEvents;
|
|
1289
|
+
const adapter = adaptationOptions.adapters[source.version];
|
|
1290
|
+
if (adapter) return validateEvents(await adapter(sourceEvents), target);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
const wildcardAdapter = adaptationOptions.adapters["*"];
|
|
1294
|
+
if (wildcardAdapter) return validateEvents(await wildcardAdapter(events, adaptationOptions.from), target);
|
|
1295
|
+
if (hasTypedSource) {
|
|
1296
|
+
if (sourceValidationFailed) throw sourceError instanceof Error ? sourceError : new Error(`Unable to validate event history for machine '${source.id}' version '${source.version}'.`, { cause: sourceError });
|
|
1297
|
+
throw new Error(`No event adapter from version '${source.version}' to '${target.version}' for machine '${target.id}'.`);
|
|
1298
|
+
}
|
|
1299
|
+
if (source) throw new Error(`Machine version '${source.version}' does not define an event schema; only the '*' adapter can handle its event history.`);
|
|
1300
|
+
throw new Error(`Unknown event history source '${sourceId}' version '${adaptationOptions.from.version}'.`);
|
|
1301
|
+
},
|
|
1302
|
+
async migrateSnapshot(raw, migrationOptions) {
|
|
1303
|
+
const target = machinesByIdentity.get(`${machineId}\0${migrationOptions.to}`);
|
|
1304
|
+
if (!target) throw new Error(`Target version '${migrationOptions.to}' is not backed by a machine for '${machineId}'.`);
|
|
1305
|
+
let source;
|
|
1306
|
+
let parseError;
|
|
1307
|
+
try {
|
|
1308
|
+
source = await parseSnapshot(raw);
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
parseError = error;
|
|
1311
|
+
}
|
|
1312
|
+
if (source) {
|
|
1313
|
+
if (source.source.version === target.version) return finalizeSnapshot(source.snapshot, target);
|
|
1314
|
+
const exactMigration = migrationOptions.migrations[source.source.version];
|
|
1315
|
+
if (exactMigration) return finalizeSnapshot(await exactMigration(source.snapshot), target);
|
|
1316
|
+
}
|
|
1317
|
+
const wildcardMigration = migrationOptions.migrations["*"];
|
|
1318
|
+
if (!wildcardMigration) {
|
|
1319
|
+
if (source) throw new Error(`No snapshot migration from version '${source.source.version}' to '${target.version}' for machine '${target.id}'.`);
|
|
1320
|
+
throw parseError;
|
|
1321
|
+
}
|
|
1322
|
+
return finalizeSnapshot(await wildcardMigration(raw, getSnapshotSource(raw, machineId)), target);
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
// src/mapState.ts
|
|
1327
|
+
const ownResultOf = (snapshot, stateNode, mapper) => mapper.map === void 0 ? [] : [{
|
|
1328
|
+
stateNode,
|
|
1329
|
+
result: mapper.map(snapshot)
|
|
1330
|
+
}];
|
|
1331
|
+
const mappedStatesOf = (snapshot, active, stateNode, mapper) => mapper === void 0 ? [] : [...Object.values(stateNode.states).filter((child) => active.has(child)).flatMap((child) => mappedStatesOf(snapshot, active, child, mapper.states?.[child.key])), ...ownResultOf(snapshot, stateNode, mapper)];
|
|
1332
|
+
const mapActiveStates = (snapshot, mapper) => snapshot.nodes.some(isAtomicStateNode) ? mappedStatesOf(snapshot, new Set(snapshot.nodes), snapshot.machine.root, mapper) : [];
|
|
1333
|
+
function mapState(...args) {
|
|
1334
|
+
if (args.length === 1) {
|
|
1335
|
+
const [mapper] = args;
|
|
1336
|
+
return (snapshot) => mapActiveStates(snapshot, mapper);
|
|
1337
|
+
}
|
|
1338
|
+
return mapActiveStates(args[0], args[1]);
|
|
1339
|
+
}
|
|
1340
|
+
// src/serialize.ts
|
|
1341
|
+
/**
|
|
1342
|
+
* Returns the JSON-serializable definition of a machine.
|
|
1343
|
+
*
|
|
1344
|
+
* Inline functions are represented as `{ "@code": string, "@lang": "ts" }`. A
|
|
1345
|
+
* machine created via `createMachineFromConfig` returns its original JSON
|
|
1346
|
+
* config (lossless round-trip):
|
|
1347
|
+
*
|
|
1348
|
+
* ```ts
|
|
1349
|
+
* import { serializeMachine, createMachineFromConfig } from '@systemfsoftware/xstate';
|
|
1350
|
+
*
|
|
1351
|
+
* const json = serializeMachine(machine);
|
|
1352
|
+
* const revived = createMachineFromConfig(
|
|
1353
|
+
* JSON.parse(JSON.stringify(json))
|
|
1354
|
+
* );
|
|
1355
|
+
* ```
|
|
1356
|
+
*
|
|
1357
|
+
* @public
|
|
1358
|
+
*/
|
|
1359
|
+
function serializeMachine(machine) {
|
|
1360
|
+
return machine._json ?? machineConfigToJSON(machine.config);
|
|
1361
|
+
}
|
|
1362
|
+
function codeExpression(fn) {
|
|
1363
|
+
return {
|
|
1364
|
+
"@code": fn.toString(),
|
|
1365
|
+
"@lang": "ts"
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
function isActorLogic(value) {
|
|
1369
|
+
return !!value && typeof value === "object" && typeof value.transition === "function" && typeof value.getInitialSnapshot === "function";
|
|
1370
|
+
}
|
|
1371
|
+
function isRuntimeSchema(value) {
|
|
1372
|
+
return !!value && typeof value === "object" && "~standard" in value;
|
|
1373
|
+
}
|
|
1374
|
+
/** JSON-safe deep copy of a plain value; functions become code expressions. */
|
|
1375
|
+
function valueToJSON(value) {
|
|
1376
|
+
if (typeof value === "function") return codeExpression(value);
|
|
1377
|
+
if (value === null || typeof value !== "object") return typeof value === "bigint" || typeof value === "symbol" ? void 0 : value;
|
|
1378
|
+
if (isActorLogic(value)) return;
|
|
1379
|
+
if (isRuntimeSchema(value)) return;
|
|
1380
|
+
if (Array.isArray(value)) return value.map(valueToJSON).filter((item) => item !== void 0);
|
|
1381
|
+
if (value.constructor !== Object && value.constructor !== void 0) return;
|
|
1382
|
+
const result = {};
|
|
1383
|
+
for (const key of Object.keys(value)) {
|
|
1384
|
+
const v = value[key];
|
|
1385
|
+
if (v !== void 0) {
|
|
1386
|
+
const jsonValue = valueToJSON(v);
|
|
1387
|
+
if (jsonValue !== void 0) result[key] = jsonValue;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return result;
|
|
1391
|
+
}
|
|
1392
|
+
function invokeToJSON(invoke) {
|
|
1393
|
+
if (Array.isArray(invoke)) {
|
|
1394
|
+
const values = invoke.map(invokeToJSON).filter((value) => value !== void 0);
|
|
1395
|
+
return values.length ? values : void 0;
|
|
1396
|
+
}
|
|
1397
|
+
const def = invoke;
|
|
1398
|
+
const result = {};
|
|
1399
|
+
for (const key of Object.keys(def)) {
|
|
1400
|
+
const value = def[key];
|
|
1401
|
+
if (value === void 0) continue;
|
|
1402
|
+
result[key] = key === "src" && typeof value !== "string" ? value.id : valueToJSON(value);
|
|
1403
|
+
if (result[key] === void 0) delete result[key];
|
|
1404
|
+
}
|
|
1405
|
+
return result["src"] === void 0 ? void 0 : result;
|
|
1406
|
+
}
|
|
1407
|
+
function sourcesToJSON(map) {
|
|
1408
|
+
if (!map) return;
|
|
1409
|
+
const result = {};
|
|
1410
|
+
for (const key of Object.keys(map)) {
|
|
1411
|
+
const value = map[key];
|
|
1412
|
+
if (typeof value === "function") result[key] = void 0;
|
|
1413
|
+
else result[key] = valueToJSON(value);
|
|
1414
|
+
if (result[key] === void 0) delete result[key];
|
|
1415
|
+
}
|
|
1416
|
+
return result;
|
|
1417
|
+
}
|
|
1418
|
+
function stateNodeConfigToJSON(config) {
|
|
1419
|
+
const result = {};
|
|
1420
|
+
for (const key of Object.keys(config)) {
|
|
1421
|
+
if (key === "states" || key === "invoke") continue;
|
|
1422
|
+
const value = config[key];
|
|
1423
|
+
if (value !== void 0) result[key] = valueToJSON(value);
|
|
1424
|
+
}
|
|
1425
|
+
if (config["invoke"] !== void 0) {
|
|
1426
|
+
const invoke = invokeToJSON(config["invoke"]);
|
|
1427
|
+
if (invoke !== void 0) result["invoke"] = invoke;
|
|
1428
|
+
}
|
|
1429
|
+
if (config["states"]) {
|
|
1430
|
+
const states = {};
|
|
1431
|
+
for (const [key, stateConfig] of Object.entries(config["states"])) states[key] = stateNodeConfigToJSON(stateConfig);
|
|
1432
|
+
result["states"] = states;
|
|
1433
|
+
}
|
|
1434
|
+
return result;
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Converts a machine config (as passed to `createMachine`) to its JSON-safe
|
|
1438
|
+
* definition.
|
|
1439
|
+
*
|
|
1440
|
+
* @public
|
|
1441
|
+
*/
|
|
1442
|
+
function machineConfigToJSON(config) {
|
|
1443
|
+
const result = stateNodeConfigToJSON(config);
|
|
1444
|
+
const internalEvents = Object.keys(config["schemas"]?.["internalEvents"] ?? {});
|
|
1445
|
+
delete result["internalEvents"];
|
|
1446
|
+
if (internalEvents.length) result["internalEvents"] = internalEvents;
|
|
1447
|
+
if (config["schemas"]) {
|
|
1448
|
+
const schemas = {};
|
|
1449
|
+
for (const key of Object.keys(config["schemas"])) {
|
|
1450
|
+
const value = config["schemas"][key];
|
|
1451
|
+
if (value && typeof value === "object" && !("~standard" in value)) schemas[key] = sourcesToJSON(value);
|
|
1452
|
+
else schemas[key] = valueToJSON(value);
|
|
1453
|
+
if (schemas[key] === void 0) delete schemas[key];
|
|
1454
|
+
}
|
|
1455
|
+
result["schemas"] = schemas;
|
|
1456
|
+
}
|
|
1457
|
+
for (const key of [
|
|
1458
|
+
"actions",
|
|
1459
|
+
"guards",
|
|
1460
|
+
"actors",
|
|
1461
|
+
"delays"
|
|
1462
|
+
]) if (config[key]) {
|
|
1463
|
+
const value = sourcesToJSON(config[key]);
|
|
1464
|
+
if (value && Object.keys(value).length) result[key] = value;
|
|
1465
|
+
else delete result[key];
|
|
1466
|
+
}
|
|
1467
|
+
return result;
|
|
1468
|
+
}
|
|
1469
|
+
// src/hotSwap.ts
|
|
1470
|
+
/**
|
|
1471
|
+
* Switches a running machine actor to another version of its machine, for
|
|
1472
|
+
* development hot reloading. The live snapshot is carried over in memory and
|
|
1473
|
+
* never serialized, so context may hold values that `getPersistedSnapshot()`
|
|
1474
|
+
* cannot represent (DOM nodes, cycles).
|
|
1475
|
+
*
|
|
1476
|
+
* Children whose logic is unchanged by identity keep running; other children
|
|
1477
|
+
* are stopped and restarted from the new machine's logic. Returns `false`,
|
|
1478
|
+
* leaving the actor unchanged, when the actor is not a running machine actor
|
|
1479
|
+
* with the same machine id, a state or history id does not exist on the new
|
|
1480
|
+
* machine, a child's logic cannot be resolved statically, context or a pending
|
|
1481
|
+
* timer references a child that would restart, or the new machine's validator
|
|
1482
|
+
* rejects the snapshot.
|
|
1483
|
+
*
|
|
1484
|
+
* @experimental Used by framework integrations; not part of the stable API.
|
|
1485
|
+
*/
|
|
1486
|
+
function hotSwapActorLogic(actorRef, machine) {
|
|
1487
|
+
const actor = actorRef;
|
|
1488
|
+
const snapshot = actor.getSnapshot();
|
|
1489
|
+
if (actor._processingStatus !== 1 || snapshot.status !== "active" || snapshot.machine?.id !== machine.id) return false;
|
|
1490
|
+
let nodes;
|
|
1491
|
+
const historyValue = {};
|
|
1492
|
+
try {
|
|
1493
|
+
nodes = [...getAllStateNodes(getStateNodes(machine.root, snapshot.value))];
|
|
1494
|
+
for (const [key, items] of Object.entries(snapshot.historyValue ?? {})) {
|
|
1495
|
+
if (machine.getStateNodeById(key).type !== "history") return false;
|
|
1496
|
+
historyValue[key] = items.map((item) => machine.getStateNodeById(item.id));
|
|
1497
|
+
}
|
|
1498
|
+
} catch {
|
|
1499
|
+
return false;
|
|
1500
|
+
}
|
|
1501
|
+
const children = {};
|
|
1502
|
+
const restarts = [];
|
|
1503
|
+
for (const [childId, child] of Object.entries(snapshot.children)) {
|
|
1504
|
+
if (!child) continue;
|
|
1505
|
+
if (typeof child.src !== "string") {
|
|
1506
|
+
children[childId] = child;
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
let logic;
|
|
1510
|
+
try {
|
|
1511
|
+
logic = resolveReferencedActor(machine, child.src);
|
|
1512
|
+
} catch {}
|
|
1513
|
+
if (!logic || typeof logic === "function") return false;
|
|
1514
|
+
if (logic === child.logic) children[childId] = child;
|
|
1515
|
+
else restarts.push([
|
|
1516
|
+
childId,
|
|
1517
|
+
child,
|
|
1518
|
+
logic
|
|
1519
|
+
]);
|
|
1520
|
+
}
|
|
1521
|
+
const restarting = new Set(restarts.map(([, child]) => child));
|
|
1522
|
+
if (restarting.size && (Object.values(snapshot.timers ?? {}).some((timer) => restarting.has(timer.target)) || holdsActorRef(snapshot.context, restarting))) return false;
|
|
1523
|
+
const buildSnapshot = () => createMachineSnapshot({
|
|
1524
|
+
...snapshot,
|
|
1525
|
+
_nodes: nodes,
|
|
1526
|
+
value: void 0,
|
|
1527
|
+
children,
|
|
1528
|
+
historyValue
|
|
1529
|
+
}, machine);
|
|
1530
|
+
if (machine.validator?.check({
|
|
1531
|
+
kind: "result",
|
|
1532
|
+
logic: machine,
|
|
1533
|
+
snapshot: buildSnapshot(),
|
|
1534
|
+
effects: []
|
|
1535
|
+
})) return false;
|
|
1536
|
+
for (const [childId, child, logic] of restarts) {
|
|
1537
|
+
actor._stopChild(child);
|
|
1538
|
+
children[childId] = actor.system.createActorRef(logic, {
|
|
1539
|
+
id: childId,
|
|
1540
|
+
parent: actor,
|
|
1541
|
+
syncSnapshot: child._syncSnapshot,
|
|
1542
|
+
src: child.src,
|
|
1543
|
+
registryKey: child.registryKey,
|
|
1544
|
+
input: child.options?.input
|
|
1545
|
+
});
|
|
1546
|
+
}
|
|
1547
|
+
if (actor.src === actor.logic) actor.src = machine;
|
|
1548
|
+
actor.logic = machine;
|
|
1549
|
+
const next = buildSnapshot();
|
|
1550
|
+
actor._setSnapshot(next);
|
|
1551
|
+
actor._next(next);
|
|
1552
|
+
for (const [childId] of restarts) children[childId].start();
|
|
1553
|
+
return true;
|
|
1554
|
+
}
|
|
1555
|
+
/**
|
|
1556
|
+
* Whether `value` holds any of `refs`, searching plain objects, arrays, maps
|
|
1557
|
+
* and sets. Actor refs, class instances and host objects (DOM nodes) are not
|
|
1558
|
+
* searched into.
|
|
1559
|
+
*/
|
|
1560
|
+
function holdsActorRef(value, refs, visited = /* @__PURE__ */ new Set()) {
|
|
1561
|
+
if (!value || typeof value !== "object" || visited.has(value)) return false;
|
|
1562
|
+
visited.add(value);
|
|
1563
|
+
if (isActorRefLike(value)) return refs.has(value);
|
|
1564
|
+
const proto = Object.getPrototypeOf(value);
|
|
1565
|
+
return (value instanceof Map || value instanceof Set ? [...value.values()] : Array.isArray(value) || proto === Object.prototype || proto === null ? Object.values(value) : []).some((item) => holdsActorRef(item, refs, visited));
|
|
1566
|
+
}
|
|
1567
|
+
// src/setup.ts
|
|
1568
|
+
/**
|
|
1569
|
+
* Sets up a state machine with state input schemas and other configuration.
|
|
1570
|
+
*
|
|
1571
|
+
* @example
|
|
1572
|
+
*
|
|
1573
|
+
* ```ts
|
|
1574
|
+
* import { setup } from '@systemfsoftware/xstate';
|
|
1575
|
+
* import z from 'zod';
|
|
1576
|
+
*
|
|
1577
|
+
* const s = setup({
|
|
1578
|
+
* states: {
|
|
1579
|
+
* loading: {
|
|
1580
|
+
* schemas: {
|
|
1581
|
+
* input: z.object({
|
|
1582
|
+
* userId: z.string()
|
|
1583
|
+
* })
|
|
1584
|
+
* }
|
|
1585
|
+
* }
|
|
1586
|
+
* }
|
|
1587
|
+
* });
|
|
1588
|
+
*
|
|
1589
|
+
* const machine = s.createMachine({
|
|
1590
|
+
* initial: {
|
|
1591
|
+
* target: 'loading',
|
|
1592
|
+
* input: { userId: '123' }
|
|
1593
|
+
* },
|
|
1594
|
+
* states: {
|
|
1595
|
+
* loading: {
|
|
1596
|
+
* entry: ({ input }) => {
|
|
1597
|
+
* console.log(input.userId);
|
|
1598
|
+
* }
|
|
1599
|
+
* }
|
|
1600
|
+
* }
|
|
1601
|
+
* });
|
|
1602
|
+
* ```
|
|
1603
|
+
* @public
|
|
1604
|
+
*/
|
|
1605
|
+
const setup = function setupImplementation(config = {}) {
|
|
1606
|
+
const { validator, states = {}, schemas, actions, actors, guards, delays } = config;
|
|
1607
|
+
return {
|
|
1608
|
+
extend(extension) {
|
|
1609
|
+
return setup(mergeSetupConfigs(config, extension));
|
|
1610
|
+
},
|
|
1611
|
+
createMachine(machineConfig) {
|
|
1612
|
+
const configSchemas = machineConfig.schemas;
|
|
1613
|
+
const mergedSchemas = mergeSchemas(configSchemas, schemas);
|
|
1614
|
+
const mergedStates = mergeStateSchemas(machineConfig.states, states);
|
|
1615
|
+
const mergedActions = mergeMaps(actions, machineConfig.actions);
|
|
1616
|
+
const mergedActors = mergeMaps(actors, machineConfig.actors);
|
|
1617
|
+
const mergedGuards = mergeMaps(guards, machineConfig.guards);
|
|
1618
|
+
const mergedDelays = mergeMaps(delays, machineConfig.delays);
|
|
1619
|
+
return new StateMachine({
|
|
1620
|
+
...machineConfig,
|
|
1621
|
+
...mergedSchemas ? { schemas: mergedSchemas } : void 0,
|
|
1622
|
+
...mergedStates ? { states: mergedStates } : void 0,
|
|
1623
|
+
...mergedActions ? { actions: mergedActions } : void 0,
|
|
1624
|
+
...mergedActors ? { actors: mergedActors } : void 0,
|
|
1625
|
+
...mergedGuards ? { guards: mergedGuards } : void 0,
|
|
1626
|
+
...mergedDelays ? { delays: mergedDelays } : void 0
|
|
1627
|
+
}, void 0, validator);
|
|
1628
|
+
},
|
|
1629
|
+
createInvoke(invokeConfig, ..._check) {
|
|
1630
|
+
return typeof invokeConfig.src === "function" ? {
|
|
1631
|
+
...invokeConfig,
|
|
1632
|
+
src: createAsyncLogic({
|
|
1633
|
+
schemas: invokeConfig.schemas,
|
|
1634
|
+
run: invokeConfig.src
|
|
1635
|
+
})
|
|
1636
|
+
} : invokeConfig;
|
|
1637
|
+
},
|
|
1638
|
+
createStateConfig(...args) {
|
|
1639
|
+
return args.length > 1 ? args[1] : args[0];
|
|
1640
|
+
},
|
|
1641
|
+
states,
|
|
1642
|
+
schemas: schemas ?? {}
|
|
1643
|
+
};
|
|
1644
|
+
};
|
|
1645
|
+
/** @public */
|
|
1646
|
+
function createSystem(_config = {}) {
|
|
1647
|
+
const runtimeRef = {};
|
|
1648
|
+
const pending = [];
|
|
1649
|
+
const flushObservers = () => {
|
|
1650
|
+
const runtime = runtimeRef.current;
|
|
1651
|
+
if (!runtime) return;
|
|
1652
|
+
for (const entry of pending) if (entry.active && !entry.subscription) entry.subscription = entry.subscribe(runtime);
|
|
1653
|
+
};
|
|
1654
|
+
const subscribeToSystem = (subscribe) => {
|
|
1655
|
+
const runtime = runtimeRef.current;
|
|
1656
|
+
if (runtime) return subscribe(runtime);
|
|
1657
|
+
const entry = {
|
|
1658
|
+
subscribe,
|
|
1659
|
+
active: true
|
|
1660
|
+
};
|
|
1661
|
+
pending.push(entry);
|
|
1662
|
+
return { unsubscribe() {
|
|
1663
|
+
entry.active = false;
|
|
1664
|
+
entry.subscription?.unsubscribe();
|
|
1665
|
+
} };
|
|
1666
|
+
};
|
|
1667
|
+
return {
|
|
1668
|
+
createActor(logic, ...[options]) {
|
|
1669
|
+
const actor = createActor(logic, {
|
|
1670
|
+
...options,
|
|
1671
|
+
_systemRef: runtimeRef
|
|
1672
|
+
});
|
|
1673
|
+
flushObservers();
|
|
1674
|
+
return actor;
|
|
1675
|
+
},
|
|
1676
|
+
get(key) {
|
|
1677
|
+
return runtimeRef.current?.get(key);
|
|
1678
|
+
},
|
|
1679
|
+
getAll() {
|
|
1680
|
+
return runtimeRef.current?.getAll() ?? {};
|
|
1681
|
+
},
|
|
1682
|
+
inspect(observer) {
|
|
1683
|
+
return subscribeToSystem((system) => system.inspect(observer));
|
|
1684
|
+
},
|
|
1685
|
+
onRejectedEvent(listener) {
|
|
1686
|
+
return subscribeToSystem((system) => system.onRejectedEvent(listener));
|
|
1687
|
+
},
|
|
1688
|
+
setup
|
|
1689
|
+
};
|
|
1690
|
+
}
|
|
1691
|
+
function mergeMaps(left, right) {
|
|
1692
|
+
return left || right ? {
|
|
1693
|
+
...left,
|
|
1694
|
+
...right
|
|
1695
|
+
} : void 0;
|
|
1696
|
+
}
|
|
1697
|
+
function mergeSetupStateSchemas(left, right) {
|
|
1698
|
+
if (!left && !right) return;
|
|
1699
|
+
if (!left) return right;
|
|
1700
|
+
if (!right) return left;
|
|
1701
|
+
return Object.fromEntries(Array.from(/* @__PURE__ */ new Set([...Object.keys(left), ...Object.keys(right)])).map((key) => {
|
|
1702
|
+
const leftState = left[key];
|
|
1703
|
+
const rightState = right[key];
|
|
1704
|
+
if (!leftState) {
|
|
1705
|
+
if (!rightState) throw new Error(`Missing state schema for '${key}'.`);
|
|
1706
|
+
return [key, rightState];
|
|
1707
|
+
}
|
|
1708
|
+
if (!rightState) return [key, leftState];
|
|
1709
|
+
const schemas = mergeSchemas(leftState.schemas, rightState.schemas);
|
|
1710
|
+
const states = mergeSetupStateSchemas(leftState.states, rightState.states);
|
|
1711
|
+
return [key, {
|
|
1712
|
+
...leftState,
|
|
1713
|
+
...rightState,
|
|
1714
|
+
...schemas ? { schemas } : void 0,
|
|
1715
|
+
...states ? { states } : void 0
|
|
1716
|
+
}];
|
|
1717
|
+
}));
|
|
1718
|
+
}
|
|
1719
|
+
function mergeSchemas(left, right) {
|
|
1720
|
+
if (!left && !right) return;
|
|
1721
|
+
return {
|
|
1722
|
+
...left,
|
|
1723
|
+
...right,
|
|
1724
|
+
events: mergeMaps(left?.events, right?.events),
|
|
1725
|
+
internalEvents: mergeMaps(left?.internalEvents, right?.internalEvents),
|
|
1726
|
+
actions: mergeMaps(left?.actions, right?.actions),
|
|
1727
|
+
guards: mergeMaps(left?.guards, right?.guards),
|
|
1728
|
+
emitted: mergeMaps(left?.emitted, right?.emitted),
|
|
1729
|
+
children: mergeMaps(left?.children, right?.children)
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Setup schemas win over inline config schemas (same precedence as root
|
|
1734
|
+
* `mergeSchemas`); setup states with no matching config state are skipped.
|
|
1735
|
+
*/
|
|
1736
|
+
function mergeStateSchemas(configStates, setupStates) {
|
|
1737
|
+
if (!configStates || !setupStates) return configStates;
|
|
1738
|
+
return Object.fromEntries(Object.entries(configStates).map(([key, configState]) => {
|
|
1739
|
+
const setupState = setupStates[key];
|
|
1740
|
+
if (!setupState) return [key, configState];
|
|
1741
|
+
const schemas = mergeMaps(configState.schemas, setupState.schemas);
|
|
1742
|
+
const states = mergeStateSchemas(configState.states, setupState.states);
|
|
1743
|
+
const structural = Object.fromEntries([
|
|
1744
|
+
"type",
|
|
1745
|
+
"id",
|
|
1746
|
+
"initial",
|
|
1747
|
+
"history",
|
|
1748
|
+
"target",
|
|
1749
|
+
"route"
|
|
1750
|
+
].flatMap((field) => configState[field] === void 0 && setupState[field] !== void 0 ? [[field, setupState[field]]] : []));
|
|
1751
|
+
return [key, {
|
|
1752
|
+
...configState,
|
|
1753
|
+
...structural,
|
|
1754
|
+
...schemas ? { schemas } : void 0,
|
|
1755
|
+
...states ? { states } : void 0
|
|
1756
|
+
}];
|
|
1757
|
+
}));
|
|
1758
|
+
}
|
|
1759
|
+
function mergeSetupConfigs(base, extension) {
|
|
1760
|
+
return {
|
|
1761
|
+
...base,
|
|
1762
|
+
...extension,
|
|
1763
|
+
schemas: mergeSchemas(base.schemas, extension.schemas),
|
|
1764
|
+
states: mergeSetupStateSchemas(base.states, extension.states),
|
|
1765
|
+
actions: mergeMaps(base.actions, extension.actions),
|
|
1766
|
+
actors: mergeMaps(base.actors, extension.actors),
|
|
1767
|
+
guards: mergeMaps(base.guards, extension.guards),
|
|
1768
|
+
delays: mergeMaps(base.delays, extension.delays)
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
// src/IndexedHeap.ts
|
|
1772
|
+
/** An ordered queue whose entries can be removed without retaining cancelled work. @internal */
|
|
1773
|
+
var IndexedHeap = class {
|
|
1774
|
+
before;
|
|
1775
|
+
entries = [];
|
|
1776
|
+
indices = /* @__PURE__ */ new Map();
|
|
1777
|
+
constructor(before) {
|
|
1778
|
+
this.before = before;
|
|
1779
|
+
}
|
|
1780
|
+
push(entry) {
|
|
1781
|
+
this.entries.push(entry);
|
|
1782
|
+
this.up(this.entries.length - 1, entry);
|
|
1783
|
+
}
|
|
1784
|
+
peek() {
|
|
1785
|
+
return this.entries[0];
|
|
1786
|
+
}
|
|
1787
|
+
at(index) {
|
|
1788
|
+
const entry = this.entries[index];
|
|
1789
|
+
if (entry === void 0) throw new Error("IndexedHeap index out of range.");
|
|
1790
|
+
return entry;
|
|
1791
|
+
}
|
|
1792
|
+
pop() {
|
|
1793
|
+
const first = this.peek();
|
|
1794
|
+
if (first) this.remove(first);
|
|
1795
|
+
return first;
|
|
1796
|
+
}
|
|
1797
|
+
remove(entry) {
|
|
1798
|
+
const index = this.indices.get(entry);
|
|
1799
|
+
if (index === void 0) return;
|
|
1800
|
+
const last = this.entries.pop();
|
|
1801
|
+
this.indices.delete(entry);
|
|
1802
|
+
if (index === this.entries.length) return;
|
|
1803
|
+
if (index > 0 && this.before(last, this.at(index - 1 >> 1))) this.up(index, last);
|
|
1804
|
+
else this.down(index, last);
|
|
1805
|
+
}
|
|
1806
|
+
up(index, entry) {
|
|
1807
|
+
while (index > 0) {
|
|
1808
|
+
const parent = index - 1 >> 1;
|
|
1809
|
+
if (!this.before(entry, this.at(parent))) break;
|
|
1810
|
+
this.place(index, this.at(parent));
|
|
1811
|
+
index = parent;
|
|
1812
|
+
}
|
|
1813
|
+
this.place(index, entry);
|
|
1814
|
+
}
|
|
1815
|
+
down(index, entry) {
|
|
1816
|
+
while (index * 2 + 1 < this.entries.length) {
|
|
1817
|
+
let child = index * 2 + 1;
|
|
1818
|
+
if (child + 1 < this.entries.length && this.before(this.at(child + 1), this.at(child))) child++;
|
|
1819
|
+
if (!this.before(this.at(child), entry)) break;
|
|
1820
|
+
this.place(index, this.at(child));
|
|
1821
|
+
index = child;
|
|
1822
|
+
}
|
|
1823
|
+
this.place(index, entry);
|
|
1824
|
+
}
|
|
1825
|
+
place(index, entry) {
|
|
1826
|
+
this.entries[index] = entry;
|
|
1827
|
+
this.indices.set(entry, index);
|
|
1828
|
+
}
|
|
1829
|
+
};
|
|
1830
|
+
// src/SimulatedClock.ts
|
|
1831
|
+
var SimulatedClock = class {
|
|
1832
|
+
timeouts = /* @__PURE__ */ new Map();
|
|
1833
|
+
deadlines = new IndexedHeap((a, b) => {
|
|
1834
|
+
const aTime = a.start + a.timeout;
|
|
1835
|
+
const bTime = b.start + b.timeout;
|
|
1836
|
+
return aTime < bTime || aTime === bTime && a.id < b.id;
|
|
1837
|
+
});
|
|
1838
|
+
_now = 0;
|
|
1839
|
+
_id = 0;
|
|
1840
|
+
_flushing = false;
|
|
1841
|
+
now() {
|
|
1842
|
+
return this._now;
|
|
1843
|
+
}
|
|
1844
|
+
getId() {
|
|
1845
|
+
return this._id++;
|
|
1846
|
+
}
|
|
1847
|
+
setTimeout(fn, timeout) {
|
|
1848
|
+
if (!Number.isFinite(timeout)) throw new Error("Timer delay must be finite");
|
|
1849
|
+
const id = this.getId();
|
|
1850
|
+
const timer = {
|
|
1851
|
+
id,
|
|
1852
|
+
start: this.now(),
|
|
1853
|
+
timeout: Math.max(0, timeout),
|
|
1854
|
+
fn
|
|
1855
|
+
};
|
|
1856
|
+
this.timeouts.set(id, timer);
|
|
1857
|
+
this.deadlines.push(timer);
|
|
1858
|
+
return id;
|
|
1859
|
+
}
|
|
1860
|
+
clearTimeout(id) {
|
|
1861
|
+
const timer = this.timeouts.get(id);
|
|
1862
|
+
if (timer) this.deadlines.remove(timer);
|
|
1863
|
+
this.timeouts.delete(id);
|
|
1864
|
+
}
|
|
1865
|
+
set(time) {
|
|
1866
|
+
if (!Number.isFinite(time) || this._now > time) throw new Error("Clock time must be finite; unable to travel back in time");
|
|
1867
|
+
if (this._flushing) throw new Error("Cannot advance a clock during a timer callback");
|
|
1868
|
+
this.flushTimeouts(time);
|
|
1869
|
+
}
|
|
1870
|
+
flushTimeouts(destination) {
|
|
1871
|
+
this._flushing = true;
|
|
1872
|
+
try {
|
|
1873
|
+
let count = 0;
|
|
1874
|
+
while (true) {
|
|
1875
|
+
const next = this.deadlines.peek();
|
|
1876
|
+
if (!next || next.start + next.timeout > destination) break;
|
|
1877
|
+
if (++count > 1e4) throw new Error("Clock exceeded 10,000 timer callbacks; check for a zero-delay loop");
|
|
1878
|
+
this.deadlines.pop();
|
|
1879
|
+
this._now = Math.max(this._now, next.start + next.timeout);
|
|
1880
|
+
this.timeouts.delete(next.id);
|
|
1881
|
+
next.fn.call(null);
|
|
1882
|
+
}
|
|
1883
|
+
this._now = destination;
|
|
1884
|
+
} finally {
|
|
1885
|
+
this._flushing = false;
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
increment(ms) {
|
|
1889
|
+
this.set(this._now + ms);
|
|
1890
|
+
}
|
|
1891
|
+
};
|
|
1892
|
+
// src/systemTransition.ts
|
|
1893
|
+
function timerKey(source, id) {
|
|
1894
|
+
return JSON.stringify([
|
|
1895
|
+
source.$actor,
|
|
1896
|
+
source.incarnation,
|
|
1897
|
+
id
|
|
1898
|
+
]);
|
|
1899
|
+
}
|
|
1900
|
+
function isReference(value) {
|
|
1901
|
+
return !!value && typeof value.$actor === "string" && Number.isSafeInteger(value.incarnation);
|
|
1902
|
+
}
|
|
1903
|
+
function isHandle(value) {
|
|
1904
|
+
return !!value && typeof value.address === "string" && typeof value.getSnapshot === "function";
|
|
1905
|
+
}
|
|
1906
|
+
const systemActorHandle = Symbol("xstate.systemActorHandle");
|
|
1907
|
+
const systemLogger = () => {};
|
|
1908
|
+
/** Converts runtime-shaped, reduction-local capabilities to snapshot data. */
|
|
1909
|
+
function encode(value, seen = /* @__PURE__ */ new Set(), mappers) {
|
|
1910
|
+
if (typeof value === "function" && mappers?.has(value)) return { $systemMapper: mappers.get(value) };
|
|
1911
|
+
if (isHandle(value)) {
|
|
1912
|
+
if (!value[systemActorHandle]) throw new Error("Live actors cannot be stored in a pure system snapshot.");
|
|
1913
|
+
return {
|
|
1914
|
+
$actor: value.address,
|
|
1915
|
+
incarnation: Number(value.sessionId)
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
if (!value || typeof value !== "object") {
|
|
1919
|
+
if (typeof value === "function" || typeof value === "symbol" || typeof value === "bigint" || typeof value === "number" && !Number.isFinite(value)) throw new Error("System snapshots require data, not executable capabilities.");
|
|
1920
|
+
return value;
|
|
1921
|
+
}
|
|
1922
|
+
if (seen.has(value)) throw new Error("System snapshots cannot contain cyclic data.");
|
|
1923
|
+
seen.add(value);
|
|
1924
|
+
let result;
|
|
1925
|
+
if (value.machine && typeof value.machine.transition === "function" && typeof value.id === "string" && Array.isArray(value.path)) result = { $stateNode: value.id };
|
|
1926
|
+
else if (value instanceof Error) result = {
|
|
1927
|
+
$error: value.name,
|
|
1928
|
+
message: value.message
|
|
1929
|
+
};
|
|
1930
|
+
else if (Array.isArray(value)) result = value.map((item) => encode(item, seen, mappers));
|
|
1931
|
+
else {
|
|
1932
|
+
if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) throw new Error("System snapshot values must be plain data.");
|
|
1933
|
+
result = Object.fromEntries(Object.entries(value).map(([key, item]) => [key, encode(item, seen, mappers)]));
|
|
1934
|
+
}
|
|
1935
|
+
seen.delete(value);
|
|
1936
|
+
return result;
|
|
1937
|
+
}
|
|
1938
|
+
function snapshotData(snapshot, mappers) {
|
|
1939
|
+
if (!isMachineSnapshot(snapshot)) return encode(snapshot, /* @__PURE__ */ new Set(), mappers);
|
|
1940
|
+
const { machine, nodes, tags, matches, can, hasTag, getMeta, getInputs, toJSON, ...data } = snapshot;
|
|
1941
|
+
return encode({
|
|
1942
|
+
...data,
|
|
1943
|
+
nodeIds: nodes.map((node) => node.id)
|
|
1944
|
+
}, /* @__PURE__ */ new Set(), mappers);
|
|
1945
|
+
}
|
|
1946
|
+
function definitions(systemLogic) {
|
|
1947
|
+
const result = /* @__PURE__ */ new Map();
|
|
1948
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1949
|
+
function add(key, logic) {
|
|
1950
|
+
result.set(key, logic);
|
|
1951
|
+
if (visited.has(logic)) return;
|
|
1952
|
+
visited.add(logic);
|
|
1953
|
+
const machine = logic;
|
|
1954
|
+
for (const [name, child] of Object.entries(machine.sources?.actors ?? {})) add(`${key}/actors/${encodeAddressSegment(name)}`, child);
|
|
1955
|
+
function visit(node) {
|
|
1956
|
+
for (const invoke of node.invoke ?? []) if (typeof invoke.logic === "object") add(`${key}/invoke/${encodeAddressSegment(node.id)}/${encodeAddressSegment(invoke.id)}`, invoke.logic);
|
|
1957
|
+
for (const child of Object.values(node.states ?? {})) visit(child);
|
|
1958
|
+
}
|
|
1959
|
+
if (machine.root) visit(machine.root);
|
|
1960
|
+
}
|
|
1961
|
+
add("root", systemLogic.root);
|
|
1962
|
+
for (const [key, logic] of Object.entries(systemLogic.actors ?? {})) add(`actors/${encodeAddressSegment(key)}`, logic);
|
|
1963
|
+
add("xstate.listener", listenerLogic);
|
|
1964
|
+
add("xstate.subscription", subscriptionLogic);
|
|
1965
|
+
return result;
|
|
1966
|
+
}
|
|
1967
|
+
var SystemReduction = class {
|
|
1968
|
+
snapshot;
|
|
1969
|
+
effects = [];
|
|
1970
|
+
logic;
|
|
1971
|
+
handles = /* @__PURE__ */ new Map();
|
|
1972
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
1973
|
+
initialEffects = /* @__PURE__ */ new Map();
|
|
1974
|
+
stopping = /* @__PURE__ */ new Set();
|
|
1975
|
+
deadlines = new IndexedHeap((a, b) => a.dueAt < b.dueAt || a.dueAt === b.dueAt && a.sequence < b.sequence);
|
|
1976
|
+
effectNames = /* @__PURE__ */ new Map([[systemLogger, "xstate.log"]]);
|
|
1977
|
+
mapperNames;
|
|
1978
|
+
mappers;
|
|
1979
|
+
remaining;
|
|
1980
|
+
constructor(logic, snapshot, maxSteps = 1e4) {
|
|
1981
|
+
if (!Number.isSafeInteger(maxSteps) || maxSteps < 1) throw new Error("maxSteps must be a positive integer.");
|
|
1982
|
+
this.logic = definitions(logic);
|
|
1983
|
+
this.mappers = logic.mappers ?? {};
|
|
1984
|
+
this.mapperNames = new Map(Object.entries(this.mappers).map(([key, mapper]) => [mapper, key]));
|
|
1985
|
+
for (const [key, implementation] of Object.entries(logic.effects ?? {})) this.effectNames.set(implementation, key);
|
|
1986
|
+
for (const [key, definition] of this.logic) for (const [name, implementation] of Object.entries(definition.sources?.actions ?? {})) if (typeof implementation === "function" && !this.effectNames.has(implementation)) this.effectNames.set(implementation, `${key}/actions/${encodeAddressSegment(name)}`);
|
|
1987
|
+
this.snapshot = {
|
|
1988
|
+
...snapshot,
|
|
1989
|
+
actors: Object.assign(Object.create(null), snapshot.actors),
|
|
1990
|
+
registry: Object.assign(Object.create(null), snapshot.registry),
|
|
1991
|
+
messages: [...snapshot.messages],
|
|
1992
|
+
timers: { ...snapshot.timers },
|
|
1993
|
+
externalEffects: { ...snapshot.externalEffects },
|
|
1994
|
+
counters: { ...snapshot.counters }
|
|
1995
|
+
};
|
|
1996
|
+
this.remaining = maxSteps;
|
|
1997
|
+
for (const timer of Object.values(snapshot.timers)) this.deadlines.push(timer);
|
|
1998
|
+
}
|
|
1999
|
+
step() {
|
|
2000
|
+
if (--this.remaining < 0) throw new Error("System transition exceeded maxSteps; the input snapshot is unchanged.");
|
|
2001
|
+
}
|
|
2002
|
+
reference(actor) {
|
|
2003
|
+
return {
|
|
2004
|
+
$actor: actor.address,
|
|
2005
|
+
incarnation: Number(actor.sessionId)
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
current(ref) {
|
|
2009
|
+
const actor = this.snapshot.actors[ref.$actor];
|
|
2010
|
+
return actor?.incarnation === ref.incarnation ? actor : void 0;
|
|
2011
|
+
}
|
|
2012
|
+
decode(value, machine) {
|
|
2013
|
+
if (isReference(value)) return this.handle(value);
|
|
2014
|
+
if (!value || typeof value !== "object") return value;
|
|
2015
|
+
if (typeof value.$systemMapper === "string") {
|
|
2016
|
+
const mapper = Object.hasOwn(this.mappers, value.$systemMapper) && this.mappers[value.$systemMapper];
|
|
2017
|
+
if (!mapper) throw new Error(`Missing pure mapper '${value.$systemMapper}'.`);
|
|
2018
|
+
return mapper;
|
|
2019
|
+
}
|
|
2020
|
+
if (typeof value.$stateNode === "string") return machine.getStateNodeById(value.$stateNode);
|
|
2021
|
+
if (typeof value.$error === "string") {
|
|
2022
|
+
const error = new Error(value.message);
|
|
2023
|
+
error.name = value.$error;
|
|
2024
|
+
if (error.name === "TimeoutError") Object.setPrototypeOf(error, TimeoutError.prototype);
|
|
2025
|
+
return error;
|
|
2026
|
+
}
|
|
2027
|
+
if (Array.isArray(value)) return value.map((item) => this.decode(item, machine));
|
|
2028
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, this.decode(item, machine)]));
|
|
2029
|
+
}
|
|
2030
|
+
encode(value) {
|
|
2031
|
+
return encode(value, /* @__PURE__ */ new Set(), this.mapperNames);
|
|
2032
|
+
}
|
|
2033
|
+
attachedKind(actor) {
|
|
2034
|
+
return this.logic.get(actor.logic)?.[systemLogicMetadata]?.kind;
|
|
2035
|
+
}
|
|
2036
|
+
relayAttachments(source, emitted) {
|
|
2037
|
+
for (const actor of Object.values(this.snapshot.actors)) {
|
|
2038
|
+
const kind = this.attachedKind(actor);
|
|
2039
|
+
if (!actor.started || actor.snapshot["status"] !== "active" || kind !== "xstate.listener" && kind !== "xstate.subscription") continue;
|
|
2040
|
+
const target = actor.input.actor;
|
|
2041
|
+
if (!target || target.$actor !== source.$actor || target.incarnation !== source.incarnation || !actor.parent || this.current(actor.parent)?.snapshot["status"] !== "active") continue;
|
|
2042
|
+
const input = this.decode(actor.input);
|
|
2043
|
+
let event;
|
|
2044
|
+
if (kind === "xstate.listener") {
|
|
2045
|
+
if (emitted && matchesEventDescriptor(emitted.type, input.eventType)) event = input.mapper(this.decode(this.encode(emitted)));
|
|
2046
|
+
} else if (!emitted) {
|
|
2047
|
+
const snapshot = this.hydrate(source);
|
|
2048
|
+
const mapper = input.mappers[snapshot.status === "active" ? "snapshot" : snapshot.status === "done" ? "done" : snapshot.status === "error" ? "error" : ""];
|
|
2049
|
+
if (mapper) event = mapper(snapshot.status === "done" ? snapshot.output : snapshot.status === "error" ? snapshot.error : snapshot);
|
|
2050
|
+
}
|
|
2051
|
+
if (event) this.enqueue({
|
|
2052
|
+
$actor: actor.address,
|
|
2053
|
+
incarnation: actor.incarnation
|
|
2054
|
+
}, actor.parent, event);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
stopAttachments(parent) {
|
|
2058
|
+
for (const actor of Object.values(this.snapshot.actors)) {
|
|
2059
|
+
const kind = this.attachedKind(actor);
|
|
2060
|
+
if ((kind === "xstate.listener" || kind === "xstate.subscription") && actor.parent?.$actor === parent.$actor && actor.parent.incarnation === parent.incarnation) this.stop({
|
|
2061
|
+
$actor: actor.address,
|
|
2062
|
+
incarnation: actor.incarnation
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
hydrate(ref) {
|
|
2067
|
+
const key = JSON.stringify(ref);
|
|
2068
|
+
if (this.snapshots.has(key)) return this.snapshots.get(key);
|
|
2069
|
+
const actor = this.current(ref);
|
|
2070
|
+
if (!actor) return {
|
|
2071
|
+
status: "stopped",
|
|
2072
|
+
output: void 0,
|
|
2073
|
+
error: void 0
|
|
2074
|
+
};
|
|
2075
|
+
const logic = this.logic.get(actor.logic);
|
|
2076
|
+
if (!logic || logic.version !== actor.logicVersion) throw new Error(`Actor definition/version mismatch for '${actor.address}'.`);
|
|
2077
|
+
const data = this.decode(actor.snapshot, logic);
|
|
2078
|
+
const snapshot = typeof logic.getStateNodeById === "function" && data.nodeIds ? createMachineSnapshot({
|
|
2079
|
+
...data,
|
|
2080
|
+
_nodes: data.nodeIds.map((id) => logic.getStateNodeById(id))
|
|
2081
|
+
}, logic) : data;
|
|
2082
|
+
this.snapshots.set(key, snapshot);
|
|
2083
|
+
return snapshot;
|
|
2084
|
+
}
|
|
2085
|
+
handle(ref) {
|
|
2086
|
+
const key = JSON.stringify(ref);
|
|
2087
|
+
const existing = this.handles.get(key);
|
|
2088
|
+
if (existing) return existing;
|
|
2089
|
+
const actor = this.current(ref);
|
|
2090
|
+
const getState = () => this.current(ref) ?? actor;
|
|
2091
|
+
const getLogic = () => this.logic.get(getState()?.logic ?? "");
|
|
2092
|
+
const getParent = () => {
|
|
2093
|
+
const parent = getState()?.parent;
|
|
2094
|
+
return parent && this.handle(parent);
|
|
2095
|
+
};
|
|
2096
|
+
const getSnapshot = () => this.hydrate(ref);
|
|
2097
|
+
const unsupported = () => {
|
|
2098
|
+
throw new Error("Use enqueue operations and systemTransition; live actor capabilities are unavailable in a pure snapshot.");
|
|
2099
|
+
};
|
|
2100
|
+
const handle = {
|
|
2101
|
+
[systemActorHandle]: true,
|
|
2102
|
+
id: actor?.id ?? ref.$actor.split("/").at(-1),
|
|
2103
|
+
address: ref.$actor,
|
|
2104
|
+
sessionId: String(ref.incarnation),
|
|
2105
|
+
options: {
|
|
2106
|
+
_inert: true,
|
|
2107
|
+
input: actor?.input
|
|
2108
|
+
},
|
|
2109
|
+
get logic() {
|
|
2110
|
+
return getLogic();
|
|
2111
|
+
},
|
|
2112
|
+
get src() {
|
|
2113
|
+
return getState()?.logic;
|
|
2114
|
+
},
|
|
2115
|
+
get registryKey() {
|
|
2116
|
+
return getState()?.registryKey;
|
|
2117
|
+
},
|
|
2118
|
+
get _parent() {
|
|
2119
|
+
return getParent();
|
|
2120
|
+
},
|
|
2121
|
+
get _snapshot() {
|
|
2122
|
+
return getSnapshot();
|
|
2123
|
+
},
|
|
2124
|
+
getSnapshot,
|
|
2125
|
+
send: unsupported,
|
|
2126
|
+
start: unsupported,
|
|
2127
|
+
stop: unsupported,
|
|
2128
|
+
subscribe: unsupported,
|
|
2129
|
+
on: unsupported,
|
|
2130
|
+
system: void 0
|
|
2131
|
+
};
|
|
2132
|
+
this.handles.set(key, handle);
|
|
2133
|
+
handle.system = this.system();
|
|
2134
|
+
return handle;
|
|
2135
|
+
}
|
|
2136
|
+
system() {
|
|
2137
|
+
return {
|
|
2138
|
+
createActorRef: (logic, options) => this.allocate(logic, options),
|
|
2139
|
+
_unregister: (actor) => {
|
|
2140
|
+
this.stopping.add(JSON.stringify(this.reference(actor)));
|
|
2141
|
+
for (const [key, ref] of Object.entries(this.snapshot.registry)) if (ref.$actor === actor.address && String(ref.incarnation) === actor.sessionId) delete this.snapshot.registry[key];
|
|
2142
|
+
},
|
|
2143
|
+
get: (key) => this.snapshot.registry[key] && this.handle(this.snapshot.registry[key]),
|
|
2144
|
+
getAll: () => Object.fromEntries(Object.entries(this.snapshot.registry).map(([key, ref]) => [key, this.handle(ref)])),
|
|
2145
|
+
_hasInspectionObservers: () => false,
|
|
2146
|
+
_sendInspectionEvent: () => {},
|
|
2147
|
+
_clock: {
|
|
2148
|
+
now: () => this.snapshot.now,
|
|
2149
|
+
setTimeout: () => {
|
|
2150
|
+
throw new Error("Timers must be declared as logical effects.");
|
|
2151
|
+
},
|
|
2152
|
+
clearTimeout: () => {
|
|
2153
|
+
throw new Error("Timers must be canceled as logical effects.");
|
|
2154
|
+
}
|
|
2155
|
+
},
|
|
2156
|
+
_wallClock: { now: () => this.snapshot.now }
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
scope(ref, source) {
|
|
2160
|
+
const self = this.handle(ref);
|
|
2161
|
+
self._lastSourceRef = source && this.handle(source);
|
|
2162
|
+
return {
|
|
2163
|
+
self,
|
|
2164
|
+
system: self.system,
|
|
2165
|
+
id: self.id,
|
|
2166
|
+
sessionId: self.sessionId,
|
|
2167
|
+
logger: systemLogger,
|
|
2168
|
+
warn: defaultWarn,
|
|
2169
|
+
defer: () => {},
|
|
2170
|
+
emit: () => {
|
|
2171
|
+
throw new Error("Emit events through enqueue operations.");
|
|
2172
|
+
},
|
|
2173
|
+
actionExecutor: () => {
|
|
2174
|
+
throw new Error("Execute external effects outside system transitions.");
|
|
2175
|
+
},
|
|
2176
|
+
stopChild: () => {
|
|
2177
|
+
throw new Error("Stop children through enqueue operations.");
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
allocate(logic, options = {}) {
|
|
2182
|
+
this.step();
|
|
2183
|
+
const definition = [...this.logic].find(([, value]) => value === logic);
|
|
2184
|
+
if (!definition) throw new Error("Register dynamically spawned logic in systemLogic.actors.");
|
|
2185
|
+
const parent = options.parent && this.reference(options.parent);
|
|
2186
|
+
const id = options.id ?? `${getActorIdPrefix(options.src ?? logic)}:${this.snapshot.counters.actor}`;
|
|
2187
|
+
const address = `${parent ? parent.$actor + "/" : ""}${encodeAddressSegment(id)}`;
|
|
2188
|
+
const occupied = this.snapshot.actors[address];
|
|
2189
|
+
if (occupied?.snapshot["status"] === "active") {
|
|
2190
|
+
const previous = {
|
|
2191
|
+
$actor: address,
|
|
2192
|
+
incarnation: occupied.incarnation
|
|
2193
|
+
};
|
|
2194
|
+
if (!this.stopping.has(JSON.stringify(previous))) throw new Error(`Actor address '${address}' is already occupied.`);
|
|
2195
|
+
this.stop(previous);
|
|
2196
|
+
}
|
|
2197
|
+
const incarnation = this.snapshot.counters.actor++;
|
|
2198
|
+
const ref = {
|
|
2199
|
+
$actor: address,
|
|
2200
|
+
incarnation
|
|
2201
|
+
};
|
|
2202
|
+
const state = {
|
|
2203
|
+
id,
|
|
2204
|
+
address,
|
|
2205
|
+
incarnation,
|
|
2206
|
+
logic: definition[0],
|
|
2207
|
+
...logic.version !== void 0 && { logicVersion: logic.version },
|
|
2208
|
+
parent,
|
|
2209
|
+
registryKey: options.registryKey,
|
|
2210
|
+
syncSnapshot: options.syncSnapshot,
|
|
2211
|
+
started: false,
|
|
2212
|
+
input: this.encode(options.input),
|
|
2213
|
+
snapshot: {
|
|
2214
|
+
status: "active",
|
|
2215
|
+
output: void 0,
|
|
2216
|
+
error: void 0
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
this.snapshot.actors[address] = state;
|
|
2220
|
+
if (options.registryKey) {
|
|
2221
|
+
if (this.snapshot.registry[options.registryKey]) throw new Error(`Registry key '${options.registryKey}' is already occupied.`);
|
|
2222
|
+
this.snapshot.registry[options.registryKey] = ref;
|
|
2223
|
+
}
|
|
2224
|
+
const scope = this.scope(ref);
|
|
2225
|
+
const result = logic.initialTransition(this.decode(state.input), scope);
|
|
2226
|
+
const [snapshot, effects] = finalizeTransitionResult(scope, void 0, result);
|
|
2227
|
+
this.store(ref, snapshot);
|
|
2228
|
+
this.initialEffects.set(JSON.stringify(ref), effects);
|
|
2229
|
+
return this.handle(ref);
|
|
2230
|
+
}
|
|
2231
|
+
store(ref, snapshot) {
|
|
2232
|
+
const actor = this.current(ref);
|
|
2233
|
+
if (!actor) return;
|
|
2234
|
+
this.snapshots.set(JSON.stringify(ref), snapshot);
|
|
2235
|
+
this.snapshot.actors[ref.$actor] = {
|
|
2236
|
+
...actor,
|
|
2237
|
+
snapshot: snapshotData(snapshot, this.mapperNames)
|
|
2238
|
+
};
|
|
2239
|
+
}
|
|
2240
|
+
external(ref, effect) {
|
|
2241
|
+
const value = effect;
|
|
2242
|
+
if (value.kind === "action" && value.action === void 0) return;
|
|
2243
|
+
const type = value.kind === "action" && !value.type.startsWith("xstate.logic.") ? this.effectNames.get(value.action) : value.type;
|
|
2244
|
+
if (type === void 0) throw new Error("Register callable external actions in systemLogic.effects or the machine action sources.");
|
|
2245
|
+
const id = `${this.snapshot.executionId}:${this.snapshot.counters.effect++}`;
|
|
2246
|
+
const params = typeof value.params?.action === "function" ? Object.fromEntries(Object.entries(value.params).filter(([key]) => key !== "action" && key !== "args")) : value.params;
|
|
2247
|
+
const result = {
|
|
2248
|
+
id,
|
|
2249
|
+
source: ref,
|
|
2250
|
+
type,
|
|
2251
|
+
kind: value.kind === "emit" ? "emit" : value.type === "@xstate.deadLetter" ? "deadLetter" : "action",
|
|
2252
|
+
...params !== void 0 && { params: this.encode(params) },
|
|
2253
|
+
args: this.encode(value.args ?? []),
|
|
2254
|
+
...value.event && { event: this.encode(value.event) },
|
|
2255
|
+
...value.reason && { reason: value.reason }
|
|
2256
|
+
};
|
|
2257
|
+
this.effects.push(result);
|
|
2258
|
+
this.snapshot.externalEffects[id] = result;
|
|
2259
|
+
if (value.kind === "emit") this.relayAttachments(ref, value.event);
|
|
2260
|
+
}
|
|
2261
|
+
enqueue(source, target, event, timerOccurrence) {
|
|
2262
|
+
this.snapshot.messages.push({
|
|
2263
|
+
source,
|
|
2264
|
+
target,
|
|
2265
|
+
event: this.encode(event),
|
|
2266
|
+
sequence: this.snapshot.counters.sequence++,
|
|
2267
|
+
...timerOccurrence !== void 0 && { timerOccurrence }
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
cancel(ref, id) {
|
|
2271
|
+
for (const [key, timer] of Object.entries(this.snapshot.timers)) if (timer.source.$actor === ref.$actor && timer.source.incarnation === ref.incarnation && (id === void 0 || timer.id === id)) delete this.snapshot.timers[key];
|
|
2272
|
+
}
|
|
2273
|
+
effectsFor(ref, effects) {
|
|
2274
|
+
for (const effect of effects) {
|
|
2275
|
+
const value = effect;
|
|
2276
|
+
if (value.kind !== "builtin") {
|
|
2277
|
+
this.external(ref, effect);
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
switch (value.type) {
|
|
2281
|
+
case "@xstate.spawn": break;
|
|
2282
|
+
case "@xstate.start":
|
|
2283
|
+
this.start(this.reference(value.actor));
|
|
2284
|
+
break;
|
|
2285
|
+
case "@xstate.stop":
|
|
2286
|
+
this.stop(this.reference(value.actor));
|
|
2287
|
+
break;
|
|
2288
|
+
case "@xstate.cancel":
|
|
2289
|
+
this.cancel(ref, value.id);
|
|
2290
|
+
break;
|
|
2291
|
+
case "@xstate.raise":
|
|
2292
|
+
case "@xstate.sendTo":
|
|
2293
|
+
if (value.delay !== void 0) {
|
|
2294
|
+
if (!Number.isFinite(value.delay) || value.delay < 0) throw new Error("System timer delays must be finite and nonnegative.");
|
|
2295
|
+
if (!this.current(ref)?.snapshot["timers"]?.[value.id]) break;
|
|
2296
|
+
this.schedule(ref, value.id, value.delay);
|
|
2297
|
+
} else if (value.target) this.enqueue(ref, this.reference(value.target), value.event);
|
|
2298
|
+
break;
|
|
2299
|
+
case "@xstate.terminate":
|
|
2300
|
+
this.terminate(ref);
|
|
2301
|
+
break;
|
|
2302
|
+
default: this.external(ref, effect);
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
schedule(ref, id, delay, event) {
|
|
2307
|
+
const dueAt = this.snapshot.now + delay;
|
|
2308
|
+
if (!Number.isFinite(delay) || delay < 0 || !Number.isFinite(dueAt)) throw new Error("System timer deadlines must be finite and delays nonnegative.");
|
|
2309
|
+
const timer = {
|
|
2310
|
+
source: ref,
|
|
2311
|
+
id,
|
|
2312
|
+
occurrence: this.snapshot.counters.timer++,
|
|
2313
|
+
scheduledAt: this.snapshot.now,
|
|
2314
|
+
dueAt,
|
|
2315
|
+
sequence: this.snapshot.counters.sequence++,
|
|
2316
|
+
...event && { event: this.encode(event) }
|
|
2317
|
+
};
|
|
2318
|
+
this.snapshot.timers[timerKey(ref, id)] = timer;
|
|
2319
|
+
this.deadlines.push(timer);
|
|
2320
|
+
}
|
|
2321
|
+
start(ref) {
|
|
2322
|
+
const actor = this.current(ref);
|
|
2323
|
+
if (!actor || actor.started || actor.snapshot["status"] === "stopped") return;
|
|
2324
|
+
this.snapshot.actors[ref.$actor] = {
|
|
2325
|
+
...actor,
|
|
2326
|
+
started: true
|
|
2327
|
+
};
|
|
2328
|
+
this.effectsFor(ref, this.initialEffects.get(JSON.stringify(ref)) ?? []);
|
|
2329
|
+
this.initialEffects.delete(JSON.stringify(ref));
|
|
2330
|
+
if (this.attachedKind(actor) === "xstate.subscription") {
|
|
2331
|
+
const input = this.decode(actor.input);
|
|
2332
|
+
const target = this.reference(input.actor);
|
|
2333
|
+
const observed = this.current(target);
|
|
2334
|
+
if (observed?.started && observed.snapshot["status"] === "error" && input.mappers.error && actor.parent) this.enqueue(ref, actor.parent, input.mappers.error(this.hydrate(target).error));
|
|
2335
|
+
}
|
|
2336
|
+
if (this.current(ref)?.snapshot["status"] === "active") this.relayAttachments(ref);
|
|
2337
|
+
const metadata = this.logic.get(actor.logic)[systemLogicMetadata];
|
|
2338
|
+
if (metadata?.timeout !== void 0 && this.current(ref)?.snapshot["status"] === "active") {
|
|
2339
|
+
const delay = parseDelayToMilliseconds(metadata.timeout);
|
|
2340
|
+
if (delay === void 0) throw new Error("Unable to resolve async actor timeout.");
|
|
2341
|
+
this.schedule(ref, "xstate.async.timeout", delay, {
|
|
2342
|
+
type: "xstate.async.reject",
|
|
2343
|
+
data: new TimeoutError(metadata.timeout)
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
stop(ref) {
|
|
2348
|
+
const actor = this.current(ref);
|
|
2349
|
+
if (!actor || actor.snapshot["status"] !== "active") return;
|
|
2350
|
+
this.process({
|
|
2351
|
+
source: ref,
|
|
2352
|
+
target: ref,
|
|
2353
|
+
event: { type: "@xstate.stop" },
|
|
2354
|
+
sequence: this.snapshot.counters.sequence++
|
|
2355
|
+
});
|
|
2356
|
+
this.cancel(ref);
|
|
2357
|
+
this.handle(ref).system._unregister(this.handle(ref));
|
|
2358
|
+
this.snapshot.messages = this.snapshot.messages.filter((message) => message.target.$actor !== ref.$actor || message.target.incarnation !== ref.incarnation);
|
|
2359
|
+
this.cancelExternal(ref);
|
|
2360
|
+
this.stopAttachments(ref);
|
|
2361
|
+
}
|
|
2362
|
+
cancelExternal(ref) {
|
|
2363
|
+
for (const [id, effect] of Object.entries(this.snapshot.externalEffects)) {
|
|
2364
|
+
if (effect.kind !== "action" || effect.type === "xstate.system.cancelEffect" || effect.type === "xstate.logic.cleanup" || effect.source.$actor !== ref.$actor || effect.source.incarnation !== ref.incarnation) continue;
|
|
2365
|
+
delete this.snapshot.externalEffects[id];
|
|
2366
|
+
const cancellation = {
|
|
2367
|
+
id: `${this.snapshot.executionId}:${this.snapshot.counters.effect++}`,
|
|
2368
|
+
source: ref,
|
|
2369
|
+
kind: "action",
|
|
2370
|
+
type: "xstate.system.cancelEffect",
|
|
2371
|
+
params: { effectId: id }
|
|
2372
|
+
};
|
|
2373
|
+
this.effects.push(cancellation);
|
|
2374
|
+
this.snapshot.externalEffects[cancellation.id] = cancellation;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
terminate(ref) {
|
|
2378
|
+
const actor = this.current(ref);
|
|
2379
|
+
if (!actor) return;
|
|
2380
|
+
this.relayAttachments(ref);
|
|
2381
|
+
this.cancel(ref);
|
|
2382
|
+
this.cancelExternal(ref);
|
|
2383
|
+
this.stopAttachments(ref);
|
|
2384
|
+
if (actor.parent) {
|
|
2385
|
+
const event = actor.snapshot["status"] === "done" ? createDoneActorEvent(actor.id, this.decode(actor.snapshot["output"]), String(actor.incarnation)) : createErrorActorEvent(actor.id, this.decode(actor.snapshot["error"]), String(actor.incarnation));
|
|
2386
|
+
this.enqueue(ref, actor.parent, event);
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
process(message) {
|
|
2390
|
+
this.step();
|
|
2391
|
+
const actor = this.current(message.target);
|
|
2392
|
+
if (!actor || actor.snapshot["status"] !== "active") return;
|
|
2393
|
+
if (message.event.type === "xstate.timer" && message.timerOccurrence === void 0) throw new Error("Only the system scheduler may deliver a timer occurrence.");
|
|
2394
|
+
const logic = this.logic.get(actor.logic);
|
|
2395
|
+
if (!logic) throw new Error(`Missing actor definition '${actor.logic}'.`);
|
|
2396
|
+
if (logic.isInternalEventType?.(message.event.type) && (!message.source || message.source.$actor !== actor.address || message.source.incarnation !== actor.incarnation)) throw new Error(`Internal event '${message.event.type}' cannot be injected into a pure system.`);
|
|
2397
|
+
if (!message.source && /^(xstate\.after(?:\.|$)|xstate\.timeout(?:\.|$))/.test(message.event.type)) throw new Error("Select a pending timer instead of injecting its internal event.");
|
|
2398
|
+
const scope = this.scope(message.target, message.source);
|
|
2399
|
+
const previous = this.hydrate(message.target);
|
|
2400
|
+
const [snapshot, effects] = finalizeTransitionResult(scope, previous, logic.transition(previous, this.decode(message.event), scope));
|
|
2401
|
+
this.store(message.target, snapshot);
|
|
2402
|
+
this.effectsFor(message.target, effects);
|
|
2403
|
+
if (snapshot.status === "active") this.relayAttachments(message.target);
|
|
2404
|
+
if (actor.syncSnapshot && actor.parent && snapshot.status === "active") this.enqueue(message.target, actor.parent, {
|
|
2405
|
+
type: "xstate.snapshot.actor",
|
|
2406
|
+
actorId: actor.id,
|
|
2407
|
+
sessionId: String(actor.incarnation),
|
|
2408
|
+
snapshot: snapshotData(snapshot, this.mapperNames)
|
|
2409
|
+
});
|
|
2410
|
+
}
|
|
2411
|
+
settle() {
|
|
2412
|
+
while (this.snapshot.messages.length) {
|
|
2413
|
+
const message = this.snapshot.messages.shift();
|
|
2414
|
+
this.process(message);
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
advance(time) {
|
|
2418
|
+
if (!Number.isFinite(time) || time < this.snapshot.now) throw new Error("System time must be finite and cannot move backwards.");
|
|
2419
|
+
this.settle();
|
|
2420
|
+
while (true) {
|
|
2421
|
+
const timer = this.deadlines.peek();
|
|
2422
|
+
if (timer && this.snapshot.timers[timerKey(timer.source, timer.id)] !== timer) {
|
|
2423
|
+
this.deadlines.pop();
|
|
2424
|
+
continue;
|
|
2425
|
+
}
|
|
2426
|
+
if (!timer || timer.dueAt > time) break;
|
|
2427
|
+
this.step();
|
|
2428
|
+
this.deadlines.pop();
|
|
2429
|
+
delete this.snapshot.timers[timerKey(timer.source, timer.id)];
|
|
2430
|
+
this.snapshot.now = Math.max(this.snapshot.now, timer.dueAt);
|
|
2431
|
+
const actor = this.current(timer.source);
|
|
2432
|
+
if (!actor || actor.snapshot["status"] !== "active" || !timer.event && !actor.snapshot["timers"]?.[timer.id]) continue;
|
|
2433
|
+
this.enqueue(timer.source, timer.source, timer.event ?? {
|
|
2434
|
+
type: "xstate.timer",
|
|
2435
|
+
id: timer.id
|
|
2436
|
+
}, timer.occurrence);
|
|
2437
|
+
this.settle();
|
|
2438
|
+
}
|
|
2439
|
+
this.snapshot.now = time;
|
|
2440
|
+
}
|
|
2441
|
+
send(path, event) {
|
|
2442
|
+
if (event.type === "xstate.timer") {
|
|
2443
|
+
const actor = this.snapshot.actors[path];
|
|
2444
|
+
if (!actor) throw new Error(`Unknown actor address '${path}'.`);
|
|
2445
|
+
const timer = this.snapshot.timers[timerKey({
|
|
2446
|
+
$actor: path,
|
|
2447
|
+
incarnation: actor.incarnation
|
|
2448
|
+
}, event["id"])];
|
|
2449
|
+
if (!timer || event["occurrence"] !== timer.occurrence) throw new Error("Select a current timer occurrence.");
|
|
2450
|
+
this.advance(timer.dueAt);
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
this.advance(this.snapshot.now);
|
|
2454
|
+
const actor = this.snapshot.actors[path];
|
|
2455
|
+
if (!actor) throw new Error(`Unknown actor address '${path}'.`);
|
|
2456
|
+
const ref = {
|
|
2457
|
+
$actor: path,
|
|
2458
|
+
incarnation: actor.incarnation
|
|
2459
|
+
};
|
|
2460
|
+
if (event.type === "xstate.system.effect.result") {
|
|
2461
|
+
const effect = this.snapshot.externalEffects[event["effectId"]];
|
|
2462
|
+
if (!effect || effect.source.$actor !== path || event["event"] && (effect.source.incarnation !== actor.incarnation || actor.snapshot["status"] !== "active" || effect.type === "xstate.system.cancelEffect" || effect.type === "xstate.logic.cleanup")) throw new Error("External effect result does not match its owner or is stale.");
|
|
2463
|
+
delete this.snapshot.externalEffects[event["effectId"]];
|
|
2464
|
+
if (event["event"]) {
|
|
2465
|
+
this.enqueue(ref, ref, event["event"]);
|
|
2466
|
+
this.settle();
|
|
2467
|
+
}
|
|
2468
|
+
return;
|
|
2469
|
+
}
|
|
2470
|
+
if (event.type === "@xstate.stop") {
|
|
2471
|
+
this.stop(ref);
|
|
2472
|
+
this.settle();
|
|
2473
|
+
return;
|
|
2474
|
+
}
|
|
2475
|
+
if (event.type.startsWith("xstate.async.") || event.type.startsWith("xstate.logic.effect.")) throw new Error("Deliver external work results with xstate.system.effect.result.");
|
|
2476
|
+
this.enqueue(void 0, ref, event);
|
|
2477
|
+
this.settle();
|
|
2478
|
+
}
|
|
2479
|
+
initialize(input, id, registryKey) {
|
|
2480
|
+
const root = this.allocate(this.logic.get("root"), {
|
|
2481
|
+
id,
|
|
2482
|
+
input,
|
|
2483
|
+
registryKey
|
|
2484
|
+
});
|
|
2485
|
+
this.snapshot.root = root.address;
|
|
2486
|
+
this.start(this.reference(root));
|
|
2487
|
+
this.settle();
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
/** Initialize all actors and return external work without executing it. @experimental */
|
|
2491
|
+
function initialSystemTransition(systemLogic, options = {}) {
|
|
2492
|
+
const now = options.time ?? 0;
|
|
2493
|
+
if (!Number.isFinite(now) || now < 0) throw new Error("Initial system time must be finite and nonnegative.");
|
|
2494
|
+
const reduction = new SystemReduction(systemLogic, {
|
|
2495
|
+
root: "",
|
|
2496
|
+
executionId: options.executionId ?? "system",
|
|
2497
|
+
now,
|
|
2498
|
+
actors: {},
|
|
2499
|
+
registry: {},
|
|
2500
|
+
messages: [],
|
|
2501
|
+
timers: {},
|
|
2502
|
+
externalEffects: {},
|
|
2503
|
+
counters: {
|
|
2504
|
+
actor: 0,
|
|
2505
|
+
timer: 0,
|
|
2506
|
+
sequence: 0,
|
|
2507
|
+
effect: 0
|
|
2508
|
+
}
|
|
2509
|
+
}, options.maxSteps);
|
|
2510
|
+
reduction.initialize(options.input, options.id ?? systemLogic.root.id ?? "root", options.registryKey);
|
|
2511
|
+
return [reduction.snapshot, reduction.effects];
|
|
2512
|
+
}
|
|
2513
|
+
/** Complete actor macrosteps and immediate communication in an immutable snapshot. @experimental */
|
|
2514
|
+
function systemTransition(systemLogic, snapshot, actorPath, event) {
|
|
2515
|
+
const reduction = new SystemReduction(systemLogic, snapshot);
|
|
2516
|
+
reduction.send(actorPath, event);
|
|
2517
|
+
return [reduction.snapshot, reduction.effects];
|
|
2518
|
+
}
|
|
2519
|
+
/** Process every due timer chronologically across the entire system snapshot. @experimental */
|
|
2520
|
+
function advanceSystemTime(systemLogic, snapshot, options) {
|
|
2521
|
+
const reduction = new SystemReduction(systemLogic, snapshot, options.maxSteps);
|
|
2522
|
+
reduction.advance(options.time);
|
|
2523
|
+
return [reduction.snapshot, reduction.effects];
|
|
2524
|
+
}
|
|
2525
|
+
// src/toPromise.ts
|
|
2526
|
+
/**
|
|
2527
|
+
* Returns a promise that resolves to the `output` of the actor when it is done.
|
|
2528
|
+
*
|
|
2529
|
+
* @example
|
|
2530
|
+
*
|
|
2531
|
+
* ```ts
|
|
2532
|
+
* const machine = createMachine({
|
|
2533
|
+
* // ...
|
|
2534
|
+
* output: {
|
|
2535
|
+
* count: 42
|
|
2536
|
+
* }
|
|
2537
|
+
* });
|
|
2538
|
+
*
|
|
2539
|
+
* const actor = createActor(machine);
|
|
2540
|
+
*
|
|
2541
|
+
* actor.start();
|
|
2542
|
+
*
|
|
2543
|
+
* const output = await toPromise(actor);
|
|
2544
|
+
*
|
|
2545
|
+
* console.log(output);
|
|
2546
|
+
* // logs { count: 42 }
|
|
2547
|
+
* ```
|
|
2548
|
+
* @public
|
|
2549
|
+
*/
|
|
2550
|
+
function toPromise(actor) {
|
|
2551
|
+
return new Promise((resolve, reject) => {
|
|
2552
|
+
actor.subscribe({
|
|
2553
|
+
complete: () => {
|
|
2554
|
+
resolve(actor.getSnapshot().output);
|
|
2555
|
+
},
|
|
2556
|
+
error: reject
|
|
2557
|
+
});
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
// src/waitFor.ts
|
|
2561
|
+
const defaultWaitForOptions = { timeout: Infinity };
|
|
2562
|
+
/**
|
|
2563
|
+
* Subscribes to an actor ref and waits for its emitted value to satisfy a
|
|
2564
|
+
* predicate, and then resolves with that value. Will throw if the desired state
|
|
2565
|
+
* is not reached after an optional timeout. (defaults to Infinity).
|
|
2566
|
+
*
|
|
2567
|
+
* @example
|
|
2568
|
+
*
|
|
2569
|
+
* ```js
|
|
2570
|
+
* const state = await waitFor(someService, (state) => {
|
|
2571
|
+
* return state.hasTag('loaded');
|
|
2572
|
+
* });
|
|
2573
|
+
*
|
|
2574
|
+
* state.hasTag('loaded'); // true
|
|
2575
|
+
* ```
|
|
2576
|
+
*
|
|
2577
|
+
* @param actorRef The actor ref to subscribe to
|
|
2578
|
+
* @param predicate Determines if a value matches the condition to wait for
|
|
2579
|
+
* @param options
|
|
2580
|
+
* @returns A promise that eventually resolves to the emitted value that matches
|
|
2581
|
+
* the condition
|
|
2582
|
+
* @public
|
|
2583
|
+
*/
|
|
2584
|
+
function waitFor(actorRef, predicate, options) {
|
|
2585
|
+
const resolvedOptions = {
|
|
2586
|
+
...defaultWaitForOptions,
|
|
2587
|
+
...options
|
|
2588
|
+
};
|
|
2589
|
+
return new Promise((res, rej) => {
|
|
2590
|
+
const { signal } = resolvedOptions;
|
|
2591
|
+
if (signal?.aborted) {
|
|
2592
|
+
rej(signal.reason);
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
let done = false;
|
|
2596
|
+
const handle = resolvedOptions.timeout === Infinity ? void 0 : setTimeout(() => {
|
|
2597
|
+
dispose();
|
|
2598
|
+
rej(/* @__PURE__ */ new Error(`Timeout of ${resolvedOptions.timeout} ms exceeded`));
|
|
2599
|
+
}, resolvedOptions.timeout);
|
|
2600
|
+
const dispose = () => {
|
|
2601
|
+
clearTimeout(handle);
|
|
2602
|
+
done = true;
|
|
2603
|
+
sub?.unsubscribe();
|
|
2604
|
+
if (abortListener) signal.removeEventListener("abort", abortListener);
|
|
2605
|
+
};
|
|
2606
|
+
function checkEmitted(emitted) {
|
|
2607
|
+
if (predicate(emitted)) {
|
|
2608
|
+
dispose();
|
|
2609
|
+
res(emitted);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
/**
|
|
2613
|
+
* If the `signal` option is provided, this will be the listener for its
|
|
2614
|
+
* `abort` event
|
|
2615
|
+
*/
|
|
2616
|
+
let abortListener;
|
|
2617
|
+
let sub;
|
|
2618
|
+
checkEmitted(actorRef.getSnapshot());
|
|
2619
|
+
if (done) return;
|
|
2620
|
+
if (signal) {
|
|
2621
|
+
abortListener = () => {
|
|
2622
|
+
dispose();
|
|
2623
|
+
rej(signal.reason);
|
|
2624
|
+
};
|
|
2625
|
+
signal.addEventListener("abort", abortListener);
|
|
2626
|
+
}
|
|
2627
|
+
sub = actorRef.subscribe({
|
|
2628
|
+
next: checkEmitted,
|
|
2629
|
+
error: (err) => {
|
|
2630
|
+
dispose();
|
|
2631
|
+
rej(err);
|
|
2632
|
+
},
|
|
2633
|
+
complete: () => {
|
|
2634
|
+
dispose();
|
|
2635
|
+
rej(/* @__PURE__ */ new Error(`Actor terminated without satisfying predicate`));
|
|
2636
|
+
}
|
|
2637
|
+
});
|
|
2638
|
+
if (done) sub.unsubscribe();
|
|
2639
|
+
});
|
|
2640
|
+
}
|
|
2641
|
+
export { createStateConfig as _, systemTransition as a, SpecialTargets as b, setup as c, serializeMachine as d, mapState as f, createMachineFromCompiledConfig as g, createMachine as h, initialSystemTransition as i, hotSwapActorLogic as l, createMachineFromConfig as m, toPromise as n, SimulatedClock as o, machineVersions as p, advanceSystemTime as r, createSystem as s, waitFor as t, machineConfigToJSON as u, StateMachine as v, assertEvent as y };
|