xstate 6.0.0-alpha.4 → 6.0.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -17
- package/bin/xstate.js +36 -0
- package/dist/StateMachine-8e73d782.cjs.js +1200 -0
- package/dist/StateMachine-db4e6d4b.development.cjs.js +1232 -0
- package/dist/StateMachine-ee89d49f.esm.js +1197 -0
- package/dist/StateMachine-f60abe05.development.esm.js +1229 -0
- package/dist/createActor-20615484.development.cjs.js +5717 -0
- package/dist/createActor-a3f7750f.esm.js +5578 -0
- package/dist/createActor-c97c04c9.development.esm.js +5620 -0
- package/dist/createActor-f3afd774.cjs.js +5675 -0
- package/dist/declarations/src/State.d.ts +17 -4
- package/dist/declarations/src/StateMachine.d.ts +37 -16
- package/dist/declarations/src/StateNode.d.ts +5 -2
- package/dist/declarations/src/actions.d.ts +10 -7
- package/dist/declarations/src/actors/callback.d.ts +2 -0
- package/dist/declarations/src/actors/logic.d.ts +7 -24
- package/dist/declarations/src/actors/observable.d.ts +3 -0
- package/dist/declarations/src/actors/promise.d.ts +6 -2
- package/dist/declarations/src/createActor.d.ts +64 -20
- package/dist/declarations/src/createMachine.d.ts +21 -13
- package/dist/declarations/src/createMachineFromConfig.d.ts +64 -30
- package/dist/declarations/src/durable/index.d.ts +133 -0
- package/dist/declarations/src/effectDescriptor.d.ts +81 -0
- package/dist/declarations/src/fsm.d.ts +77 -0
- package/dist/declarations/src/index.d.ts +15 -8
- package/dist/declarations/src/inspection.d.ts +11 -1
- package/dist/declarations/src/machineVersion.types.d.ts +38 -0
- package/dist/declarations/src/machineVersions.d.ts +100 -0
- package/dist/declarations/src/runtimeHelpers.d.ts +35 -0
- package/dist/declarations/src/schema.types.d.ts +5 -0
- package/dist/declarations/src/serialize.d.ts +9 -22
- package/dist/declarations/src/setup.d.ts +317 -64
- package/dist/declarations/src/spawn.d.ts +5 -5
- package/dist/declarations/src/stateUtils.d.ts +19 -7
- package/dist/declarations/src/system.d.ts +74 -11
- package/dist/declarations/src/transitionActions.d.ts +14 -2
- package/dist/declarations/src/types.d.ts +318 -134
- package/dist/declarations/src/types.v6.d.ts +306 -52
- package/dist/declarations/src/utils.d.ts +5 -3
- package/dist/declarations/src/validation/index.d.ts +37 -0
- package/dist/declarations/src/validation.types.d.ts +22 -0
- package/dist/effectDescriptor-57ae11fa.esm.js +250 -0
- package/dist/effectDescriptor-b7c48ceb.development.esm.js +250 -0
- package/dist/effectDescriptor-ba9e14f0.development.cjs.js +257 -0
- package/dist/effectDescriptor-e2bb61e2.cjs.js +257 -0
- package/dist/index-7fd92683.cjs.js +941 -0
- package/dist/index-a8ce72f1.esm.js +932 -0
- package/dist/index-b5b1b2bc.development.esm.js +932 -0
- package/dist/index-d42efc07.development.cjs.js +941 -0
- package/dist/xstate-actors.cjs.js +6 -5
- package/dist/xstate-actors.development.cjs.js +6 -5
- package/dist/xstate-actors.development.esm.js +2 -1
- package/dist/xstate-actors.esm.js +2 -1
- package/dist/xstate-actors.umd.min.js +1 -1
- package/dist/xstate-actors.umd.min.js.map +1 -1
- package/dist/xstate-durable.cjs.d.mts +2 -0
- package/dist/xstate-durable.cjs.d.ts +2 -0
- package/dist/xstate-durable.cjs.js +327 -0
- package/dist/xstate-durable.cjs.mjs +5 -0
- package/dist/xstate-durable.development.cjs.js +327 -0
- package/dist/xstate-durable.development.cjs.mjs +5 -0
- package/dist/xstate-durable.development.esm.js +321 -0
- package/dist/xstate-durable.esm.js +321 -0
- package/dist/xstate-durable.umd.min.js +2 -0
- package/dist/xstate-durable.umd.min.js.map +1 -0
- package/dist/xstate-graph.cjs.js +16 -19
- package/dist/xstate-graph.development.cjs.js +16 -19
- package/dist/xstate-graph.development.esm.js +13 -16
- package/dist/xstate-graph.esm.js +13 -16
- package/dist/xstate-graph.umd.min.js +1 -1
- package/dist/xstate-graph.umd.min.js.map +1 -1
- package/dist/xstate-validation.cjs.d.mts +2 -0
- package/dist/xstate-validation.cjs.d.ts +2 -0
- package/dist/xstate-validation.cjs.js +277 -0
- package/dist/xstate-validation.cjs.mjs +5 -0
- package/dist/xstate-validation.development.cjs.js +277 -0
- package/dist/xstate-validation.development.cjs.mjs +5 -0
- package/dist/xstate-validation.development.esm.js +271 -0
- package/dist/xstate-validation.esm.js +271 -0
- package/dist/xstate-validation.umd.min.js +2 -0
- package/dist/xstate-validation.umd.min.js.map +1 -0
- package/dist/xstate.cjs.js +1361 -412
- package/dist/xstate.cjs.mjs +9 -0
- package/dist/xstate.development.cjs.js +1361 -412
- package/dist/xstate.development.cjs.mjs +9 -0
- package/dist/xstate.development.esm.js +1332 -392
- package/dist/xstate.esm.js +1332 -392
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/durable/package.json +8 -0
- package/package.json +39 -3
- package/validation/package.json +8 -0
- package/dist/StateMachine-445a8480.cjs.js +0 -732
- package/dist/StateMachine-4deb4090.development.esm.js +0 -746
- package/dist/StateMachine-f310bc72.esm.js +0 -729
- package/dist/StateMachine-f83f01f9.development.cjs.js +0 -749
- package/dist/index-918ab496.cjs.js +0 -4397
- package/dist/index-9cce3480.esm.js +0 -4342
- package/dist/index-ccbe7c0b.development.esm.js +0 -4381
- package/dist/index-ebaf6fd8.development.cjs.js +0 -4436
package/dist/xstate.esm.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
export { T as TimeoutError, c as createAsyncLogic, a as createCallbackLogic, b as createEmptyActor, d as createEventObservableLogic, e as createLogic, f as createObservableLogic } from './index-a8ce72f1.esm.js';
|
|
2
|
+
import { m as matchesEventDescriptor, t as toArray$1, b as builtInActions, a as beginSpawnAllocation, X as XSTATE_INIT, f as finalizeTransitionResult, d as deriveDeferredStarts, c as createTransitionEnqueue, w as withActorScope, e as XSTATE_STOP, r as resolveActionsWithContext, g as XSTATE_TIMER, h as createSendToEffect, i as isLazyActorScope, p as parseDelayToMilliseconds, j as isAtomicStateNode, k as createActor } from './createActor-a3f7750f.esm.js';
|
|
3
|
+
export { A as Actor, y as __unsafe_getAllOwnEventDescriptors, B as checkStateIn, k as createActor, l as createListenerLogic, n as createSubscriptionLogic, G as deliverEvent, E as executeEffects, q as getInitialSnapshot, u as getNextSnapshot, x as getStateNodes, F as isBuiltInExecutableAction, v as isMachineSnapshot, o as listenerLogic, z as matchesState, C as pathToStateValue, H as runStep, I as stopActor, s as subscriptionLogic, J as terminateActor, D as toObserver } from './createActor-a3f7750f.esm.js';
|
|
4
|
+
import { S as StateMachine } from './StateMachine-ee89d49f.esm.js';
|
|
5
|
+
export { S as StateMachine, a as StateNode } from './StateMachine-ee89d49f.esm.js';
|
|
6
|
+
export { c as getEffectDescriptor, a as getInitialMicrosteps, g as getMicrosteps, b as getNextTransitions, i as initialTransition, t as transition } from './effectDescriptor-57ae11fa.esm.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Asserts that the given event object is of the specified type or types. Throws
|
|
@@ -27,7 +29,7 @@ export { S as StateMachine, a as StateNode } from './StateMachine-f310bc72.esm.j
|
|
|
27
29
|
* ```
|
|
28
30
|
*/
|
|
29
31
|
function assertEvent(event, type) {
|
|
30
|
-
const types = toArray(type);
|
|
32
|
+
const types = toArray$1(type);
|
|
31
33
|
const matches = types.some(descriptor => matchesEventDescriptor(event.type, descriptor));
|
|
32
34
|
if (!matches) {
|
|
33
35
|
const typesText = types.length === 1 ? `type matching "${types[0]}"` : `one of types matching "${types.join('", "')}"`;
|
|
@@ -75,7 +77,7 @@ function assertEvent(event, type) {
|
|
|
75
77
|
*
|
|
76
78
|
* @param config The state machine configuration.
|
|
77
79
|
* @param options DEPRECATED: use `setup({ ... })` or `machine.provide({ ... })`
|
|
78
|
-
* to provide machine
|
|
80
|
+
* to provide machine sources instead.
|
|
79
81
|
*/
|
|
80
82
|
// Overload 1: With schemas.context — context type inferred from schema
|
|
81
83
|
|
|
@@ -89,11 +91,392 @@ function createStateConfig(config) {
|
|
|
89
91
|
return config;
|
|
90
92
|
}
|
|
91
93
|
|
|
94
|
+
const emptySources = {
|
|
95
|
+
actions: {},
|
|
96
|
+
actors: {},
|
|
97
|
+
guards: {},
|
|
98
|
+
delays: {}
|
|
99
|
+
};
|
|
100
|
+
const emptyExecutableActions = [];
|
|
101
|
+
const emptyRawActions = [];
|
|
102
|
+
const builtInActionSet = new Set(Object.values(builtInActions));
|
|
103
|
+
function toArray(value) {
|
|
104
|
+
return value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
105
|
+
}
|
|
106
|
+
function resolveContext(context, input) {
|
|
107
|
+
return typeof context === 'function' ? context({
|
|
108
|
+
input
|
|
109
|
+
}) : context ?? {};
|
|
110
|
+
}
|
|
111
|
+
function resolveTransitionContext(context, args) {
|
|
112
|
+
return typeof context === 'function' ? context(args) : context;
|
|
113
|
+
}
|
|
114
|
+
function resolveInput(input, context, event) {
|
|
115
|
+
return typeof input === 'function' ? input({
|
|
116
|
+
context,
|
|
117
|
+
event
|
|
118
|
+
}) : input;
|
|
119
|
+
}
|
|
120
|
+
function mergeContextPatch(context, patch) {
|
|
121
|
+
for (const key of Object.keys(patch)) {
|
|
122
|
+
if (!Object.prototype.hasOwnProperty.call(context, key) || !Object.is(context[key], patch[key])) {
|
|
123
|
+
return {
|
|
124
|
+
...context,
|
|
125
|
+
...patch
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return context;
|
|
130
|
+
}
|
|
131
|
+
function createSnapshot(value, context, input, machine, stateInput) {
|
|
132
|
+
return {
|
|
133
|
+
status: 'active',
|
|
134
|
+
output: undefined,
|
|
135
|
+
error: undefined,
|
|
136
|
+
value,
|
|
137
|
+
context,
|
|
138
|
+
input,
|
|
139
|
+
children: {},
|
|
140
|
+
timers: {},
|
|
141
|
+
_nextTimerId: 0,
|
|
142
|
+
_stateInput: stateInput,
|
|
143
|
+
machine
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function cloneSnapshot(snapshot, value, context, stateInput) {
|
|
147
|
+
return {
|
|
148
|
+
status: snapshot.status,
|
|
149
|
+
output: snapshot.output,
|
|
150
|
+
error: snapshot.error,
|
|
151
|
+
value,
|
|
152
|
+
context,
|
|
153
|
+
input: snapshot.input,
|
|
154
|
+
children: snapshot.children,
|
|
155
|
+
timers: snapshot.timers,
|
|
156
|
+
_nextTimerId: snapshot._nextTimerId,
|
|
157
|
+
// Carried across state changes: dropping these would let a later spawn
|
|
158
|
+
// reuse the id of a still-running child.
|
|
159
|
+
_nextActorIds: snapshot._nextActorIds,
|
|
160
|
+
_stateInput: stateInput,
|
|
161
|
+
machine: snapshot.machine
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function stopSnapshot(snapshot) {
|
|
165
|
+
return {
|
|
166
|
+
status: 'stopped',
|
|
167
|
+
output: undefined,
|
|
168
|
+
error: undefined,
|
|
169
|
+
value: snapshot.value,
|
|
170
|
+
context: snapshot.context,
|
|
171
|
+
input: undefined,
|
|
172
|
+
children: snapshot.children,
|
|
173
|
+
timers: snapshot.timers,
|
|
174
|
+
_nextTimerId: snapshot._nextTimerId,
|
|
175
|
+
_nextActorIds: snapshot._nextActorIds,
|
|
176
|
+
_stateInput: snapshot._stateInput,
|
|
177
|
+
machine: snapshot.machine
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function assertNoStringTransitions(config) {
|
|
181
|
+
for (const [stateKey, stateConfig] of Object.entries(config.states)) {
|
|
182
|
+
for (const [eventType, transitionConfig] of Object.entries(stateConfig.on ?? {})) {
|
|
183
|
+
for (const transition of toArray(transitionConfig)) {
|
|
184
|
+
if (typeof transition === 'string') {
|
|
185
|
+
const target = transition;
|
|
186
|
+
throw new Error(`Invalid transition for "${stateKey}.${eventType}": use { target: "${target}" } instead of a string target.`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function resolveSimpleEnqueuedActions(rawActions) {
|
|
193
|
+
const executableActions = [];
|
|
194
|
+
for (const action of rawActions) {
|
|
195
|
+
if (!action || typeof action !== 'object' || !('action' in action) || typeof action.action !== 'function' || builtInActionSet.has(action.action) || '_special' in action.action) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
executableActions.push({
|
|
199
|
+
kind: 'action',
|
|
200
|
+
type: action.action.name || '(anonymous)',
|
|
201
|
+
params: undefined,
|
|
202
|
+
args: action.args,
|
|
203
|
+
action: action.action,
|
|
204
|
+
exec() {
|
|
205
|
+
return this.action?.(...this.args);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return executableActions;
|
|
210
|
+
}
|
|
211
|
+
function createFSM(config) {
|
|
212
|
+
const machine = {
|
|
213
|
+
id: config.id ?? '(fsm)',
|
|
214
|
+
sources: emptySources
|
|
215
|
+
};
|
|
216
|
+
assertNoStringTransitions(config);
|
|
217
|
+
const runActions = (snapshot, event, actorScope, rawActions) => {
|
|
218
|
+
if (!rawActions?.length) {
|
|
219
|
+
return [snapshot, emptyExecutableActions];
|
|
220
|
+
}
|
|
221
|
+
const simpleExecutableActions = resolveSimpleEnqueuedActions(rawActions);
|
|
222
|
+
if (simpleExecutableActions) {
|
|
223
|
+
return [snapshot, simpleExecutableActions];
|
|
224
|
+
}
|
|
225
|
+
return resolveActionsWithContext(snapshot, event, actorScope, rawActions);
|
|
226
|
+
};
|
|
227
|
+
const runStateActions = (snapshot, event, actorScope, actionsConfig, stateInput, internalQueue) => {
|
|
228
|
+
if (!actionsConfig) {
|
|
229
|
+
return [snapshot, emptyExecutableActions];
|
|
230
|
+
}
|
|
231
|
+
const actions = [];
|
|
232
|
+
const enq = createTransitionEnqueue(actorScope, actions, internalQueue, true);
|
|
233
|
+
let context;
|
|
234
|
+
const actionCount = Array.isArray(actionsConfig) ? actionsConfig.length : 1;
|
|
235
|
+
for (let i = 0; i < actionCount; i++) {
|
|
236
|
+
const action = Array.isArray(actionsConfig) ? actionsConfig[i] : actionsConfig;
|
|
237
|
+
const result = action(withActorScope({
|
|
238
|
+
context: context ?? snapshot.context,
|
|
239
|
+
event: event,
|
|
240
|
+
input: stateInput,
|
|
241
|
+
value: snapshot.value,
|
|
242
|
+
children: snapshot.children
|
|
243
|
+
}, actorScope), enq);
|
|
244
|
+
if (result?.context !== undefined) {
|
|
245
|
+
const currentContext = context ?? snapshot.context;
|
|
246
|
+
const nextContext = mergeContextPatch(currentContext, result.context);
|
|
247
|
+
if (nextContext !== currentContext) {
|
|
248
|
+
context = nextContext;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const nextSnapshot = context !== undefined ? cloneSnapshot(snapshot, snapshot.value, context, snapshot._stateInput) : snapshot;
|
|
253
|
+
return runActions(nextSnapshot, event, actorScope, actions);
|
|
254
|
+
};
|
|
255
|
+
const selectTransition = (snapshot, event, actorScope, internalQueue) => {
|
|
256
|
+
const state = config.states[snapshot.value];
|
|
257
|
+
const transitionsConfig = state?.on?.[event.type];
|
|
258
|
+
if (!transitionsConfig) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
const transitionCount = Array.isArray(transitionsConfig) ? transitionsConfig.length : 1;
|
|
262
|
+
for (let i = 0; i < transitionCount; i++) {
|
|
263
|
+
const transition = Array.isArray(transitionsConfig) ? transitionsConfig[i] : transitionsConfig;
|
|
264
|
+
const args = withActorScope({
|
|
265
|
+
context: snapshot.context,
|
|
266
|
+
event,
|
|
267
|
+
input: snapshot.input,
|
|
268
|
+
value: snapshot.value,
|
|
269
|
+
children: snapshot.children
|
|
270
|
+
}, actorScope);
|
|
271
|
+
if (typeof transition === 'function') {
|
|
272
|
+
const actions = [];
|
|
273
|
+
const enq = createTransitionEnqueue(actorScope, actions, internalQueue, true);
|
|
274
|
+
const result = transition(args, enq);
|
|
275
|
+
if (!result) {
|
|
276
|
+
if (actions.length) {
|
|
277
|
+
return {
|
|
278
|
+
actions
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
target: result.target,
|
|
285
|
+
context: result.context,
|
|
286
|
+
input: result.input,
|
|
287
|
+
actions
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if ('guard' in transition && transition.guard && !transition.guard(args)) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
return {
|
|
294
|
+
target: transition.target,
|
|
295
|
+
context: resolveTransitionContext(transition.context, args),
|
|
296
|
+
input: transition.input,
|
|
297
|
+
actions: 'actions' in transition && transition.actions ? toArray(transition.actions) : emptyRawActions
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
return undefined;
|
|
301
|
+
};
|
|
302
|
+
const transitionCore = (snapshot, event, actorScope) => {
|
|
303
|
+
if (snapshot.status !== 'active') {
|
|
304
|
+
return [snapshot, []];
|
|
305
|
+
}
|
|
306
|
+
if (event.type === XSTATE_STOP) {
|
|
307
|
+
const actions = [];
|
|
308
|
+
const enqueue = createTransitionEnqueue(actorScope, actions, []);
|
|
309
|
+
for (const id of Object.keys(snapshot.timers)) {
|
|
310
|
+
enqueue.cancel(id);
|
|
311
|
+
}
|
|
312
|
+
const [withoutTimers, cancelEffects] = resolveActionsWithContext(snapshot, event, actorScope, actions);
|
|
313
|
+
return [stopSnapshot(withoutTimers), cancelEffects];
|
|
314
|
+
}
|
|
315
|
+
if (event.type === XSTATE_TIMER) {
|
|
316
|
+
const timer = snapshot.timers[event.id];
|
|
317
|
+
if (!timer) {
|
|
318
|
+
return [snapshot, emptyExecutableActions];
|
|
319
|
+
}
|
|
320
|
+
const timers = {
|
|
321
|
+
...snapshot.timers
|
|
322
|
+
};
|
|
323
|
+
delete timers[timer.id];
|
|
324
|
+
const nextSnapshot = {
|
|
325
|
+
...snapshot,
|
|
326
|
+
timers
|
|
327
|
+
};
|
|
328
|
+
if (timer.type === '@xstate.raise') {
|
|
329
|
+
return transitionCore(nextSnapshot, timer.event, actorScope);
|
|
330
|
+
}
|
|
331
|
+
const target = timer.target === 'self' ? actorScope.self : timer.target;
|
|
332
|
+
return [nextSnapshot, [createSendToEffect(actorScope, target, timer.event)]];
|
|
333
|
+
}
|
|
334
|
+
const stateConfig = config.states[snapshot.value];
|
|
335
|
+
const directTransition = stateConfig?.on?.[event.type];
|
|
336
|
+
if (!directTransition) {
|
|
337
|
+
return [snapshot, emptyExecutableActions];
|
|
338
|
+
}
|
|
339
|
+
if (!Array.isArray(directTransition) && typeof directTransition !== 'function' && !('guard' in directTransition) && !('actions' in directTransition && directTransition.actions) && typeof directTransition.input !== 'function') {
|
|
340
|
+
const target = directTransition.target ?? snapshot.value;
|
|
341
|
+
const stateChanged = target !== snapshot.value;
|
|
342
|
+
if (stateChanged && (stateConfig.exit || config.states[target]?.entry)) ; else {
|
|
343
|
+
const hasContext = directTransition.context !== undefined;
|
|
344
|
+
const hasInput = directTransition.input !== undefined;
|
|
345
|
+
const resolvedContext = !isLazyActorScope(actorScope) ? resolveTransitionContext(directTransition.context, {
|
|
346
|
+
context: snapshot.context,
|
|
347
|
+
event,
|
|
348
|
+
input: snapshot.input,
|
|
349
|
+
value: snapshot.value,
|
|
350
|
+
self: actorScope.self,
|
|
351
|
+
system: actorScope.system,
|
|
352
|
+
parent: actorScope.self._parent,
|
|
353
|
+
children: snapshot.children
|
|
354
|
+
}) : typeof directTransition.context === 'function' ? directTransition.context(withActorScope({
|
|
355
|
+
context: snapshot.context,
|
|
356
|
+
event,
|
|
357
|
+
input: snapshot.input,
|
|
358
|
+
value: snapshot.value,
|
|
359
|
+
children: snapshot.children
|
|
360
|
+
}, actorScope)) : directTransition.context;
|
|
361
|
+
const context = hasContext && resolvedContext ? mergeContextPatch(snapshot.context, resolvedContext) : snapshot.context;
|
|
362
|
+
if (!stateChanged && context === snapshot.context && !hasInput && snapshot._stateInput === undefined) {
|
|
363
|
+
return [snapshot, emptyExecutableActions];
|
|
364
|
+
}
|
|
365
|
+
return [cloneSnapshot(snapshot, target, context, hasInput ? resolveInput(directTransition.input, context, event) : undefined), emptyExecutableActions];
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (typeof directTransition === 'function' && directTransition.length < 2 && !stateConfig?.exit) {
|
|
369
|
+
const result = directTransition(withActorScope({
|
|
370
|
+
context: snapshot.context,
|
|
371
|
+
event,
|
|
372
|
+
input: snapshot.input,
|
|
373
|
+
value: snapshot.value,
|
|
374
|
+
children: snapshot.children
|
|
375
|
+
}, actorScope), undefined);
|
|
376
|
+
if (result) {
|
|
377
|
+
const target = result.target ?? snapshot.value;
|
|
378
|
+
if (!config.states[target]?.entry) {
|
|
379
|
+
const hasContext = result.context !== undefined;
|
|
380
|
+
const hasInput = result.input !== undefined;
|
|
381
|
+
const context = hasContext && result.context ? mergeContextPatch(snapshot.context, result.context) : snapshot.context;
|
|
382
|
+
if (target === snapshot.value && context === snapshot.context && !hasInput && snapshot._stateInput === undefined) {
|
|
383
|
+
return [snapshot, emptyExecutableActions];
|
|
384
|
+
}
|
|
385
|
+
return [cloneSnapshot(snapshot, target, context, hasInput ? resolveInput(result.input, context, event) : undefined), emptyExecutableActions];
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
let nextSnapshot = snapshot;
|
|
390
|
+
const executableActions = [];
|
|
391
|
+
const internalQueue = [event];
|
|
392
|
+
let iterations = 0;
|
|
393
|
+
while (internalQueue.length) {
|
|
394
|
+
if (++iterations > 1000) {
|
|
395
|
+
throw new Error('FSM microstep count exceeded 1000');
|
|
396
|
+
}
|
|
397
|
+
const nextEvent = internalQueue.shift();
|
|
398
|
+
const selected = selectTransition(nextSnapshot, nextEvent, actorScope, internalQueue);
|
|
399
|
+
if (!selected) {
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
const nextValue = selected.target ?? nextSnapshot.value;
|
|
403
|
+
const stateChanged = nextValue !== nextSnapshot.value;
|
|
404
|
+
if (stateChanged) {
|
|
405
|
+
const [exited, exitActions] = runStateActions(nextSnapshot, nextEvent, actorScope, config.states[nextSnapshot.value]?.exit, nextSnapshot._stateInput, internalQueue);
|
|
406
|
+
nextSnapshot = exited;
|
|
407
|
+
executableActions.push(...exitActions);
|
|
408
|
+
}
|
|
409
|
+
let context = nextSnapshot.context;
|
|
410
|
+
if (selected.context !== undefined) {
|
|
411
|
+
context = mergeContextPatch(context, selected.context);
|
|
412
|
+
}
|
|
413
|
+
const hasInput = selected.input !== undefined;
|
|
414
|
+
const stateInput = hasInput ? resolveInput(selected.input, context, nextEvent) : undefined;
|
|
415
|
+
if (stateChanged || context !== nextSnapshot.context || hasInput || nextSnapshot._stateInput !== undefined) {
|
|
416
|
+
nextSnapshot = cloneSnapshot(nextSnapshot, nextValue, context, stateInput);
|
|
417
|
+
}
|
|
418
|
+
const [afterTransition, transitionActions] = runActions(nextSnapshot, nextEvent, actorScope, selected.actions);
|
|
419
|
+
nextSnapshot = afterTransition;
|
|
420
|
+
executableActions.push(...transitionActions);
|
|
421
|
+
if (stateChanged) {
|
|
422
|
+
const [entered, entryActions] = runStateActions(nextSnapshot, nextEvent, actorScope, config.states[nextValue]?.entry, stateInput, internalQueue);
|
|
423
|
+
nextSnapshot = entered;
|
|
424
|
+
executableActions.push(...entryActions);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return [nextSnapshot, executableActions];
|
|
428
|
+
};
|
|
429
|
+
const transition = (...args) => {
|
|
430
|
+
beginSpawnAllocation(args[2]);
|
|
431
|
+
const [nextSnapshot, effects] = transitionCore(...args);
|
|
432
|
+
return finalizeTransitionResult(args[2], args[0], [nextSnapshot, [...effects, ...deriveDeferredStarts(effects)]]);
|
|
433
|
+
};
|
|
434
|
+
const logic = {
|
|
435
|
+
id: config.id,
|
|
436
|
+
config,
|
|
437
|
+
transition,
|
|
438
|
+
initialTransition: (input, actorScope) => {
|
|
439
|
+
beginSpawnAllocation(actorScope);
|
|
440
|
+
const context = resolveContext(config.context, input);
|
|
441
|
+
const snapshot = createSnapshot(config.initial, context, input, machine);
|
|
442
|
+
const internalQueue = [];
|
|
443
|
+
let [nextSnapshot, actions] = runStateActions(snapshot, {
|
|
444
|
+
type: XSTATE_INIT
|
|
445
|
+
}, actorScope, config.states[config.initial]?.entry, undefined, internalQueue);
|
|
446
|
+
if (!actions.length) {
|
|
447
|
+
actions = [];
|
|
448
|
+
}
|
|
449
|
+
while (internalQueue.length) {
|
|
450
|
+
const [raisedSnapshot, raisedActions] = transitionCore(nextSnapshot, internalQueue.shift(), actorScope);
|
|
451
|
+
nextSnapshot = raisedSnapshot;
|
|
452
|
+
actions.push(...raisedActions);
|
|
453
|
+
}
|
|
454
|
+
return finalizeTransitionResult(actorScope, undefined, [nextSnapshot, [...actions, ...deriveDeferredStarts(actions)]]);
|
|
455
|
+
},
|
|
456
|
+
getInitialSnapshot: (actorScope, input) => logic.initialTransition(input, actorScope)[0],
|
|
457
|
+
getPersistedSnapshot: ({
|
|
458
|
+
machine: _,
|
|
459
|
+
...snapshot
|
|
460
|
+
}) => snapshot,
|
|
461
|
+
restoreSnapshot: snapshot => ({
|
|
462
|
+
...snapshot,
|
|
463
|
+
machine: machine
|
|
464
|
+
})
|
|
465
|
+
};
|
|
466
|
+
return logic;
|
|
467
|
+
}
|
|
468
|
+
|
|
92
469
|
function delayToMs(delay) {
|
|
93
470
|
const parsedDelay = parseDelayToMilliseconds(delay);
|
|
94
471
|
if (parsedDelay !== undefined) return parsedDelay;
|
|
95
472
|
return typeof delay === 'string' ? parseFloat(delay) || 0 : delay;
|
|
96
473
|
}
|
|
474
|
+
function normalizeScxmlEventType(eventType) {
|
|
475
|
+
return /^error(\.|$)/.test(eventType) ? `xstate.${eventType}` : eventType;
|
|
476
|
+
}
|
|
477
|
+
function toScxmlEventName(eventType) {
|
|
478
|
+
return eventType.startsWith('xstate.error.') ? eventType.slice('xstate.'.length) : eventType;
|
|
479
|
+
}
|
|
97
480
|
|
|
98
481
|
/**
|
|
99
482
|
* Isolated executable-content block. Errors in nested actions stop this block
|
|
@@ -101,48 +484,23 @@ function delayToMs(delay) {
|
|
|
101
484
|
* separate <onentry>/<onexit> blocks: each is its own block per spec.
|
|
102
485
|
*/
|
|
103
486
|
|
|
104
|
-
function
|
|
105
|
-
return !!value && typeof value === 'object' &&
|
|
487
|
+
function isExpression(value) {
|
|
488
|
+
return !!value && typeof value === 'object' && typeof value['@expr'] === 'string';
|
|
106
489
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
if (value.$unserializable === 'actor' && path.endsWith('.src')) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
throw new Error(`Unresolved ${value.$unserializable} at ${path}`);
|
|
113
|
-
}
|
|
114
|
-
if (!value || typeof value !== 'object') {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (Array.isArray(value)) {
|
|
118
|
-
value.forEach((item, index) => assertNoUnresolvedMarkers(item, `${path}[${index}]`));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
for (const key of Object.keys(value)) {
|
|
122
|
-
const child = value[key];
|
|
123
|
-
const isImplementationMarker = path === '$' && (key === 'actions' || key === 'guards' || key === 'actors' || key === 'delays' || key === 'schemas');
|
|
124
|
-
if (isImplementationMarker) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
assertNoUnresolvedMarkers(child, `${path}.${key}`);
|
|
128
|
-
}
|
|
490
|
+
function isCode(value) {
|
|
491
|
+
return !!value && typeof value === 'object' && typeof value['@code'] === 'string';
|
|
129
492
|
}
|
|
130
|
-
function
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (!implementations[kind]?.[key]) {
|
|
142
|
-
throw new Error(`Missing ${kind}.${key}`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
493
|
+
function isResolvable(value) {
|
|
494
|
+
return isExpression(value) || isCode(value);
|
|
495
|
+
}
|
|
496
|
+
function isBuiltInActionType(type) {
|
|
497
|
+
return type.startsWith('@xstate.');
|
|
498
|
+
}
|
|
499
|
+
function isScxmlActionType(type) {
|
|
500
|
+
return type.startsWith('scxml.');
|
|
501
|
+
}
|
|
502
|
+
function toPath(parent, key) {
|
|
503
|
+
return typeof key === 'number' ? `${parent}[${key}]` : `${parent}.${key}`;
|
|
146
504
|
}
|
|
147
505
|
function extractSerializableDelays(delays) {
|
|
148
506
|
const result = {};
|
|
@@ -153,22 +511,267 @@ function extractSerializableDelays(delays) {
|
|
|
153
511
|
const value = delays[key];
|
|
154
512
|
if (typeof value === 'number') {
|
|
155
513
|
result[key] = value;
|
|
514
|
+
} else if (value && typeof value === 'object' && typeof value.duration === 'number') {
|
|
515
|
+
result[key] = value.duration;
|
|
156
516
|
}
|
|
157
517
|
}
|
|
158
518
|
return result;
|
|
159
519
|
}
|
|
160
|
-
function
|
|
520
|
+
function mergeSources(json, sources) {
|
|
161
521
|
return {
|
|
162
|
-
actions:
|
|
163
|
-
guards:
|
|
164
|
-
actors:
|
|
522
|
+
actions: sources.actions ?? {},
|
|
523
|
+
guards: sources.guards ?? {},
|
|
524
|
+
actors: sources.actors ?? {},
|
|
165
525
|
delays: {
|
|
166
526
|
...extractSerializableDelays(json.delays),
|
|
167
|
-
...(
|
|
527
|
+
...(sources.delays ?? {})
|
|
528
|
+
},
|
|
529
|
+
evaluators: sources.evaluators ?? {}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
function createExpressionResolver(expressionLanguage, sources) {
|
|
533
|
+
function getEvaluator(value, path) {
|
|
534
|
+
const lang = value['@lang'] ?? expressionLanguage;
|
|
535
|
+
if (!lang) {
|
|
536
|
+
throw new Error(`Missing @exprLang for expression at ${path}`);
|
|
537
|
+
}
|
|
538
|
+
const evaluator = sources.evaluators[lang];
|
|
539
|
+
if (!evaluator) {
|
|
540
|
+
throw new Error(`Missing evaluator for @lang '${lang}' at ${path}`);
|
|
168
541
|
}
|
|
542
|
+
return evaluator;
|
|
543
|
+
}
|
|
544
|
+
function evaluateResolvable(value, slot, scope, path) {
|
|
545
|
+
const kind = isExpression(value) ? 'expr' : 'code';
|
|
546
|
+
const source = isExpression(value) ? value['@expr'] : value['@code'];
|
|
547
|
+
return getEvaluator(value, path)({
|
|
548
|
+
source,
|
|
549
|
+
kind,
|
|
550
|
+
slot,
|
|
551
|
+
scope,
|
|
552
|
+
path
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
function resolveValue(value, slot, scope, path) {
|
|
556
|
+
if (isResolvable(value)) {
|
|
557
|
+
return evaluateResolvable(value, slot, scope, path);
|
|
558
|
+
}
|
|
559
|
+
if (Array.isArray(value)) {
|
|
560
|
+
return value.map((item, index) => resolveValue(item, slot, scope, toPath(path, index)));
|
|
561
|
+
}
|
|
562
|
+
if (!value || typeof value !== 'object') {
|
|
563
|
+
return value;
|
|
564
|
+
}
|
|
565
|
+
const result = {};
|
|
566
|
+
for (const key of Object.keys(value)) {
|
|
567
|
+
result[key] = resolveValue(value[key], slot, scope, toPath(path, key));
|
|
568
|
+
}
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
function makeScope(x, extra) {
|
|
572
|
+
return {
|
|
573
|
+
context: x.context,
|
|
574
|
+
event: x.event,
|
|
575
|
+
input: x.input,
|
|
576
|
+
self: x.self,
|
|
577
|
+
children: x.children,
|
|
578
|
+
params: x.params,
|
|
579
|
+
...extra
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
function getDurationConfig(value, path) {
|
|
583
|
+
if (!isResolvable(value)) {
|
|
584
|
+
return value;
|
|
585
|
+
}
|
|
586
|
+
return args => delayToMs(resolveValue(value, 'delay', makeScope(args), path));
|
|
587
|
+
}
|
|
588
|
+
function assertResolvable(value, path) {
|
|
589
|
+
if (isResolvable(value)) {
|
|
590
|
+
getEvaluator(value, path);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
if (Array.isArray(value)) {
|
|
594
|
+
value.forEach((item, index) => assertResolvable(item, toPath(path, index)));
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
if (!value || typeof value !== 'object') {
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
for (const key of Object.keys(value)) {
|
|
601
|
+
assertResolvable(value[key], toPath(path, key));
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return {
|
|
605
|
+
evaluateResolvable,
|
|
606
|
+
resolveValue,
|
|
607
|
+
makeScope,
|
|
608
|
+
getDurationConfig,
|
|
609
|
+
assertResolvable
|
|
169
610
|
};
|
|
170
611
|
}
|
|
171
|
-
|
|
612
|
+
function toActionArray(actions) {
|
|
613
|
+
return actions === undefined ? [] : Array.isArray(actions) ? actions : [actions];
|
|
614
|
+
}
|
|
615
|
+
function validateChoiceConfig(choice, path) {
|
|
616
|
+
if (!Array.isArray(choice)) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
choice.forEach((branch, index) => {
|
|
620
|
+
if (branch.when === undefined && index !== choice.length - 1) {
|
|
621
|
+
throw new Error(`Choice fallback branch at ${path}[${index}] must be last.`);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
function assertMachineJSON(json, resolvedSources, expressionResolver) {
|
|
626
|
+
const {
|
|
627
|
+
assertResolvable
|
|
628
|
+
} = expressionResolver;
|
|
629
|
+
function assertCondition(condition, path) {
|
|
630
|
+
if (!condition) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (isResolvable(condition)) {
|
|
634
|
+
assertResolvable(condition, path);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
assertResolvable(condition.params, `${path}.params`);
|
|
638
|
+
if (json.guards?.[condition.type]) {
|
|
639
|
+
assertCondition(json.guards[condition.type].when, `$.guards.${condition.type}.when`);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
if (!resolvedSources.guards[condition.type] && !['scxml.cond', 'xstate.stateIn', 'xstate.not'].includes(condition.type)) {
|
|
643
|
+
throw new Error(`Missing guard source "${condition.type}"`);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
function assertAction(action, path, stack = []) {
|
|
647
|
+
if (isResolvable(action)) {
|
|
648
|
+
assertResolvable(action, path);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (!action || typeof action.type !== 'string') {
|
|
652
|
+
throw new Error(`Invalid action at ${path}`);
|
|
653
|
+
}
|
|
654
|
+
assertResolvable(action.params, `${path}.params`);
|
|
655
|
+
if (isBuiltInActionType(action.type) || isScxmlActionType(action.type)) {
|
|
656
|
+
assertResolvable(action, path);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const definition = json.actions?.[action.type];
|
|
660
|
+
if (definition) {
|
|
661
|
+
if (stack.includes(action.type)) {
|
|
662
|
+
throw new Error(`Circular action reference: ${stack.concat(action.type).join(' -> ')}`);
|
|
663
|
+
}
|
|
664
|
+
const definitions = Array.isArray(definition) ? definition : [definition];
|
|
665
|
+
definitions.forEach((item, index) => assertAction(item, `${path}.actions.${action.type}${definitions.length > 1 ? `[${index}]` : ''}`, stack.concat(action.type)));
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
if (!resolvedSources.actions[action.type]) {
|
|
669
|
+
throw new Error(`Missing action source "${action.type}"`);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function assertActions(actions, path) {
|
|
673
|
+
toActionArray(actions).forEach((action, index) => assertAction(action, `${path}[${index}]`));
|
|
674
|
+
}
|
|
675
|
+
function assertTransition(transition, path) {
|
|
676
|
+
const transitions = Array.isArray(transition) ? transition : transition ? [transition] : [];
|
|
677
|
+
transitions.forEach((t, index) => {
|
|
678
|
+
const transitionPath = Array.isArray(transition) ? `${path}[${index}]` : path;
|
|
679
|
+
if (isResolvable(t)) {
|
|
680
|
+
assertResolvable(t, transitionPath);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
assertCondition(t.guard, `${transitionPath}.guard`);
|
|
684
|
+
assertActions(t.actions, `${transitionPath}.actions`);
|
|
685
|
+
assertResolvable(t.context, `${transitionPath}.context`);
|
|
686
|
+
assertResolvable(t.input, `${transitionPath}.input`);
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
function assertStateNode(node, path) {
|
|
690
|
+
if ((node.type === 'history' || node.history !== undefined) && !(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))) {
|
|
691
|
+
throw new Error(`History state at ${path} must declare a non-empty target.`);
|
|
692
|
+
}
|
|
693
|
+
assertResolvable(node.context, `${path}.context`);
|
|
694
|
+
assertResolvable(node.input, `${path}.input`);
|
|
695
|
+
assertResolvable(node.output, `${path}.output`);
|
|
696
|
+
assertResolvable(node.timeout, `${path}.timeout`);
|
|
697
|
+
assertActions(node.entry, `${path}.entry`);
|
|
698
|
+
assertActions(node.exit, `${path}.exit`);
|
|
699
|
+
if (node.type === 'choice') {
|
|
700
|
+
if (!node.choice) {
|
|
701
|
+
throw new Error(`Choice state at ${path} must declare choice.`);
|
|
702
|
+
}
|
|
703
|
+
if (Array.isArray(node.choice)) {
|
|
704
|
+
validateChoiceConfig(node.choice, `${path}.choice`);
|
|
705
|
+
node.choice.forEach((branch, index) => {
|
|
706
|
+
assertCondition(branch.when, `${path}.choice[${index}].when`);
|
|
707
|
+
assertResolvable(branch.context, `${path}.choice[${index}].context`);
|
|
708
|
+
assertResolvable(branch.input, `${path}.choice[${index}].input`);
|
|
709
|
+
});
|
|
710
|
+
} else {
|
|
711
|
+
assertResolvable(node.choice, `${path}.choice`);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (isResolvable(node.route)) {
|
|
715
|
+
assertResolvable(node.route, `${path}.route`);
|
|
716
|
+
}
|
|
717
|
+
if (node.invoke) {
|
|
718
|
+
const invokes = Array.isArray(node.invoke) ? node.invoke : [node.invoke];
|
|
719
|
+
invokes.forEach((invoke, index) => {
|
|
720
|
+
const invokePath = `${path}.invoke${Array.isArray(node.invoke) ? `[${index}]` : ''}`;
|
|
721
|
+
const extInv = invoke;
|
|
722
|
+
if (!extInv._nestedMachineJSON && !resolvedSources.actors[invoke.src]) {
|
|
723
|
+
throw new Error(`Missing actor source "${invoke.src}"`);
|
|
724
|
+
}
|
|
725
|
+
assertResolvable(invoke.input, `${invokePath}.input`);
|
|
726
|
+
assertResolvable(invoke.timeout, `${invokePath}.timeout`);
|
|
727
|
+
assertTransition(invoke.onDone, `${invokePath}.onDone`);
|
|
728
|
+
assertTransition(invoke.onError, `${invokePath}.onError`);
|
|
729
|
+
assertTransition(invoke.onSnapshot, `${invokePath}.onSnapshot`);
|
|
730
|
+
assertTransition(invoke.onTimeout, `${invokePath}.onTimeout`);
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
if (node.on) {
|
|
734
|
+
for (const descriptor of Object.keys(node.on)) {
|
|
735
|
+
assertTransition(node.on[descriptor], `${path}.on.${descriptor}`);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
if (node.after) {
|
|
739
|
+
for (const delay of Object.keys(node.after)) {
|
|
740
|
+
if (Number.isNaN(Number(delay)) && parseDelayToMilliseconds(delay) === undefined && !json.delays?.[delay] && !resolvedSources.delays[delay]) {
|
|
741
|
+
throw new Error(`Missing delay source "${delay}"`);
|
|
742
|
+
}
|
|
743
|
+
assertTransition(node.after[delay], `${path}.after.${delay}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
assertTransition(node.always, `${path}.always`);
|
|
747
|
+
assertTransition(node.onError, `${path}.onError`);
|
|
748
|
+
assertTransition(node.onTimeout, `${path}.onTimeout`);
|
|
749
|
+
if (node.states) {
|
|
750
|
+
for (const key of Object.keys(node.states)) {
|
|
751
|
+
assertStateNode(node.states[key], `${path}.states.${key}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
if (json.actions) {
|
|
756
|
+
for (const key of Object.keys(json.actions)) {
|
|
757
|
+
const actions = Array.isArray(json.actions[key]) ? json.actions[key] : [json.actions[key]];
|
|
758
|
+
actions.forEach((action, index) => assertAction(action, `$.actions.${key}${actions.length > 1 ? `[${index}]` : ''}`, [key]));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
if (json.guards) {
|
|
762
|
+
for (const key of Object.keys(json.guards)) {
|
|
763
|
+
assertCondition(json.guards[key].when, `$.guards.${key}.when`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
if (json.delays) {
|
|
767
|
+
for (const key of Object.keys(json.delays)) {
|
|
768
|
+
const delay = json.delays[key];
|
|
769
|
+
assertResolvable(delay && typeof delay === 'object' && 'duration' in delay ? delay.duration : delay, `$.delays.${key}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
assertStateNode(json, '$');
|
|
773
|
+
}
|
|
774
|
+
let _scxmlSessionId = 'session_scxml';
|
|
172
775
|
let _scxmlMachineName = '';
|
|
173
776
|
|
|
174
777
|
/** Evaluates an SCXML expression with context variables available via `with`. */
|
|
@@ -184,16 +787,16 @@ with (context) {
|
|
|
184
787
|
if (!self) return false;
|
|
185
788
|
try {
|
|
186
789
|
const snap = self.getSnapshot?.();
|
|
187
|
-
if (snap?.
|
|
790
|
+
if (snap?.nodes) {
|
|
188
791
|
const sanitized = stateId.replace(/\./g, '$');
|
|
189
|
-
return snap.
|
|
792
|
+
return snap.nodes.some(node => node.id === sanitized || node.id.endsWith('.' + sanitized));
|
|
190
793
|
}
|
|
191
794
|
} catch {
|
|
192
795
|
// ignore
|
|
193
796
|
}
|
|
194
797
|
return false;
|
|
195
798
|
};
|
|
196
|
-
const SCXML_ORIGIN =
|
|
799
|
+
const SCXML_ORIGIN = `#_scxml_${_scxmlSessionId}`;
|
|
197
800
|
const SCXML_ORIGIN_TYPE = 'http://www.w3.org/TR/scxml/#SCXMLEventProcessor';
|
|
198
801
|
const isExternal = event && event._scxmlExternal;
|
|
199
802
|
const isInitEvent = event && event.type === '@xstate.init';
|
|
@@ -205,7 +808,7 @@ with (context) {
|
|
|
205
808
|
const isDoneEvent = event && typeof event.type === 'string' && event.type.startsWith('xstate.done.');
|
|
206
809
|
const explicitType = event && event._scxmlEventType;
|
|
207
810
|
const scxmlEvent = event && !isInitEvent ? {
|
|
208
|
-
name: event.type,
|
|
811
|
+
name: event._scxmlEventName ?? toScxmlEventName(event.type),
|
|
209
812
|
type: explicitType || (isExternal ? 'external' : 'internal'),
|
|
210
813
|
sendid: event._scxmlSendId,
|
|
211
814
|
origin: isExternal ? SCXML_ORIGIN : undefined,
|
|
@@ -240,10 +843,65 @@ return updates;
|
|
|
240
843
|
const fn = new Function('context', 'updates', fnBody);
|
|
241
844
|
return fn(context, updates);
|
|
242
845
|
}
|
|
243
|
-
function createMachineFromConfig(json,
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const
|
|
846
|
+
function createMachineFromConfig(json, sources = {}) {
|
|
847
|
+
const resolvedSources = mergeSources(json, sources);
|
|
848
|
+
const expressionResolver = createExpressionResolver(json['@exprLang'], resolvedSources);
|
|
849
|
+
const {
|
|
850
|
+
evaluateResolvable,
|
|
851
|
+
resolveValue,
|
|
852
|
+
makeScope,
|
|
853
|
+
getDurationConfig
|
|
854
|
+
} = expressionResolver;
|
|
855
|
+
function resolveCondition(condition, slot, path) {
|
|
856
|
+
if (!condition) {
|
|
857
|
+
return undefined;
|
|
858
|
+
}
|
|
859
|
+
if (isResolvable(condition)) {
|
|
860
|
+
return args => !!evaluateResolvable(condition, slot, makeScope(args), path);
|
|
861
|
+
}
|
|
862
|
+
return args => {
|
|
863
|
+
const params = resolveValue(condition.params, slot, makeScope(args), `${path}.params`);
|
|
864
|
+
const declarativeGuard = json.guards?.[condition.type];
|
|
865
|
+
if (declarativeGuard) {
|
|
866
|
+
const guard = resolveCondition(declarativeGuard.when, 'guard', `$.guards.${condition.type}.when`);
|
|
867
|
+
return !!guard?.({
|
|
868
|
+
...args,
|
|
869
|
+
params
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
const guardImpl = args.guards?.[condition.type];
|
|
873
|
+
if (!guardImpl) {
|
|
874
|
+
throw new Error(getMissingGuardMessage(condition.type, args.guards ?? {}));
|
|
875
|
+
}
|
|
876
|
+
return guardImpl(args, params);
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function makeChoiceConfig(choice, path) {
|
|
880
|
+
if (!choice) {
|
|
881
|
+
return undefined;
|
|
882
|
+
}
|
|
883
|
+
if (isResolvable(choice)) {
|
|
884
|
+
return args => evaluateResolvable(choice, 'choice', makeScope(args), path);
|
|
885
|
+
}
|
|
886
|
+
validateChoiceConfig(choice, path);
|
|
887
|
+
return args => {
|
|
888
|
+
for (let index = 0; index < choice.length; index++) {
|
|
889
|
+
const branch = choice[index];
|
|
890
|
+
const guard = resolveCondition(branch.when, 'choice', `${path}[${index}].when`);
|
|
891
|
+
if (!guard || guard(args)) {
|
|
892
|
+
return {
|
|
893
|
+
target: branch.target,
|
|
894
|
+
context: branch.context ? resolveValue(branch.context, 'transitionContext', makeScope(args), `${path}[${index}].context`) : undefined,
|
|
895
|
+
input: branch.input !== undefined ? resolveValue(branch.input, 'input', makeScope(args), `${path}[${index}].input`) : undefined,
|
|
896
|
+
description: branch.description,
|
|
897
|
+
reenter: branch.reenter,
|
|
898
|
+
meta: branch.meta
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
throw new Error(`Choice state at ${path} did not match any branch.`);
|
|
903
|
+
};
|
|
904
|
+
}
|
|
247
905
|
|
|
248
906
|
// Pending transition actions: set by .to functions, consumed by entry functions.
|
|
249
907
|
// This bridges SCXML's exit→transition→entry action ordering with XState's
|
|
@@ -266,22 +924,13 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
266
924
|
function getMissingGuardMessage(type, guards) {
|
|
267
925
|
return `Guard '${type}' is not implemented in machine '${json.id ?? '(machine)'}'. Available guards: ${Object.keys(guards).map(key => `'${key}'`).join(', ') || '(none)'}.`;
|
|
268
926
|
}
|
|
269
|
-
function
|
|
270
|
-
if (!guard) {
|
|
271
|
-
return undefined;
|
|
272
|
-
}
|
|
273
|
-
return args => {
|
|
274
|
-
const guardImpl = args.guards?.[guard.type];
|
|
275
|
-
if (!guardImpl) {
|
|
276
|
-
throw new Error(getMissingGuardMessage(guard.type, args.guards ?? {}));
|
|
277
|
-
}
|
|
278
|
-
return guardImpl(args, guard.params);
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
function resolveRouteConfig(route) {
|
|
927
|
+
function resolveRouteConfig(route, path) {
|
|
282
928
|
if (!route || typeof route === 'function') {
|
|
283
929
|
return route;
|
|
284
930
|
}
|
|
931
|
+
if (isResolvable(route)) {
|
|
932
|
+
return args => evaluateResolvable(route, 'transition', makeScope(args), path);
|
|
933
|
+
}
|
|
285
934
|
const {
|
|
286
935
|
guard,
|
|
287
936
|
...routeConfig
|
|
@@ -289,13 +938,13 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
289
938
|
if (!guard) {
|
|
290
939
|
return routeConfig;
|
|
291
940
|
}
|
|
292
|
-
const resolvedGuard =
|
|
941
|
+
const resolvedGuard = resolveCondition({
|
|
293
942
|
type: guard
|
|
294
|
-
});
|
|
943
|
+
}, 'guard', path);
|
|
295
944
|
return args => resolvedGuard(args) ? routeConfig : undefined;
|
|
296
945
|
}
|
|
297
946
|
function iterNode(node, nodeKey) {
|
|
298
|
-
const originalEntryActions = node.entry;
|
|
947
|
+
const originalEntryActions = toActionArray(node.entry);
|
|
299
948
|
const stateId = node.id || nodeKey;
|
|
300
949
|
|
|
301
950
|
// Wrap entry to first execute any pending transition actions, then normal entry.
|
|
@@ -309,8 +958,10 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
309
958
|
while (pendingPlatformErrors.length) {
|
|
310
959
|
const err = pendingPlatformErrors.shift();
|
|
311
960
|
enq.raise({
|
|
312
|
-
type: 'error.execution',
|
|
961
|
+
type: 'xstate.error.execution',
|
|
962
|
+
error: err,
|
|
313
963
|
_scxmlEventType: 'platform',
|
|
964
|
+
_scxmlEventName: 'error.execution',
|
|
314
965
|
_scxmlEventData: err
|
|
315
966
|
});
|
|
316
967
|
}
|
|
@@ -358,7 +1009,7 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
358
1009
|
}
|
|
359
1010
|
|
|
360
1011
|
// Execute normal entry actions
|
|
361
|
-
if (originalEntryActions
|
|
1012
|
+
if (originalEntryActions.length) {
|
|
362
1013
|
const mergedX = context ? {
|
|
363
1014
|
...x,
|
|
364
1015
|
context
|
|
@@ -381,7 +1032,7 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
381
1032
|
description: node.description,
|
|
382
1033
|
tags: node.tags,
|
|
383
1034
|
input: node.input,
|
|
384
|
-
timeout: node.timeout,
|
|
1035
|
+
timeout: getDurationConfig(node.timeout, `$.states.${nodeKey}.timeout`),
|
|
385
1036
|
states: node.states ? Object.entries(node.states).reduce((acc, [key, value]) => {
|
|
386
1037
|
acc[key] = iterNode(value, key);
|
|
387
1038
|
return acc;
|
|
@@ -391,18 +1042,27 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
391
1042
|
return acc;
|
|
392
1043
|
}, {}) : undefined,
|
|
393
1044
|
always: node.always ? getTransitionConfig(node.always) : undefined,
|
|
394
|
-
|
|
395
|
-
|
|
1045
|
+
onError: node.onError ? getTransitionConfig(node.onError) : undefined,
|
|
1046
|
+
choice: makeChoiceConfig(node.choice, `$.states.${nodeKey}.choice`),
|
|
1047
|
+
route: resolveRouteConfig(node.route, `$.states.${nodeKey}.route`),
|
|
396
1048
|
after: node.after ? Object.entries(node.after).reduce((acc, [key, value]) => {
|
|
397
1049
|
acc[key] = getTransitionConfig(value);
|
|
398
1050
|
return acc;
|
|
399
1051
|
}, {}) : undefined,
|
|
400
1052
|
onTimeout: node.onTimeout ? getTransitionConfig(node.onTimeout) : undefined,
|
|
401
|
-
entry: entryFn,
|
|
402
|
-
exit: node.exit ? iterActions(node.exit) : undefined,
|
|
1053
|
+
entry: node.type === 'choice' ? undefined : entryFn,
|
|
1054
|
+
exit: node.exit ? iterActions(toActionArray(node.exit)) : undefined,
|
|
403
1055
|
invoke: node.invoke ? iterInvokeConfigs(node.invoke) : undefined,
|
|
404
1056
|
meta: node.meta,
|
|
405
|
-
output: node._scxmlDonedata ? makeDonedataOutput(node._scxmlDonedata) : node.output
|
|
1057
|
+
output: node._scxmlDonedata ? makeDonedataOutput(node._scxmlDonedata) : isResolvable(node.output) ? ({
|
|
1058
|
+
context,
|
|
1059
|
+
event,
|
|
1060
|
+
self
|
|
1061
|
+
}) => evaluateResolvable(node.output, 'output', {
|
|
1062
|
+
context,
|
|
1063
|
+
event,
|
|
1064
|
+
self
|
|
1065
|
+
}, `$.states.${nodeKey}.output`) : node.output
|
|
406
1066
|
};
|
|
407
1067
|
return nodeConfig;
|
|
408
1068
|
}
|
|
@@ -454,24 +1114,19 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
454
1114
|
// Create child machine from nested SCXML JSON
|
|
455
1115
|
let src;
|
|
456
1116
|
if (extInv._nestedMachineJSON) {
|
|
457
|
-
src = createMachineFromConfig(extInv._nestedMachineJSON,
|
|
458
|
-
} else if (isUnserializableMarker(inv.src)) {
|
|
459
|
-
src = inv.src.id ? resolvedImplementations.actors[inv.src.id] : undefined;
|
|
460
|
-
if (!src) {
|
|
461
|
-
throw new Error(`Missing actors.${inv.src.id ?? ''}`);
|
|
462
|
-
}
|
|
1117
|
+
src = createMachineFromConfig(extInv._nestedMachineJSON, resolvedSources);
|
|
463
1118
|
} else {
|
|
464
|
-
src = inv.src;
|
|
1119
|
+
src = resolvedSources.actors[inv.src] ?? inv.src;
|
|
465
1120
|
}
|
|
466
1121
|
return {
|
|
467
1122
|
src,
|
|
468
1123
|
id: inv.id,
|
|
469
|
-
|
|
470
|
-
input: inv.input,
|
|
1124
|
+
registryKey: inv.registryKey,
|
|
1125
|
+
input: inv.input !== undefined ? args => resolveValue(inv.input, 'input', makeScope(args), '$.invoke.input') : undefined,
|
|
471
1126
|
onDone: inv.onDone ? getTransitionConfig(inv.onDone) : undefined,
|
|
472
1127
|
onError: inv.onError ? getTransitionConfig(inv.onError) : undefined,
|
|
473
1128
|
onSnapshot: inv.onSnapshot ? getTransitionConfig(inv.onSnapshot) : undefined,
|
|
474
|
-
timeout: inv.timeout,
|
|
1129
|
+
timeout: getDurationConfig(inv.timeout, '$.invoke.timeout'),
|
|
475
1130
|
onTimeout: inv.onTimeout ? getTransitionConfig(inv.onTimeout) : undefined
|
|
476
1131
|
};
|
|
477
1132
|
});
|
|
@@ -485,8 +1140,16 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
485
1140
|
function raiseErrorExecution(state, tagname, err) {
|
|
486
1141
|
const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
|
|
487
1142
|
state.enq.raise({
|
|
488
|
-
type: 'error.execution',
|
|
1143
|
+
type: 'xstate.error.execution',
|
|
1144
|
+
error: {
|
|
1145
|
+
tagname,
|
|
1146
|
+
message,
|
|
1147
|
+
line: NaN,
|
|
1148
|
+
column: NaN,
|
|
1149
|
+
reason: message
|
|
1150
|
+
},
|
|
489
1151
|
_scxmlEventType: 'platform',
|
|
1152
|
+
_scxmlEventName: 'error.execution',
|
|
490
1153
|
_scxmlEventData: {
|
|
491
1154
|
tagname,
|
|
492
1155
|
message,
|
|
@@ -497,8 +1160,31 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
497
1160
|
});
|
|
498
1161
|
state.errored = true;
|
|
499
1162
|
}
|
|
1163
|
+
function executeActionDefinition(action, x, enq, params, parentState, stack) {
|
|
1164
|
+
const definition = json.actions?.[action.type];
|
|
1165
|
+
if (!definition) {
|
|
1166
|
+
enq(x.actions[action.type], params);
|
|
1167
|
+
return {
|
|
1168
|
+
context: undefined,
|
|
1169
|
+
errored: parentState.errored
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
if (stack.includes(action.type)) {
|
|
1173
|
+
raiseErrorExecution(parentState, 'action', new Error(`Circular action reference: ${stack.concat(action.type).join(' -> ')}`));
|
|
1174
|
+
return {
|
|
1175
|
+
context: undefined,
|
|
1176
|
+
errored: true
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
const definitions = Array.isArray(definition) ? definition : [definition];
|
|
1180
|
+
return executeActions(definitions, {
|
|
1181
|
+
...x,
|
|
1182
|
+
params
|
|
1183
|
+
}, enq, parentState, stack.concat(action.type));
|
|
1184
|
+
}
|
|
1185
|
+
|
|
500
1186
|
/** Execute an array of SCXML action JSON descriptors with context and enqueue. */
|
|
501
|
-
function executeActions(actions, x, enq, parentState) {
|
|
1187
|
+
function executeActions(actions, x, enq, parentState, stack = []) {
|
|
502
1188
|
const state = parentState ?? {
|
|
503
1189
|
enq,
|
|
504
1190
|
errored: false
|
|
@@ -506,22 +1192,34 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
506
1192
|
let context;
|
|
507
1193
|
for (const action of actions) {
|
|
508
1194
|
if (state.errored) break;
|
|
509
|
-
if (
|
|
1195
|
+
if (isResolvable(action)) {
|
|
1196
|
+
const result = evaluateResolvable(action, 'action', makeScope(x, {
|
|
1197
|
+
params: x.params,
|
|
1198
|
+
enq
|
|
1199
|
+
}), '$.actions');
|
|
1200
|
+
if (result && typeof result === 'object' && 'context' in result && result.context) {
|
|
1201
|
+
context ??= {};
|
|
1202
|
+
Object.assign(context, result.context);
|
|
1203
|
+
}
|
|
1204
|
+
} else if (isBuiltInActionJSON(action)) {
|
|
510
1205
|
switch (action.type) {
|
|
511
1206
|
case '@xstate.raise':
|
|
512
1207
|
{
|
|
513
1208
|
// Tag as external if it has a delay (from <send>, not <raise>).
|
|
514
1209
|
// Attach _scxmlSendId so _event.sendid can be read in expressions.
|
|
515
1210
|
const isExternal = action.delay !== undefined;
|
|
1211
|
+
const resolvedEvent = resolveValue(action.event, 'actionParams', makeScope(x, {
|
|
1212
|
+
params: x.params
|
|
1213
|
+
}), '$.actions.event');
|
|
516
1214
|
const event = isExternal || action.id !== undefined ? {
|
|
517
|
-
...
|
|
1215
|
+
...resolvedEvent,
|
|
518
1216
|
...(isExternal ? {
|
|
519
1217
|
_scxmlExternal: true
|
|
520
1218
|
} : {}),
|
|
521
1219
|
...(action.id !== undefined ? {
|
|
522
1220
|
_scxmlSendId: action.id
|
|
523
1221
|
} : {})
|
|
524
|
-
} :
|
|
1222
|
+
} : resolvedEvent;
|
|
525
1223
|
enq.raise(event, {
|
|
526
1224
|
id: action.id,
|
|
527
1225
|
delay: action.delay
|
|
@@ -535,11 +1233,15 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
535
1233
|
enq.log(...action.args);
|
|
536
1234
|
break;
|
|
537
1235
|
case '@xstate.emit':
|
|
538
|
-
enq.emit(action.event
|
|
1236
|
+
enq.emit(resolveValue(action.event, 'actionParams', makeScope(x, {
|
|
1237
|
+
params: x.params
|
|
1238
|
+
}), '$.actions.event'));
|
|
539
1239
|
break;
|
|
540
1240
|
case '@xstate.assign':
|
|
541
1241
|
context ??= {};
|
|
542
|
-
Object.assign(context, action.context
|
|
1242
|
+
Object.assign(context, resolveValue(action.context, 'transitionContext', makeScope(x, {
|
|
1243
|
+
params: x.params
|
|
1244
|
+
}), '$.actions.context'));
|
|
543
1245
|
break;
|
|
544
1246
|
default:
|
|
545
1247
|
throw new Error(`Unknown built-in action: ${action.type}`);
|
|
@@ -578,8 +1280,10 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
578
1280
|
let delay;
|
|
579
1281
|
try {
|
|
580
1282
|
eventType = scxmlAction.eventexpr ? evaluateExpr(mergedContext, scxmlAction.eventexpr, x.event, x.self) : scxmlAction.event || 'unknown';
|
|
1283
|
+
eventType = normalizeScxmlEventType(eventType);
|
|
581
1284
|
eventData = {
|
|
582
|
-
type: eventType
|
|
1285
|
+
type: eventType,
|
|
1286
|
+
_scxmlEventName: toScxmlEventName(eventType)
|
|
583
1287
|
};
|
|
584
1288
|
if (scxmlAction.params) {
|
|
585
1289
|
for (const param of scxmlAction.params) {
|
|
@@ -619,25 +1323,50 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
619
1323
|
const childRef = x.children?.[childId];
|
|
620
1324
|
if (childRef) {
|
|
621
1325
|
enq.sendTo(childRef, eventData);
|
|
1326
|
+
} else if (childId.startsWith('scxml_') && childId !== `scxml_${_scxmlSessionId}`) {
|
|
1327
|
+
const message = `Unable to dispatch event to target: ${target}`;
|
|
1328
|
+
enq.raise({
|
|
1329
|
+
type: 'xstate.error.communication',
|
|
1330
|
+
error: {
|
|
1331
|
+
tagname: 'send',
|
|
1332
|
+
message,
|
|
1333
|
+
line: NaN,
|
|
1334
|
+
column: NaN,
|
|
1335
|
+
reason: message
|
|
1336
|
+
},
|
|
1337
|
+
_scxmlEventType: 'platform',
|
|
1338
|
+
_scxmlEventName: 'error.communication',
|
|
1339
|
+
_scxmlEventData: {
|
|
1340
|
+
tagname: 'send',
|
|
1341
|
+
message,
|
|
1342
|
+
line: NaN,
|
|
1343
|
+
column: NaN,
|
|
1344
|
+
reason: message
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
622
1347
|
} else {
|
|
623
|
-
//
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
enq.raise(eventData, {
|
|
1348
|
+
// This SCXML session id uses the external event queue.
|
|
1349
|
+
eventData._scxmlExternal = true;
|
|
1350
|
+
enq.sendTo(x.self, eventData, {
|
|
628
1351
|
id: scxmlAction.id,
|
|
629
1352
|
delay
|
|
630
1353
|
});
|
|
631
1354
|
}
|
|
632
1355
|
} else {
|
|
633
|
-
//
|
|
634
|
-
|
|
1356
|
+
// #_internal uses the internal queue; self-send/no target use the
|
|
1357
|
+
// external event queue.
|
|
1358
|
+
if (isInternalTarget) {
|
|
1359
|
+
enq.raise(eventData, {
|
|
1360
|
+
id: scxmlAction.id,
|
|
1361
|
+
delay
|
|
1362
|
+
});
|
|
1363
|
+
} else {
|
|
635
1364
|
eventData._scxmlExternal = true;
|
|
1365
|
+
enq.sendTo(x.self, eventData, {
|
|
1366
|
+
id: scxmlAction.id,
|
|
1367
|
+
delay
|
|
1368
|
+
});
|
|
636
1369
|
}
|
|
637
|
-
enq.raise(eventData, {
|
|
638
|
-
id: scxmlAction.id,
|
|
639
|
-
delay
|
|
640
|
-
});
|
|
641
1370
|
}
|
|
642
1371
|
} else if (action.type === 'scxml.cancel') {
|
|
643
1372
|
const scxmlAction = action;
|
|
@@ -767,7 +1496,14 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
767
1496
|
}
|
|
768
1497
|
}
|
|
769
1498
|
} else {
|
|
770
|
-
|
|
1499
|
+
const params = resolveValue(action.params, 'actionParams', makeScope(x, {
|
|
1500
|
+
params: x.params
|
|
1501
|
+
}), '$.actions.params');
|
|
1502
|
+
const result = executeActionDefinition(action, x, enq, params, state, stack);
|
|
1503
|
+
if (result.context) {
|
|
1504
|
+
context ??= {};
|
|
1505
|
+
Object.assign(context, result.context);
|
|
1506
|
+
}
|
|
771
1507
|
}
|
|
772
1508
|
}
|
|
773
1509
|
return {
|
|
@@ -788,31 +1524,67 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
788
1524
|
// a separate XState transition with its own guard and optional .to.
|
|
789
1525
|
// This ensures guards are evaluated by XState's evaluateCandidate (once,
|
|
790
1526
|
// with pre-exit context) and NOT re-evaluated in computeEntrySet.
|
|
791
|
-
return transitions.map(t => {
|
|
1527
|
+
return transitions.map((t, index) => {
|
|
1528
|
+
if (isResolvable(t)) {
|
|
1529
|
+
return (x, enq) => evaluateResolvable(t, 'transition', makeScope(x, {
|
|
1530
|
+
enq
|
|
1531
|
+
}), `$.transition${transitions.length > 1 ? `[${index}]` : ''}`);
|
|
1532
|
+
}
|
|
792
1533
|
const target = Array.isArray(t.target) ? t.target[0] : t.target;
|
|
793
1534
|
const targetConfig = t.target;
|
|
794
|
-
const guard =
|
|
1535
|
+
const guard = resolveCondition(t.guard, 'guard', '$.transition.guard');
|
|
1536
|
+
const resolveTransitionContext = x => t.context ? resolveValue(t.context, 'transitionContext', makeScope(x), '$.transition.context') : undefined;
|
|
1537
|
+
const resolveTransitionInput = x => t.input !== undefined ? resolveValue(t.input, 'input', makeScope(x), '$.transition.input') : undefined;
|
|
795
1538
|
|
|
796
1539
|
// No guard and no actions: simple static config
|
|
797
1540
|
if (!guard && !t.actions?.length) {
|
|
1541
|
+
if (t.context) {
|
|
1542
|
+
return {
|
|
1543
|
+
matches: t.matches,
|
|
1544
|
+
to: x => ({
|
|
1545
|
+
target: targetConfig,
|
|
1546
|
+
context: resolveTransitionContext(x),
|
|
1547
|
+
reenter: t.reenter
|
|
1548
|
+
}),
|
|
1549
|
+
description: t.description,
|
|
1550
|
+
meta: t.meta,
|
|
1551
|
+
input: t.input !== undefined ? resolveTransitionInput : undefined
|
|
1552
|
+
};
|
|
1553
|
+
}
|
|
798
1554
|
return {
|
|
1555
|
+
matches: t.matches,
|
|
799
1556
|
target: targetConfig,
|
|
800
1557
|
description: t.description,
|
|
801
1558
|
reenter: t.reenter,
|
|
802
1559
|
meta: t.meta,
|
|
803
|
-
input: t.input
|
|
1560
|
+
input: t.input !== undefined ? resolveTransitionInput : undefined
|
|
804
1561
|
};
|
|
805
1562
|
}
|
|
806
1563
|
|
|
807
1564
|
// Guard but no actions: static config with guard
|
|
808
1565
|
if (guard && !t.actions?.length) {
|
|
1566
|
+
if (t.context) {
|
|
1567
|
+
return {
|
|
1568
|
+
matches: t.matches,
|
|
1569
|
+
guard,
|
|
1570
|
+
to: x => ({
|
|
1571
|
+
target: targetConfig,
|
|
1572
|
+
context: resolveTransitionContext(x),
|
|
1573
|
+
reenter: t.reenter
|
|
1574
|
+
}),
|
|
1575
|
+
description: t.description,
|
|
1576
|
+
meta: t.meta,
|
|
1577
|
+
input: t.input !== undefined ? resolveTransitionInput : undefined
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
809
1580
|
return {
|
|
1581
|
+
matches: t.matches,
|
|
810
1582
|
target: targetConfig,
|
|
811
1583
|
guard,
|
|
812
1584
|
description: t.description,
|
|
813
1585
|
reenter: t.reenter,
|
|
814
1586
|
meta: t.meta,
|
|
815
|
-
input: t.input
|
|
1587
|
+
input: t.input !== undefined ? resolveTransitionInput : undefined
|
|
816
1588
|
};
|
|
817
1589
|
}
|
|
818
1590
|
|
|
@@ -821,12 +1593,14 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
821
1593
|
// For parallel (entries exist), first entry re-executes all in document order.
|
|
822
1594
|
if (!target) {
|
|
823
1595
|
return {
|
|
1596
|
+
matches: t.matches,
|
|
824
1597
|
guard,
|
|
825
1598
|
description: t.description,
|
|
826
1599
|
reenter: t.reenter,
|
|
827
1600
|
meta: t.meta,
|
|
828
|
-
input: t.input,
|
|
1601
|
+
input: t.input !== undefined ? resolveTransitionInput : undefined,
|
|
829
1602
|
to: (x, enq) => {
|
|
1603
|
+
const context = resolveTransitionContext(x);
|
|
830
1604
|
if (t.actions?.length) {
|
|
831
1605
|
// Track for parallel re-execution (dedup by reference)
|
|
832
1606
|
if (!allTransitionActions.includes(t.actions)) {
|
|
@@ -838,11 +1612,16 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
838
1612
|
const result = executeActions(t.actions, x, enq);
|
|
839
1613
|
if (result.context) {
|
|
840
1614
|
return {
|
|
841
|
-
context:
|
|
1615
|
+
context: {
|
|
1616
|
+
...context,
|
|
1617
|
+
...result.context
|
|
1618
|
+
}
|
|
842
1619
|
};
|
|
843
1620
|
}
|
|
844
1621
|
}
|
|
845
|
-
return {
|
|
1622
|
+
return context ? {
|
|
1623
|
+
context
|
|
1624
|
+
} : {};
|
|
846
1625
|
}
|
|
847
1626
|
};
|
|
848
1627
|
}
|
|
@@ -852,12 +1631,14 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
852
1631
|
// already validated it. The .to just stores actions for entry to execute.
|
|
853
1632
|
// Map by target state ID so parallel transitions each get their own actions.
|
|
854
1633
|
return {
|
|
1634
|
+
matches: t.matches,
|
|
855
1635
|
guard,
|
|
856
1636
|
description: t.description,
|
|
857
1637
|
reenter: t.reenter,
|
|
858
1638
|
meta: t.meta,
|
|
859
|
-
input: t.input,
|
|
1639
|
+
input: t.input !== undefined ? resolveTransitionInput : undefined,
|
|
860
1640
|
to: (_x, _enq) => {
|
|
1641
|
+
const context = resolveTransitionContext(_x);
|
|
861
1642
|
if (t.actions?.length) {
|
|
862
1643
|
const targetId = target.replace(/^#/, '');
|
|
863
1644
|
pendingTransitionActionsMap[targetId] = t.actions;
|
|
@@ -868,25 +1649,39 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
868
1649
|
}
|
|
869
1650
|
return {
|
|
870
1651
|
target: targetConfig,
|
|
1652
|
+
context,
|
|
871
1653
|
reenter: t.reenter
|
|
872
1654
|
};
|
|
873
1655
|
}
|
|
874
1656
|
};
|
|
875
1657
|
});
|
|
876
1658
|
}
|
|
1659
|
+
if (json.delays) {
|
|
1660
|
+
for (const key of Object.keys(json.delays)) {
|
|
1661
|
+
const delay = json.delays[key];
|
|
1662
|
+
if (typeof delay === 'number') {
|
|
1663
|
+
resolvedSources.delays[key] = delay;
|
|
1664
|
+
} else if (typeof delay === 'string') {
|
|
1665
|
+
resolvedSources.delays[key] = delayToMs(delay);
|
|
1666
|
+
} else if (delay && typeof delay === 'object') {
|
|
1667
|
+
resolvedSources.delays[key] = args => delayToMs(resolveValue(delay.duration, 'delay', makeScope(args), `$.delays.${key}.duration`));
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
assertMachineJSON(json, resolvedSources, expressionResolver);
|
|
877
1672
|
const rootNodeConfig = iterNode(json);
|
|
878
1673
|
const contextConfig = json.context ? {
|
|
879
|
-
context: json.context
|
|
1674
|
+
context: args => resolveValue(json.context, 'context', args, '$.context')
|
|
880
1675
|
} : {};
|
|
881
1676
|
_scxmlMachineName = json.id || '';
|
|
882
|
-
_scxmlSessionId = '
|
|
1677
|
+
_scxmlSessionId = 'session_scxml';
|
|
883
1678
|
const machine = createMachine({
|
|
884
1679
|
...rootNodeConfig,
|
|
885
1680
|
...contextConfig,
|
|
886
1681
|
version: json.version
|
|
887
1682
|
});
|
|
888
1683
|
|
|
889
|
-
// Register SCXML guard
|
|
1684
|
+
// Register SCXML guard sources
|
|
890
1685
|
const providedGuards = {
|
|
891
1686
|
'scxml.cond': ({
|
|
892
1687
|
context,
|
|
@@ -917,8 +1712,8 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
917
1712
|
if (!stateId) return false;
|
|
918
1713
|
const normalizedId = stateId.replace(/^#/, '');
|
|
919
1714
|
const snapshot = args._snapshot;
|
|
920
|
-
if (snapshot?.
|
|
921
|
-
return snapshot.
|
|
1715
|
+
if (snapshot?.nodes) {
|
|
1716
|
+
return snapshot.nodes.some(node => node.id === normalizedId);
|
|
922
1717
|
}
|
|
923
1718
|
return false;
|
|
924
1719
|
},
|
|
@@ -932,13 +1727,13 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
932
1727
|
}
|
|
933
1728
|
};
|
|
934
1729
|
const provided = machine.provide({
|
|
935
|
-
actions:
|
|
936
|
-
actors:
|
|
1730
|
+
actions: resolvedSources.actions,
|
|
1731
|
+
actors: resolvedSources.actors,
|
|
937
1732
|
guards: {
|
|
938
1733
|
...providedGuards,
|
|
939
|
-
...
|
|
1734
|
+
...resolvedSources.guards
|
|
940
1735
|
},
|
|
941
|
-
delays:
|
|
1736
|
+
delays: resolvedSources.delays
|
|
942
1737
|
});
|
|
943
1738
|
|
|
944
1739
|
// Keep the original JSON so `serializeMachine(machine)`
|
|
@@ -947,7 +1742,7 @@ function createMachineFromConfig(json, implementations = {}) {
|
|
|
947
1742
|
return provided;
|
|
948
1743
|
}
|
|
949
1744
|
function isBuiltInActionJSON(action) {
|
|
950
|
-
return action.type.startsWith('@xstate.');
|
|
1745
|
+
return !isResolvable(action) && action.type.startsWith('@xstate.');
|
|
951
1746
|
}
|
|
952
1747
|
|
|
953
1748
|
/**
|
|
@@ -955,24 +1750,17 @@ function isBuiltInActionJSON(action) {
|
|
|
955
1750
|
*
|
|
956
1751
|
* `machineConfigToJSON` converts a machine config into a JSON-safe structure
|
|
957
1752
|
* (the `MachineJSON` shape accepted by `createMachineFromConfig`). The boundary
|
|
958
|
-
* between serializable structure and runtime
|
|
959
|
-
*
|
|
960
|
-
*
|
|
961
|
-
* "$unserializable": <kind> }` marker rather than silently dropped.
|
|
962
|
-
*
|
|
963
|
-
* A machine definition is fully portable iff its JSON contains no
|
|
964
|
-
* `$unserializable` markers; reviving a definition that has markers requires
|
|
965
|
-
* re-providing those implementations (e.g. via `machine.provide(...)` or the
|
|
966
|
-
* `actions`/`guards`/`actors` maps on `createMachineFromConfig` revival).
|
|
1753
|
+
* between serializable structure and runtime sources is explicit: functions are
|
|
1754
|
+
* represented as `@code` source objects, while actor logic, runtime schemas,
|
|
1755
|
+
* and other non-data runtime values are omitted.
|
|
967
1756
|
*/
|
|
968
1757
|
|
|
969
1758
|
/**
|
|
970
1759
|
* Returns the JSON-serializable definition of a machine.
|
|
971
1760
|
*
|
|
972
|
-
* Inline functions are represented as `{ "@
|
|
973
|
-
*
|
|
974
|
-
*
|
|
975
|
-
* JSON config (lossless round-trip):
|
|
1761
|
+
* Inline functions are represented as `{ "@code": string, "@lang": "ts" }`. A
|
|
1762
|
+
* machine created via `createMachineFromConfig` returns its original JSON
|
|
1763
|
+
* config (lossless round-trip):
|
|
976
1764
|
*
|
|
977
1765
|
* ```ts
|
|
978
1766
|
* import { serializeMachine, createMachineFromConfig } from 'xstate';
|
|
@@ -986,22 +1774,12 @@ function isBuiltInActionJSON(action) {
|
|
|
986
1774
|
function serializeMachine(machine) {
|
|
987
1775
|
return machine._json ?? machineConfigToJSON(machine.config);
|
|
988
1776
|
}
|
|
989
|
-
function marker(kind, id) {
|
|
990
|
-
const m = {
|
|
991
|
-
$unserializable: kind
|
|
992
|
-
};
|
|
993
|
-
if (id) {
|
|
994
|
-
m.id = id;
|
|
995
|
-
}
|
|
996
|
-
return m;
|
|
997
|
-
}
|
|
998
1777
|
|
|
999
1778
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
|
1000
1779
|
function codeExpression(fn) {
|
|
1001
1780
|
return {
|
|
1002
|
-
'@
|
|
1003
|
-
lang: 'ts'
|
|
1004
|
-
expr: fn.toString()
|
|
1781
|
+
'@code': fn.toString(),
|
|
1782
|
+
'@lang': 'ts'
|
|
1005
1783
|
};
|
|
1006
1784
|
}
|
|
1007
1785
|
function isActorLogic(value) {
|
|
@@ -1017,33 +1795,37 @@ function valueToJSON(value) {
|
|
|
1017
1795
|
return codeExpression(value);
|
|
1018
1796
|
}
|
|
1019
1797
|
if (value === null || typeof value !== 'object') {
|
|
1020
|
-
return typeof value === 'bigint' || typeof value === 'symbol' ?
|
|
1798
|
+
return typeof value === 'bigint' || typeof value === 'symbol' ? undefined : value;
|
|
1021
1799
|
}
|
|
1022
1800
|
if (isActorLogic(value)) {
|
|
1023
|
-
return
|
|
1801
|
+
return undefined;
|
|
1024
1802
|
}
|
|
1025
1803
|
if (isRuntimeSchema(value)) {
|
|
1026
|
-
return
|
|
1804
|
+
return undefined;
|
|
1027
1805
|
}
|
|
1028
1806
|
if (Array.isArray(value)) {
|
|
1029
|
-
return value.map(valueToJSON);
|
|
1807
|
+
return value.map(valueToJSON).filter(item => item !== undefined);
|
|
1030
1808
|
}
|
|
1031
1809
|
if (value.constructor !== Object && value.constructor !== undefined) {
|
|
1032
1810
|
// Class instances (actor logic, schemas, dates, ...) are not portable.
|
|
1033
|
-
return
|
|
1811
|
+
return undefined;
|
|
1034
1812
|
}
|
|
1035
1813
|
const result = {};
|
|
1036
1814
|
for (const key of Object.keys(value)) {
|
|
1037
1815
|
const v = value[key];
|
|
1038
1816
|
if (v !== undefined) {
|
|
1039
|
-
|
|
1817
|
+
const jsonValue = valueToJSON(v);
|
|
1818
|
+
if (jsonValue !== undefined) {
|
|
1819
|
+
result[key] = jsonValue;
|
|
1820
|
+
}
|
|
1040
1821
|
}
|
|
1041
1822
|
}
|
|
1042
1823
|
return result;
|
|
1043
1824
|
}
|
|
1044
1825
|
function invokeToJSON(invoke) {
|
|
1045
1826
|
if (Array.isArray(invoke)) {
|
|
1046
|
-
|
|
1827
|
+
const values = invoke.map(invokeToJSON).filter(value => value !== undefined);
|
|
1828
|
+
return values.length ? values : undefined;
|
|
1047
1829
|
}
|
|
1048
1830
|
const def = invoke;
|
|
1049
1831
|
const result = {};
|
|
@@ -1052,32 +1834,28 @@ function invokeToJSON(invoke) {
|
|
|
1052
1834
|
if (value === undefined) {
|
|
1053
1835
|
continue;
|
|
1054
1836
|
}
|
|
1055
|
-
result[key] = key === 'src' && typeof value !== 'string' ?
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1837
|
+
result[key] = key === 'src' && typeof value !== 'string' ? value.id : valueToJSON(value);
|
|
1838
|
+
if (result[key] === undefined) {
|
|
1839
|
+
delete result[key];
|
|
1840
|
+
}
|
|
1059
1841
|
}
|
|
1060
|
-
return result;
|
|
1842
|
+
return result.src === undefined ? undefined : result;
|
|
1061
1843
|
}
|
|
1062
|
-
function
|
|
1844
|
+
function sourcesToJSON(map) {
|
|
1063
1845
|
if (!map) {
|
|
1064
1846
|
return undefined;
|
|
1065
1847
|
}
|
|
1066
|
-
// Keys are preserved — they are the contract a revived machine must
|
|
1067
|
-
// fulfill via provide() — while values become markers (or stay, for
|
|
1068
|
-
// JSON-safe values like numeric delays).
|
|
1069
1848
|
const result = {};
|
|
1070
1849
|
for (const key of Object.keys(map)) {
|
|
1071
1850
|
const value = map[key];
|
|
1072
|
-
if (
|
|
1073
|
-
result[key] =
|
|
1074
|
-
} else if (typeof value === 'function') {
|
|
1075
|
-
result[key] = codeExpression(value);
|
|
1076
|
-
} else if (kind === 'actor' && value && typeof value === 'object') {
|
|
1077
|
-
result[key] = marker('actor', key);
|
|
1851
|
+
if (typeof value === 'function') {
|
|
1852
|
+
result[key] = undefined;
|
|
1078
1853
|
} else {
|
|
1079
1854
|
result[key] = valueToJSON(value);
|
|
1080
1855
|
}
|
|
1856
|
+
if (result[key] === undefined) {
|
|
1857
|
+
delete result[key];
|
|
1858
|
+
}
|
|
1081
1859
|
}
|
|
1082
1860
|
return result;
|
|
1083
1861
|
}
|
|
@@ -1093,7 +1871,10 @@ function stateNodeConfigToJSON(config) {
|
|
|
1093
1871
|
}
|
|
1094
1872
|
}
|
|
1095
1873
|
if (config.invoke !== undefined) {
|
|
1096
|
-
|
|
1874
|
+
const invoke = invokeToJSON(config.invoke);
|
|
1875
|
+
if (invoke !== undefined) {
|
|
1876
|
+
result.invoke = invoke;
|
|
1877
|
+
}
|
|
1097
1878
|
}
|
|
1098
1879
|
if (config.states) {
|
|
1099
1880
|
const states = {};
|
|
@@ -1107,7 +1888,7 @@ function stateNodeConfigToJSON(config) {
|
|
|
1107
1888
|
|
|
1108
1889
|
/**
|
|
1109
1890
|
* Converts a machine config (as passed to `createMachine`) to its JSON-safe
|
|
1110
|
-
* definition.
|
|
1891
|
+
* definition.
|
|
1111
1892
|
*/
|
|
1112
1893
|
function machineConfigToJSON(config) {
|
|
1113
1894
|
const result = stateNodeConfigToJSON(config);
|
|
@@ -1120,16 +1901,24 @@ function machineConfigToJSON(config) {
|
|
|
1120
1901
|
const value = config.schemas[key];
|
|
1121
1902
|
if (value && typeof value === 'object' && !('~standard' in value)) {
|
|
1122
1903
|
// Map-form schemas (events/emitted): preserve event-type keys.
|
|
1123
|
-
schemas[key] =
|
|
1904
|
+
schemas[key] = sourcesToJSON(value);
|
|
1124
1905
|
} else {
|
|
1125
|
-
schemas[key] =
|
|
1906
|
+
schemas[key] = valueToJSON(value);
|
|
1907
|
+
}
|
|
1908
|
+
if (schemas[key] === undefined) {
|
|
1909
|
+
delete schemas[key];
|
|
1126
1910
|
}
|
|
1127
1911
|
}
|
|
1128
1912
|
result.schemas = schemas;
|
|
1129
1913
|
}
|
|
1130
1914
|
for (const key of ['actions', 'guards', 'actors', 'delays']) {
|
|
1131
1915
|
if (config[key]) {
|
|
1132
|
-
|
|
1916
|
+
const value = sourcesToJSON(config[key]);
|
|
1917
|
+
if (value && Object.keys(value).length) {
|
|
1918
|
+
result[key] = value;
|
|
1919
|
+
} else {
|
|
1920
|
+
delete result[key];
|
|
1921
|
+
}
|
|
1133
1922
|
}
|
|
1134
1923
|
}
|
|
1135
1924
|
return result;
|
|
@@ -1164,7 +1953,7 @@ function mapState(snapshot, mapper) {
|
|
|
1164
1953
|
return mapper;
|
|
1165
1954
|
};
|
|
1166
1955
|
const visited = new Set();
|
|
1167
|
-
for (const atomicNode of snapshot.
|
|
1956
|
+
for (const atomicNode of snapshot.nodes.filter(isAtomicStateNode)) {
|
|
1168
1957
|
let current = atomicNode;
|
|
1169
1958
|
while (current && !visited.has(current)) {
|
|
1170
1959
|
visited.add(current);
|
|
@@ -1181,8 +1970,235 @@ function mapState(snapshot, mapper) {
|
|
|
1181
1970
|
return results;
|
|
1182
1971
|
}
|
|
1183
1972
|
|
|
1973
|
+
function isObject(value) {
|
|
1974
|
+
return value !== null && typeof value === 'object';
|
|
1975
|
+
}
|
|
1976
|
+
function isVersionedStateMachine(entry) {
|
|
1977
|
+
return 'transition' in entry && typeof entry.transition === 'function';
|
|
1978
|
+
}
|
|
1979
|
+
function hasSnapshotSchema(entry) {
|
|
1980
|
+
return 'snapshotSchema' in entry && entry.snapshotSchema !== undefined;
|
|
1981
|
+
}
|
|
1982
|
+
function hasEventSchema(entry) {
|
|
1983
|
+
return 'eventSchema' in entry && entry.eventSchema !== undefined;
|
|
1984
|
+
}
|
|
1985
|
+
function getSnapshotSource(raw, defaultId) {
|
|
1986
|
+
if (!isObject(raw)) {
|
|
1987
|
+
return {};
|
|
1988
|
+
}
|
|
1989
|
+
if (isObject(raw.machine)) {
|
|
1990
|
+
return {
|
|
1991
|
+
id: typeof raw.machine.id === 'string' ? raw.machine.id : undefined,
|
|
1992
|
+
version: typeof raw.machine.version === 'string' ? raw.machine.version : undefined
|
|
1993
|
+
};
|
|
1994
|
+
}
|
|
1995
|
+
return {
|
|
1996
|
+
id: typeof raw.version === 'string' ? defaultId : undefined,
|
|
1997
|
+
version: typeof raw.version === 'string' ? raw.version : undefined
|
|
1998
|
+
};
|
|
1999
|
+
}
|
|
2000
|
+
async function validate(schema, value, description) {
|
|
2001
|
+
if (!schema) {
|
|
2002
|
+
return value;
|
|
2003
|
+
}
|
|
2004
|
+
const result = await schema['~standard'].validate(value);
|
|
2005
|
+
if (result.issues) {
|
|
2006
|
+
throw new Error(`Invalid ${description}: ${result.issues[0]?.message}`);
|
|
2007
|
+
}
|
|
2008
|
+
return result.value;
|
|
2009
|
+
}
|
|
2010
|
+
async function finalizeSnapshot(snapshot, target) {
|
|
2011
|
+
return await validate(target.snapshotSchema, {
|
|
2012
|
+
...snapshot,
|
|
2013
|
+
machine: {
|
|
2014
|
+
id: target.id,
|
|
2015
|
+
version: target.version
|
|
2016
|
+
},
|
|
2017
|
+
version: target.version
|
|
2018
|
+
}, `snapshot for machine '${target.id}' version '${target.version}'`);
|
|
2019
|
+
}
|
|
2020
|
+
async function validateEvents(events, source) {
|
|
2021
|
+
return Promise.all(events.map(async (event, index) => {
|
|
2022
|
+
const result = await source.eventSchema['~standard'].validate(event);
|
|
2023
|
+
if (result.issues) {
|
|
2024
|
+
const message = result.issues[0]?.message;
|
|
2025
|
+
if (message?.startsWith('Unknown event ')) {
|
|
2026
|
+
throw new Error(message);
|
|
2027
|
+
}
|
|
2028
|
+
const type = isObject(event) ? event.type : undefined;
|
|
2029
|
+
throw new Error(typeof type === 'string' ? `Invalid event '${type}' at index ${index}${message ? `: ${message}` : '.'}` : `Invalid event at index ${index}${message ? `: ${message}` : '.'}`);
|
|
2030
|
+
}
|
|
2031
|
+
const validated = result.value;
|
|
2032
|
+
if (!isObject(validated) || typeof validated.type !== 'string') {
|
|
2033
|
+
throw new Error(`Invalid event at index ${index}.`);
|
|
2034
|
+
}
|
|
2035
|
+
return validated;
|
|
2036
|
+
}));
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
/** Creates migration and adaptation utilities for versions of one machine. */
|
|
2040
|
+
function machineVersions(entries, options) {
|
|
2041
|
+
const byIdentity = new Map();
|
|
2042
|
+
const machinesByIdentity = new Map();
|
|
2043
|
+
const machineId = entries[0].id;
|
|
2044
|
+
for (const entry of entries) {
|
|
2045
|
+
if (entry.version === undefined) {
|
|
2046
|
+
throw new Error(`Machine '${entry.id}' must define a version.`);
|
|
2047
|
+
}
|
|
2048
|
+
if (entry.version === '*') {
|
|
2049
|
+
throw new Error("Machine version '*' is reserved for wildcard migrations.");
|
|
2050
|
+
}
|
|
2051
|
+
if (entry.id !== machineId) {
|
|
2052
|
+
throw new Error(`Machine '${entry.id}' does not match machine ID '${machineId}'.`);
|
|
2053
|
+
}
|
|
2054
|
+
const key = `${entry.id}\0${entry.version}`;
|
|
2055
|
+
if (byIdentity.has(key)) {
|
|
2056
|
+
throw new Error(`Duplicate machine identity '${entry.id}' version '${entry.version}'.`);
|
|
2057
|
+
}
|
|
2058
|
+
byIdentity.set(key, entry);
|
|
2059
|
+
if (isVersionedStateMachine(entry)) {
|
|
2060
|
+
machinesByIdentity.set(key, entry);
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
if (options?.unversioned !== undefined && !byIdentity.has(`${machineId}\0${options.unversioned}`)) {
|
|
2064
|
+
throw new Error(`Unversioned snapshot version '${options.unversioned}' is not retained for machine '${machineId}'.`);
|
|
2065
|
+
}
|
|
2066
|
+
const parseSnapshot = async raw => {
|
|
2067
|
+
if (!isObject(raw)) {
|
|
2068
|
+
throw new Error('Persisted snapshot is missing machine identity.');
|
|
2069
|
+
}
|
|
2070
|
+
let id;
|
|
2071
|
+
let version;
|
|
2072
|
+
if (isObject(raw.machine)) {
|
|
2073
|
+
({
|
|
2074
|
+
id,
|
|
2075
|
+
version
|
|
2076
|
+
} = raw.machine);
|
|
2077
|
+
if (typeof id !== 'string' || typeof version !== 'string') {
|
|
2078
|
+
throw new Error('Persisted snapshot has an invalid machine identity.');
|
|
2079
|
+
}
|
|
2080
|
+
} else if (typeof raw.version === 'string') {
|
|
2081
|
+
id = machineId;
|
|
2082
|
+
version = raw.version;
|
|
2083
|
+
} else if (raw.version === undefined && options?.unversioned !== undefined) {
|
|
2084
|
+
id = machineId;
|
|
2085
|
+
version = options.unversioned;
|
|
2086
|
+
} else {
|
|
2087
|
+
throw new Error('Persisted snapshot is missing machine identity.');
|
|
2088
|
+
}
|
|
2089
|
+
if (raw.version !== undefined && typeof raw.version !== 'string') {
|
|
2090
|
+
throw new Error('Persisted snapshot has an invalid version.');
|
|
2091
|
+
}
|
|
2092
|
+
if (raw.version !== undefined && raw.version !== version) {
|
|
2093
|
+
throw new Error(`Persisted snapshot version '${raw.version}' conflicts with machine version '${version}'.`);
|
|
2094
|
+
}
|
|
2095
|
+
const source = byIdentity.get(`${id}\0${version}`);
|
|
2096
|
+
if (!source) {
|
|
2097
|
+
throw new Error(`Unknown machine identity '${id}' version '${version}'.`);
|
|
2098
|
+
}
|
|
2099
|
+
if (!hasSnapshotSchema(source)) {
|
|
2100
|
+
throw new Error(`Machine version '${version}' does not define a snapshot schema.`);
|
|
2101
|
+
}
|
|
2102
|
+
const snapshot = await validate(source.snapshotSchema, raw, `snapshot for machine '${id}' version '${version}'`);
|
|
2103
|
+
return {
|
|
2104
|
+
source,
|
|
2105
|
+
...(isVersionedStateMachine(source) && {
|
|
2106
|
+
machine: source
|
|
2107
|
+
}),
|
|
2108
|
+
snapshot: {
|
|
2109
|
+
...snapshot,
|
|
2110
|
+
machine: {
|
|
2111
|
+
id,
|
|
2112
|
+
version
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
};
|
|
2117
|
+
return {
|
|
2118
|
+
parseSnapshot,
|
|
2119
|
+
async adaptEvents(events, adaptationOptions) {
|
|
2120
|
+
const target = machinesByIdentity.get(`${machineId}\0${adaptationOptions.to}`);
|
|
2121
|
+
if (!target) {
|
|
2122
|
+
throw new Error(`Target version '${adaptationOptions.to}' is not backed by a machine for '${machineId}'.`);
|
|
2123
|
+
}
|
|
2124
|
+
const sourceId = adaptationOptions.from.id ?? machineId;
|
|
2125
|
+
const source = byIdentity.get(`${sourceId}\0${adaptationOptions.from.version}`);
|
|
2126
|
+
let sourceError;
|
|
2127
|
+
let sourceValidationFailed = false;
|
|
2128
|
+
const hasTypedSource = source && hasEventSchema(source);
|
|
2129
|
+
if (hasTypedSource) {
|
|
2130
|
+
let sourceEvents;
|
|
2131
|
+
try {
|
|
2132
|
+
sourceEvents = await validateEvents(events, source);
|
|
2133
|
+
} catch (error) {
|
|
2134
|
+
sourceError = error;
|
|
2135
|
+
sourceValidationFailed = true;
|
|
2136
|
+
}
|
|
2137
|
+
if (sourceEvents) {
|
|
2138
|
+
if (source === target) {
|
|
2139
|
+
return sourceEvents;
|
|
2140
|
+
}
|
|
2141
|
+
const adapter = adaptationOptions.adapters[source.version];
|
|
2142
|
+
if (adapter) {
|
|
2143
|
+
return validateEvents(await adapter(sourceEvents), target);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2147
|
+
const wildcardAdapter = adaptationOptions.adapters['*'];
|
|
2148
|
+
if (wildcardAdapter) {
|
|
2149
|
+
return validateEvents(await wildcardAdapter(events, adaptationOptions.from), target);
|
|
2150
|
+
}
|
|
2151
|
+
if (hasTypedSource) {
|
|
2152
|
+
if (sourceValidationFailed) {
|
|
2153
|
+
throw sourceError instanceof Error ? sourceError : new Error(`Unable to validate event history for machine '${source.id}' version '${source.version}'.`, {
|
|
2154
|
+
cause: sourceError
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
throw new Error(`No event adapter from version '${source.version}' to '${target.version}' for machine '${target.id}'.`);
|
|
2158
|
+
}
|
|
2159
|
+
if (source) {
|
|
2160
|
+
throw new Error(`Machine version '${source.version}' does not define an event schema; only the '*' adapter can handle its event history.`);
|
|
2161
|
+
}
|
|
2162
|
+
throw new Error(`Unknown event history source '${sourceId}' version '${adaptationOptions.from.version}'.`);
|
|
2163
|
+
},
|
|
2164
|
+
async migrateSnapshot(raw, migrationOptions) {
|
|
2165
|
+
const target = machinesByIdentity.get(`${machineId}\0${migrationOptions.to}`);
|
|
2166
|
+
if (!target) {
|
|
2167
|
+
throw new Error(`Target version '${migrationOptions.to}' is not backed by a machine for '${machineId}'.`);
|
|
2168
|
+
}
|
|
2169
|
+
let source;
|
|
2170
|
+
let parseError;
|
|
2171
|
+
try {
|
|
2172
|
+
source = await parseSnapshot(raw);
|
|
2173
|
+
} catch (error) {
|
|
2174
|
+
parseError = error;
|
|
2175
|
+
}
|
|
2176
|
+
if (source) {
|
|
2177
|
+
if (source.source.version === target.version) {
|
|
2178
|
+
return finalizeSnapshot(source.snapshot, target);
|
|
2179
|
+
}
|
|
2180
|
+
const exactMigration = migrationOptions.migrations[source.source.version];
|
|
2181
|
+
if (exactMigration) {
|
|
2182
|
+
return finalizeSnapshot(await exactMigration(source.snapshot), target);
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
const wildcardMigration = migrationOptions.migrations['*'];
|
|
2186
|
+
if (!wildcardMigration) {
|
|
2187
|
+
if (source) {
|
|
2188
|
+
throw new Error(`No snapshot migration from version '${source.source.version}' to '${target.version}' for machine '${target.id}'.`);
|
|
2189
|
+
}
|
|
2190
|
+
throw parseError;
|
|
2191
|
+
}
|
|
2192
|
+
const snapshot = await wildcardMigration(raw, getSnapshotSource(raw, machineId));
|
|
2193
|
+
return finalizeSnapshot(snapshot, target);
|
|
2194
|
+
}
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
|
|
1184
2198
|
/** The Standard Schema interface. */
|
|
1185
2199
|
|
|
2200
|
+
/** Schemas that can be declared for an individual state node. */
|
|
2201
|
+
|
|
1186
2202
|
/** A type-only Standard Schema produced by {@link types}. */
|
|
1187
2203
|
|
|
1188
2204
|
/**
|
|
@@ -1232,6 +2248,28 @@ function isTypeSchema(value) {
|
|
|
1232
2248
|
|
|
1233
2249
|
/** State schema with optional schemas.input and nested states */
|
|
1234
2250
|
|
|
2251
|
+
/**
|
|
2252
|
+
* The sibling state schemas of a dotted path: the children of the path's
|
|
2253
|
+
* parent. A bare transition target resolves relative to the parent, so the
|
|
2254
|
+
* valid targets for a path's config are its siblings. For a dotless (top-level)
|
|
2255
|
+
* path the siblings are the root states.
|
|
2256
|
+
*/
|
|
2257
|
+
|
|
2258
|
+
// dotless path: siblings are the current level's states
|
|
2259
|
+
|
|
2260
|
+
/**
|
|
2261
|
+
* Resolves a dotted state path (e.g. `'parent.child'`) into the leaf
|
|
2262
|
+
* `SetupStateSchema` it addresses, or `never` if any segment is missing.
|
|
2263
|
+
*/
|
|
2264
|
+
|
|
2265
|
+
/** Union of every addressable dotted path into a setup states tree */
|
|
2266
|
+
|
|
2267
|
+
/**
|
|
2268
|
+
* Shared body of both `createStateConfig` overloads: the
|
|
2269
|
+
* `StateNodeConfigWithNestedInput` instantiation whose only varying parts are
|
|
2270
|
+
* the leading sibling-schemas and state-schema arguments.
|
|
2271
|
+
*/
|
|
2272
|
+
|
|
1235
2273
|
/** Extracts input type from a state schema */
|
|
1236
2274
|
|
|
1237
2275
|
/**
|
|
@@ -1287,8 +2325,9 @@ function isTypeSchema(value) {
|
|
|
1287
2325
|
* });
|
|
1288
2326
|
* ```
|
|
1289
2327
|
*/
|
|
1290
|
-
function
|
|
2328
|
+
const setup = function setupImplementation(config = {}) {
|
|
1291
2329
|
const {
|
|
2330
|
+
validator,
|
|
1292
2331
|
states = {},
|
|
1293
2332
|
schemas,
|
|
1294
2333
|
actions,
|
|
@@ -1303,6 +2342,7 @@ function setup(config = {}) {
|
|
|
1303
2342
|
createMachine(machineConfig) {
|
|
1304
2343
|
const configSchemas = machineConfig.schemas;
|
|
1305
2344
|
const mergedSchemas = mergeSchemas(configSchemas, schemas);
|
|
2345
|
+
const mergedStates = mergeStateSchemas(machineConfig.states, states);
|
|
1306
2346
|
const mergedActions = mergeMaps(actions, machineConfig.actions);
|
|
1307
2347
|
const mergedActors = mergeMaps(actors, machineConfig.actors);
|
|
1308
2348
|
const mergedGuards = mergeMaps(guards, machineConfig.guards);
|
|
@@ -1312,6 +2352,9 @@ function setup(config = {}) {
|
|
|
1312
2352
|
...(mergedSchemas ? {
|
|
1313
2353
|
schemas: mergedSchemas
|
|
1314
2354
|
} : undefined),
|
|
2355
|
+
...(mergedStates ? {
|
|
2356
|
+
states: mergedStates
|
|
2357
|
+
} : undefined),
|
|
1315
2358
|
...(mergedActions ? {
|
|
1316
2359
|
actions: mergedActions
|
|
1317
2360
|
} : undefined),
|
|
@@ -1324,12 +2367,62 @@ function setup(config = {}) {
|
|
|
1324
2367
|
...(mergedDelays ? {
|
|
1325
2368
|
delays: mergedDelays
|
|
1326
2369
|
} : undefined)
|
|
2370
|
+
}, undefined, validator);
|
|
2371
|
+
},
|
|
2372
|
+
createStateConfig(...args) {
|
|
2373
|
+
return args.length > 1 ? args[1] : args[0];
|
|
2374
|
+
},
|
|
2375
|
+
states,
|
|
2376
|
+
schemas: schemas ?? {}
|
|
2377
|
+
};
|
|
2378
|
+
};
|
|
2379
|
+
function createSystem(_config = {}) {
|
|
2380
|
+
const runtimeRef = {};
|
|
2381
|
+
const pendingObservers = [];
|
|
2382
|
+
const flushObservers = () => {
|
|
2383
|
+
const runtime = runtimeRef.current;
|
|
2384
|
+
if (!runtime) {
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
for (const entry of pendingObservers) {
|
|
2388
|
+
if (entry.active && !entry.subscription) {
|
|
2389
|
+
entry.subscription = runtime.inspect(entry.observer);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2393
|
+
return {
|
|
2394
|
+
createActor(logic, options) {
|
|
2395
|
+
const actor = createActor(logic, {
|
|
2396
|
+
...options,
|
|
2397
|
+
_systemRef: runtimeRef
|
|
1327
2398
|
});
|
|
2399
|
+
flushObservers();
|
|
2400
|
+
return actor;
|
|
2401
|
+
},
|
|
2402
|
+
get(key) {
|
|
2403
|
+
return runtimeRef.current?.get(key);
|
|
2404
|
+
},
|
|
2405
|
+
getAll() {
|
|
2406
|
+
return runtimeRef.current?.getAll() ?? {};
|
|
1328
2407
|
},
|
|
1329
|
-
|
|
1330
|
-
|
|
2408
|
+
inspect(observer) {
|
|
2409
|
+
const runtime = runtimeRef.current;
|
|
2410
|
+
if (runtime) {
|
|
2411
|
+
return runtime.inspect(observer);
|
|
2412
|
+
}
|
|
2413
|
+
const entry = {
|
|
2414
|
+
observer: observer,
|
|
2415
|
+
active: true
|
|
2416
|
+
};
|
|
2417
|
+
pendingObservers.push(entry);
|
|
2418
|
+
return {
|
|
2419
|
+
unsubscribe() {
|
|
2420
|
+
entry.active = false;
|
|
2421
|
+
entry.subscription?.unsubscribe();
|
|
2422
|
+
}
|
|
2423
|
+
};
|
|
1331
2424
|
},
|
|
1332
|
-
|
|
2425
|
+
setup: setup
|
|
1333
2426
|
};
|
|
1334
2427
|
}
|
|
1335
2428
|
function mergeMaps(left, right) {
|
|
@@ -1346,10 +2439,39 @@ function mergeSchemas(left, right) {
|
|
|
1346
2439
|
...left,
|
|
1347
2440
|
...right,
|
|
1348
2441
|
events: mergeMaps(left?.events, right?.events),
|
|
2442
|
+
actions: mergeMaps(left?.actions, right?.actions),
|
|
2443
|
+
guards: mergeMaps(left?.guards, right?.guards),
|
|
1349
2444
|
emitted: mergeMaps(left?.emitted, right?.emitted),
|
|
1350
2445
|
children: mergeMaps(left?.children, right?.children)
|
|
1351
2446
|
};
|
|
1352
2447
|
}
|
|
2448
|
+
|
|
2449
|
+
/**
|
|
2450
|
+
* Setup schemas win over inline config schemas (same precedence as root
|
|
2451
|
+
* `mergeSchemas`); setup states with no matching config state are skipped.
|
|
2452
|
+
*/
|
|
2453
|
+
function mergeStateSchemas(configStates, setupStates) {
|
|
2454
|
+
if (!configStates || !setupStates) {
|
|
2455
|
+
return configStates;
|
|
2456
|
+
}
|
|
2457
|
+
return Object.fromEntries(Object.entries(configStates).map(([key, configState]) => {
|
|
2458
|
+
const setupState = setupStates[key];
|
|
2459
|
+
if (!setupState) {
|
|
2460
|
+
return [key, configState];
|
|
2461
|
+
}
|
|
2462
|
+
const schemas = mergeMaps(configState.schemas, setupState.schemas);
|
|
2463
|
+
const states = mergeStateSchemas(configState.states, setupState.states);
|
|
2464
|
+
return [key, {
|
|
2465
|
+
...configState,
|
|
2466
|
+
...(schemas ? {
|
|
2467
|
+
schemas
|
|
2468
|
+
} : undefined),
|
|
2469
|
+
...(states ? {
|
|
2470
|
+
states
|
|
2471
|
+
} : undefined)
|
|
2472
|
+
}];
|
|
2473
|
+
}));
|
|
2474
|
+
}
|
|
1353
2475
|
function mergeSetupConfigs(base, extension) {
|
|
1354
2476
|
return {
|
|
1355
2477
|
...base,
|
|
@@ -1363,72 +2485,8 @@ function mergeSetupConfigs(base, extension) {
|
|
|
1363
2485
|
};
|
|
1364
2486
|
}
|
|
1365
2487
|
|
|
1366
|
-
|
|
1367
|
-
function createInertActorScope(actorLogic) {
|
|
1368
|
-
const self = createActor(actorLogic);
|
|
1369
|
-
const inertActorScope = {
|
|
1370
|
-
self: self,
|
|
1371
|
-
defer: () => {},
|
|
1372
|
-
id: '',
|
|
1373
|
-
logger: () => {},
|
|
1374
|
-
sessionId: '',
|
|
1375
|
-
stopChild: () => {},
|
|
1376
|
-
system: self.system,
|
|
1377
|
-
emit: () => {},
|
|
1378
|
-
actionExecutor: () => {}
|
|
1379
|
-
};
|
|
1380
|
-
return inertActorScope;
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
/** @deprecated Use `initialTransition(…)` instead. */
|
|
1384
|
-
function getInitialSnapshot(actorLogic, ...[input]) {
|
|
1385
|
-
const actorScope = createInertActorScope(actorLogic);
|
|
1386
|
-
return actorLogic.initialTransition(input, actorScope)[0];
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
/**
|
|
1390
|
-
* Determines the next snapshot for the given `actorLogic` based on the given
|
|
1391
|
-
* `snapshot` and `event`.
|
|
1392
|
-
*
|
|
1393
|
-
* If the `snapshot` is `undefined`, the initial snapshot of the `actorLogic` is
|
|
1394
|
-
* used.
|
|
1395
|
-
*
|
|
1396
|
-
* @deprecated Use `transition(…)` instead.
|
|
1397
|
-
* @example
|
|
1398
|
-
*
|
|
1399
|
-
* ```ts
|
|
1400
|
-
* import { getNextSnapshot } from 'xstate';
|
|
1401
|
-
* import { trafficLightMachine } from './trafficLightMachine.ts';
|
|
1402
|
-
*
|
|
1403
|
-
* const nextSnapshot = getNextSnapshot(
|
|
1404
|
-
* trafficLightMachine, // actor logic
|
|
1405
|
-
* undefined, // snapshot (or initial state if undefined)
|
|
1406
|
-
* { type: 'TIMER' }
|
|
1407
|
-
* ); // event object
|
|
1408
|
-
*
|
|
1409
|
-
* console.log(nextSnapshot.value);
|
|
1410
|
-
* // => 'yellow'
|
|
1411
|
-
*
|
|
1412
|
-
* const nextSnapshot2 = getNextSnapshot(
|
|
1413
|
-
* trafficLightMachine, // actor logic
|
|
1414
|
-
* nextSnapshot, // snapshot
|
|
1415
|
-
* { type: 'TIMER' }
|
|
1416
|
-
* ); // event object
|
|
1417
|
-
*
|
|
1418
|
-
* console.log(nextSnapshot2.value);
|
|
1419
|
-
* // =>'red'
|
|
1420
|
-
* ```
|
|
1421
|
-
*/
|
|
1422
|
-
function getNextSnapshot(actorLogic, snapshot, event) {
|
|
1423
|
-
const inertActorScope = createInertActorScope(actorLogic);
|
|
1424
|
-
inertActorScope.self._snapshot = snapshot;
|
|
1425
|
-
const transitionResult = actorLogic.transition(snapshot, event, inertActorScope);
|
|
1426
|
-
return Array.isArray(transitionResult) ? transitionResult[0] : transitionResult;
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
2488
|
+
// oxlint-disable-next-line typescript/no-unsafe-declaration-merging
|
|
1430
2489
|
|
|
1431
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
1432
2490
|
class SimulatedClock {
|
|
1433
2491
|
constructor() {
|
|
1434
2492
|
this.timeouts = new Map();
|
|
@@ -1535,6 +2593,7 @@ function toPromise(actor) {
|
|
|
1535
2593
|
}
|
|
1536
2594
|
|
|
1537
2595
|
// this is needed to make JSDoc `@link` work properly
|
|
2596
|
+
// oxlint-disable-next-line no-unused-vars
|
|
1538
2597
|
|
|
1539
2598
|
/**
|
|
1540
2599
|
* @remarks
|
|
@@ -1582,23 +2641,16 @@ let SpecialTargets = /*#__PURE__*/function (SpecialTargets) {
|
|
|
1582
2641
|
return SpecialTargets;
|
|
1583
2642
|
}({});
|
|
1584
2643
|
|
|
1585
|
-
/**
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
*/
|
|
2644
|
+
/** A deterministic delayed-delivery declaration owned by a logic snapshot. */
|
|
2645
|
+
|
|
2646
|
+
/** The logical input delivered to a timer's source when its runtime delay ends. */
|
|
2647
|
+
|
|
2648
|
+
/** A persisted snapshot tied to a versioned actor logic identity. */
|
|
1590
2649
|
|
|
1591
2650
|
/**
|
|
1592
|
-
*
|
|
1593
|
-
* `
|
|
1594
|
-
*
|
|
1595
|
-
* - `'resume'` (default) — the clock was "frozen" while persisted: a 5-minute
|
|
1596
|
-
* timer persisted with 1 minute elapsed fires after 4 more minutes.
|
|
1597
|
-
* - `'restart'` — timers start over with their full delay.
|
|
1598
|
-
* - `'absolute'` — timers honor the original wall-clock expiry (`startedAt +
|
|
1599
|
-
* delay`); already-expired timers fire immediately.
|
|
1600
|
-
* - A custom function receiving the pending effect and returning the remaining
|
|
1601
|
-
* delay in ms.
|
|
2651
|
+
* A persisted snapshot restorable into the given actor logic: any snapshot
|
|
2652
|
+
* created by logic with the same machine `id` is accepted, regardless of
|
|
2653
|
+
* version (version mismatches are handled at runtime, e.g. by `migrate`).
|
|
1602
2654
|
*/
|
|
1603
2655
|
|
|
1604
2656
|
/** @deprecated Use `AnyActor` instead. */
|
|
@@ -1636,17 +2688,15 @@ let SpecialTargets = /*#__PURE__*/function (SpecialTargets) {
|
|
|
1636
2688
|
/**
|
|
1637
2689
|
* The actor's own full self handle.
|
|
1638
2690
|
*
|
|
1639
|
-
* Internals and action/guard
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
2691
|
+
* Internals and action/guard sources receive this shape because `self` can
|
|
2692
|
+
* participate in runtime-owned behavior while still being usable wherever an
|
|
2693
|
+
* `ActorRef` is expected.
|
|
1642
2694
|
*/
|
|
1643
2695
|
|
|
1644
2696
|
// TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
|
|
1645
2697
|
|
|
1646
2698
|
/** The concrete actor instance type produced from actor logic. */
|
|
1647
2699
|
|
|
1648
|
-
// TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
|
|
1649
|
-
|
|
1650
2700
|
/**
|
|
1651
2701
|
* Represents logic which can be used by an actor.
|
|
1652
2702
|
*
|
|
@@ -1664,121 +2714,11 @@ let SpecialTargets = /*#__PURE__*/function (SpecialTargets) {
|
|
|
1664
2714
|
|
|
1665
2715
|
/** @deprecated */
|
|
1666
2716
|
|
|
1667
|
-
/**
|
|
1668
|
-
|
|
1669
|
-
/**
|
|
1670
|
-
* Given actor `logic`, a `snapshot`, and an `event`, returns a tuple of the
|
|
1671
|
-
* `nextSnapshot` and `actions` to execute.
|
|
1672
|
-
*
|
|
1673
|
-
* This is a pure function that does not execute `actions`.
|
|
1674
|
-
*/
|
|
1675
|
-
function transition(logic, snapshot, event) {
|
|
1676
|
-
const actorScope = createInertActorScope(logic);
|
|
1677
|
-
const [nextSnapshot, effects] = logic.transition(snapshot, event, actorScope);
|
|
1678
|
-
return [nextSnapshot, effects];
|
|
1679
|
-
}
|
|
2717
|
+
/** The terminal result published when an actor completes or errors. */
|
|
1680
2718
|
|
|
1681
|
-
/**
|
|
1682
|
-
* Given actor `logic` and optional `input`, returns a tuple of the
|
|
1683
|
-
* `nextSnapshot` and `actions` to execute from the initial transition (no
|
|
1684
|
-
* previous state).
|
|
1685
|
-
*
|
|
1686
|
-
* This is a pure function that does not execute `actions`.
|
|
1687
|
-
*/
|
|
1688
|
-
function initialTransition(logic, ...[input]) {
|
|
1689
|
-
const actorScope = createInertActorScope(logic);
|
|
1690
|
-
const [nextSnapshot, executableActions] = logic.initialTransition(input, actorScope);
|
|
1691
|
-
return [nextSnapshot, executableActions];
|
|
1692
|
-
}
|
|
2719
|
+
/** An executable effect that publishes an actor's terminal result. */
|
|
1693
2720
|
|
|
1694
|
-
/**
|
|
1695
|
-
* Given a state `machine`, a `snapshot`, and an `event`, returns an array of
|
|
1696
|
-
* microsteps, where each microstep is a tuple of `[snapshot, actions]`.
|
|
1697
|
-
*
|
|
1698
|
-
* This is a pure function that does not execute `actions`.
|
|
1699
|
-
*/
|
|
1700
|
-
function getMicrosteps(machine, snapshot, event) {
|
|
1701
|
-
const actorScope = createInertActorScope(machine);
|
|
1702
|
-
const {
|
|
1703
|
-
microsteps
|
|
1704
|
-
} = macrostep(snapshot, event, actorScope, []);
|
|
1705
|
-
return microsteps;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
/**
|
|
1709
|
-
* Given a state `machine` and optional `input`, returns an array of microsteps
|
|
1710
|
-
* from the initial transition, where each microstep is a tuple of `[snapshot,
|
|
1711
|
-
* actions]`.
|
|
1712
|
-
*
|
|
1713
|
-
* This is a pure function that does not execute `actions`.
|
|
1714
|
-
*/
|
|
1715
|
-
function getInitialMicrosteps(machine, ...[input]) {
|
|
1716
|
-
const actorScope = createInertActorScope(machine);
|
|
1717
|
-
const initEvent = createInitEvent(input);
|
|
1718
|
-
const internalQueue = [];
|
|
1719
|
-
const preInitialSnapshot = machine._getPreInitialState(actorScope, initEvent);
|
|
1720
|
-
const first = initialMicrostep(machine.root, preInitialSnapshot, actorScope, initEvent, internalQueue);
|
|
1721
|
-
const {
|
|
1722
|
-
microsteps
|
|
1723
|
-
} = macrostep(first[0], initEvent, actorScope, internalQueue);
|
|
1724
|
-
return [first, ...microsteps];
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
/**
|
|
1728
|
-
* Gets all potential next transitions from the current state.
|
|
1729
|
-
*
|
|
1730
|
-
* Returns all transitions that are available from the current state, including:
|
|
1731
|
-
*
|
|
1732
|
-
* - All transitions from atomic states (leaf states in the current state
|
|
1733
|
-
* configuration)
|
|
1734
|
-
* - All transitions from ancestor states (parent states that may handle events)
|
|
1735
|
-
* - All guarded transitions (regardless of whether their guards would pass)
|
|
1736
|
-
* - Always (eventless) transitions
|
|
1737
|
-
* - After (delayed) transitions
|
|
1738
|
-
*
|
|
1739
|
-
* The order of transitions is deterministic:
|
|
1740
|
-
*
|
|
1741
|
-
* 1. Atomic states are processed in document order
|
|
1742
|
-
* 2. For each atomic state, transitions are collected from the state itself first,
|
|
1743
|
-
* then its ancestors
|
|
1744
|
-
* 3. Within each state node, transitions are in the order they appear in the state
|
|
1745
|
-
* definition
|
|
1746
|
-
*
|
|
1747
|
-
* @param state - The current machine snapshot
|
|
1748
|
-
* @returns Array of transition definitions from the current state, in
|
|
1749
|
-
* deterministic order
|
|
1750
|
-
*/
|
|
1751
|
-
function getNextTransitions(state) {
|
|
1752
|
-
const potentialTransitions = [];
|
|
1753
|
-
const atomicStates = state._nodes.filter(isAtomicStateNode);
|
|
1754
|
-
const visited = new Set();
|
|
1755
|
-
|
|
1756
|
-
// Collect all transitions from atomic states and their ancestors
|
|
1757
|
-
// Process atomic states in document order (as they appear in state._nodes)
|
|
1758
|
-
for (const stateNode of atomicStates) {
|
|
1759
|
-
// For each atomic state, process the state itself first, then its ancestors
|
|
1760
|
-
// This ensures child state transitions come before parent state transitions
|
|
1761
|
-
for (const s of [stateNode].concat(getProperAncestors(stateNode, undefined))) {
|
|
1762
|
-
if (visited.has(s.id)) {
|
|
1763
|
-
continue;
|
|
1764
|
-
}
|
|
1765
|
-
visited.add(s.id);
|
|
1766
|
-
|
|
1767
|
-
// Get all transitions for each event type
|
|
1768
|
-
// Include ALL transitions, even if the same event type appears in multiple state nodes
|
|
1769
|
-
// This is important for guarded transitions - all are "potential" regardless of guard evaluation
|
|
1770
|
-
for (const [, transitions] of s.transitions.entries()) {
|
|
1771
|
-
potentialTransitions.push(...transitions);
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
// Also include always (eventless) transitions
|
|
1775
|
-
if (s.always) {
|
|
1776
|
-
potentialTransitions.push(...s.always);
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
}
|
|
1780
|
-
return potentialTransitions;
|
|
1781
|
-
}
|
|
2721
|
+
/** Mappers for subscribeTo - maps lifecycle events to machine events */
|
|
1782
2722
|
|
|
1783
2723
|
const defaultWaitForOptions = {
|
|
1784
2724
|
timeout: Infinity // much more than 10 seconds
|
|
@@ -1815,7 +2755,7 @@ function waitFor(actorRef, predicate, options) {
|
|
|
1815
2755
|
signal
|
|
1816
2756
|
} = resolvedOptions;
|
|
1817
2757
|
if (signal?.aborted) {
|
|
1818
|
-
//
|
|
2758
|
+
// oxlint-disable-next-line typescript/prefer-promise-reject-errors
|
|
1819
2759
|
rej(signal.reason);
|
|
1820
2760
|
return;
|
|
1821
2761
|
}
|
|
@@ -1844,7 +2784,7 @@ function waitFor(actorRef, predicate, options) {
|
|
|
1844
2784
|
* `abort` event
|
|
1845
2785
|
*/
|
|
1846
2786
|
let abortListener;
|
|
1847
|
-
//
|
|
2787
|
+
// oxlint-disable-next-line prefer-const
|
|
1848
2788
|
let sub; // avoid TDZ when disposing synchronously
|
|
1849
2789
|
|
|
1850
2790
|
// See if the current snapshot already matches the predicate
|
|
@@ -1858,7 +2798,7 @@ function waitFor(actorRef, predicate, options) {
|
|
|
1858
2798
|
abortListener = () => {
|
|
1859
2799
|
dispose();
|
|
1860
2800
|
// XState does not "own" the signal, so we should reject with its reason (if any)
|
|
1861
|
-
//
|
|
2801
|
+
// oxlint-disable-next-line typescript/prefer-promise-reject-errors
|
|
1862
2802
|
rej(signal.reason);
|
|
1863
2803
|
};
|
|
1864
2804
|
signal.addEventListener('abort', abortListener);
|
|
@@ -1867,7 +2807,7 @@ function waitFor(actorRef, predicate, options) {
|
|
|
1867
2807
|
next: checkEmitted,
|
|
1868
2808
|
error: err => {
|
|
1869
2809
|
dispose();
|
|
1870
|
-
//
|
|
2810
|
+
// oxlint-disable-next-line typescript/prefer-promise-reject-errors
|
|
1871
2811
|
rej(err);
|
|
1872
2812
|
},
|
|
1873
2813
|
complete: () => {
|
|
@@ -1881,4 +2821,4 @@ function waitFor(actorRef, predicate, options) {
|
|
|
1881
2821
|
});
|
|
1882
2822
|
}
|
|
1883
2823
|
|
|
1884
|
-
export { SimulatedClock, SpecialTargets, assertEvent, createMachine, createMachineFromConfig, createStateConfig,
|
|
2824
|
+
export { SimulatedClock, SpecialTargets, assertEvent, createFSM, createMachine, createMachineFromConfig, createStateConfig, createSystem, isTypeSchema, machineConfigToJSON, machineVersions, mapState, serializeMachine, setup, toPromise, types, waitFor };
|