xstate 5.32.0 → 6.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -8
- package/dist/StateMachine-47e60715.esm.js +720 -0
- package/dist/StateMachine-48dcad8f.cjs.js +723 -0
- package/dist/StateMachine-6e063ac1.development.cjs.js +740 -0
- package/dist/StateMachine-6ec50e21.development.esm.js +737 -0
- package/dist/declarations/src/State.d.ts +11 -13
- package/dist/declarations/src/StateMachine.d.ts +27 -21
- package/dist/declarations/src/StateNode.d.ts +18 -54
- package/dist/declarations/src/actions.d.ts +14 -9
- package/dist/declarations/src/actors/callback.d.ts +42 -16
- package/dist/declarations/src/actors/index.d.ts +9 -6
- package/dist/declarations/src/actors/listener.d.ts +18 -0
- package/dist/declarations/src/actors/logic.d.ts +105 -0
- package/dist/declarations/src/actors/observable.d.ts +72 -27
- package/dist/declarations/src/actors/promise.d.ts +133 -48
- package/dist/declarations/src/actors/subscription.d.ts +22 -0
- package/dist/declarations/src/atom.d.ts +81 -0
- package/dist/declarations/src/createActor.d.ts +31 -33
- package/dist/declarations/src/createMachine.d.ts +35 -7
- package/dist/declarations/src/createMachineFromConfig.d.ts +176 -0
- package/dist/declarations/src/graph/graph.d.ts +4 -2
- package/dist/declarations/src/index.d.ts +12 -8
- package/dist/declarations/src/inspection.d.ts +84 -30
- package/dist/declarations/src/mapState.d.ts +4 -2
- package/dist/declarations/src/schema.types.d.ts +90 -0
- package/dist/declarations/src/serialize.d.ts +44 -0
- package/dist/declarations/src/setup.d.ts +251 -123
- package/dist/declarations/src/spawn.d.ts +6 -31
- package/dist/declarations/src/stateUtils.d.ts +21 -19
- package/dist/declarations/src/system.d.ts +8 -7
- package/dist/declarations/src/transition.d.ts +5 -5
- package/dist/declarations/src/types.d.ts +407 -406
- package/dist/declarations/src/types.v6.d.ts +392 -0
- package/dist/declarations/src/utils.d.ts +4 -3
- package/dist/index-39259ea4.development.esm.js +4948 -0
- package/dist/index-688701ed.esm.js +4909 -0
- package/dist/index-c35824b4.development.cjs.js +5007 -0
- package/dist/index-f0d7107d.cjs.js +4968 -0
- package/dist/xstate-actors.cjs.js +12 -838
- package/dist/xstate-actors.cjs.mjs +10 -5
- package/dist/xstate-actors.development.cjs.js +12 -838
- package/dist/xstate-actors.development.cjs.mjs +10 -5
- package/dist/xstate-actors.development.esm.js +1 -836
- package/dist/xstate-actors.esm.js +1 -836
- package/dist/xstate-actors.umd.min.js +1 -1
- package/dist/xstate-actors.umd.min.js.map +1 -1
- package/dist/xstate-graph.cjs.js +18 -29
- package/dist/xstate-graph.development.cjs.js +18 -29
- package/dist/xstate-graph.development.esm.js +15 -26
- package/dist/xstate-graph.esm.js +15 -26
- package/dist/xstate-graph.umd.min.js +1 -1
- package/dist/xstate-graph.umd.min.js.map +1 -1
- package/dist/xstate.cjs.js +1316 -108
- package/dist/xstate.cjs.mjs +22 -22
- package/dist/xstate.development.cjs.js +1316 -108
- package/dist/xstate.development.cjs.mjs +22 -22
- package/dist/xstate.development.esm.js +1278 -72
- package/dist/xstate.esm.js +1278 -72
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/package.json +3 -47
- package/dist/StateMachine-45e79c35.esm.js +0 -560
- package/dist/StateMachine-be2e51a3.development.cjs.js +0 -569
- package/dist/StateMachine-e0178d48.development.esm.js +0 -566
- package/dist/StateMachine-e6732977.cjs.js +0 -563
- package/dist/assign-2aa58daa.esm.js +0 -127
- package/dist/assign-927227d6.development.cjs.js +0 -135
- package/dist/assign-e8f2bd75.cjs.js +0 -129
- package/dist/assign-f338cee3.development.esm.js +0 -133
- package/dist/declarations/src/actions/assign.d.ts +0 -45
- package/dist/declarations/src/actions/cancel.d.ts +0 -39
- package/dist/declarations/src/actions/emit.d.ts +0 -44
- package/dist/declarations/src/actions/enqueueActions.d.ts +0 -57
- package/dist/declarations/src/actions/log.d.ts +0 -16
- package/dist/declarations/src/actions/raise.d.ts +0 -21
- package/dist/declarations/src/actions/send.d.ts +0 -42
- package/dist/declarations/src/actions/spawnChild.d.ts +0 -34
- package/dist/declarations/src/actions/stopChild.d.ts +0 -19
- package/dist/declarations/src/actors/transition.d.ts +0 -143
- package/dist/declarations/src/dev/index.d.ts +0 -14
- package/dist/declarations/src/guards.d.ts +0 -128
- package/dist/log-029f180b.development.cjs.js +0 -433
- package/dist/log-5a37e0ee.development.esm.js +0 -425
- package/dist/log-605ef461.esm.js +0 -397
- package/dist/log-fedf0966.cjs.js +0 -405
- package/dist/raise-74097812.development.esm.js +0 -2823
- package/dist/raise-7c948725.cjs.js +0 -2816
- package/dist/raise-964cd4e9.development.cjs.js +0 -2878
- package/dist/raise-e974d1c9.esm.js +0 -2761
- package/dist/xstate-actions.cjs.d.mts +0 -2
- package/dist/xstate-actions.cjs.d.ts +0 -2
- package/dist/xstate-actions.cjs.js +0 -23
- package/dist/xstate-actions.cjs.mjs +0 -14
- package/dist/xstate-actions.development.cjs.js +0 -23
- package/dist/xstate-actions.development.cjs.mjs +0 -14
- package/dist/xstate-actions.development.esm.js +0 -4
- package/dist/xstate-actions.esm.js +0 -4
- package/dist/xstate-actions.umd.min.js +0 -2
- package/dist/xstate-actions.umd.min.js.map +0 -1
- package/dist/xstate-dev.cjs.d.mts +0 -2
- package/dist/xstate-dev.cjs.d.ts +0 -2
- package/dist/xstate-dev.cjs.js +0 -48
- package/dist/xstate-dev.cjs.mjs +0 -5
- package/dist/xstate-dev.development.cjs.js +0 -51
- package/dist/xstate-dev.development.cjs.mjs +0 -5
- package/dist/xstate-dev.development.esm.js +0 -45
- package/dist/xstate-dev.esm.js +0 -42
- package/dist/xstate-dev.umd.min.js +0 -2
- package/dist/xstate-dev.umd.min.js.map +0 -1
- package/dist/xstate-guards.cjs.d.mts +0 -2
- package/dist/xstate-guards.cjs.d.ts +0 -2
- package/dist/xstate-guards.cjs.js +0 -14
- package/dist/xstate-guards.cjs.mjs +0 -7
- package/dist/xstate-guards.development.cjs.js +0 -14
- package/dist/xstate-guards.development.cjs.mjs +0 -7
- package/dist/xstate-guards.development.esm.js +0 -2
- package/dist/xstate-guards.esm.js +0 -2
- package/dist/xstate-guards.umd.min.js +0 -2
- package/dist/xstate-guards.umd.min.js.map +0 -1
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export { S as StateMachine, a as StateNode } from './StateMachine-e0178d48.development.esm.js';
|
|
6
|
-
import { a as assign } from './assign-f338cee3.development.esm.js';
|
|
7
|
-
export { a as assign } from './assign-f338cee3.development.esm.js';
|
|
8
|
-
import { s as sendTo, l as log, e as enqueueActions, a as emit } from './log-5a37e0ee.development.esm.js';
|
|
9
|
-
export { S as SpecialTargets, a as emit, e as enqueueActions, f as forwardTo, l as log, b as sendParent, s as sendTo } from './log-5a37e0ee.development.esm.js';
|
|
10
|
-
import './xstate-dev.development.esm.js';
|
|
1
|
+
import { m as matchesEventDescriptor, t as toArray, p as parseDelayToMilliseconds, i as isAtomicStateNode, c as createActor, a as macrostep, b as createInitEvent, d as initialMicrostep, g as getProperAncestors } from './index-39259ea4.development.esm.js';
|
|
2
|
+
export { A as Actor, T as TimeoutError, o as __unsafe_getAllOwnEventDescriptors, r as checkStateIn, c as createActor, h as createAsyncAtom, B as createAsyncLogic, e as createAtom, f as createAtomConfig, w as createCallbackLogic, v as createEmptyActor, y as createEventObservableLogic, C as createListenerLogic, z as createLogic, x as createObservableLogic, j as createReducerAtom, E as createSubscriptionLogic, n as getStateNodes, k as isAtom, l as isMachineSnapshot, D as listenerLogic, q as matchesState, s as pathToStateValue, F as subscriptionLogic, u as toObserver } from './index-39259ea4.development.esm.js';
|
|
3
|
+
import { S as StateMachine } from './StateMachine-6ec50e21.development.esm.js';
|
|
4
|
+
export { S as StateMachine, a as StateNode } from './StateMachine-6ec50e21.development.esm.js';
|
|
11
5
|
|
|
12
6
|
/**
|
|
13
7
|
* Asserts that the given event object is of the specified type or types. Throws
|
|
@@ -83,8 +77,1004 @@ function assertEvent(event, type) {
|
|
|
83
77
|
* @param options DEPRECATED: use `setup({ ... })` or `machine.provide({ ... })`
|
|
84
78
|
* to provide machine implementations instead.
|
|
85
79
|
*/
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
// Overload 1: With schemas.context — context type inferred from schema
|
|
81
|
+
|
|
82
|
+
// Overload 2: Without schemas.context — context type inferred from config.context
|
|
83
|
+
|
|
84
|
+
// Implementation
|
|
85
|
+
function createMachine(config) {
|
|
86
|
+
return new StateMachine(config);
|
|
87
|
+
}
|
|
88
|
+
function createStateConfig(config) {
|
|
89
|
+
return config;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function delayToMs(delay) {
|
|
93
|
+
const parsedDelay = parseDelayToMilliseconds(delay);
|
|
94
|
+
if (parsedDelay !== undefined) return parsedDelay;
|
|
95
|
+
return typeof delay === 'string' ? parseFloat(delay) || 0 : delay;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Isolated executable-content block. Errors in nested actions stop this block
|
|
100
|
+
* but do not propagate to the surrounding action list. Used to model SCXML's
|
|
101
|
+
* separate <onentry>/<onexit> blocks: each is its own block per spec.
|
|
102
|
+
*/
|
|
103
|
+
|
|
104
|
+
function isUnserializableMarker(value) {
|
|
105
|
+
return !!value && typeof value === 'object' && '$unserializable' in value && typeof value.$unserializable === 'string';
|
|
106
|
+
}
|
|
107
|
+
function assertNoUnresolvedMarkers(value, path = '$') {
|
|
108
|
+
if (isUnserializableMarker(value)) {
|
|
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
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function assertRootImplementationMarkers(json, implementations) {
|
|
131
|
+
for (const kind of ['actions', 'guards', 'actors', 'delays']) {
|
|
132
|
+
const map = json[kind];
|
|
133
|
+
if (!map) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
for (const key of Object.keys(map)) {
|
|
137
|
+
const value = map[key];
|
|
138
|
+
if (!isUnserializableMarker(value)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!implementations[kind]?.[key]) {
|
|
142
|
+
throw new Error(`Missing ${kind}.${key}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function extractSerializableDelays(delays) {
|
|
148
|
+
const result = {};
|
|
149
|
+
if (!delays) {
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
for (const key of Object.keys(delays)) {
|
|
153
|
+
const value = delays[key];
|
|
154
|
+
if (typeof value === 'number') {
|
|
155
|
+
result[key] = value;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
function mergeImplementations(json, implementations) {
|
|
161
|
+
return {
|
|
162
|
+
actions: implementations.actions ?? {},
|
|
163
|
+
guards: implementations.guards ?? {},
|
|
164
|
+
actors: implementations.actors ?? {},
|
|
165
|
+
delays: {
|
|
166
|
+
...extractSerializableDelays(json.delays),
|
|
167
|
+
...(implementations.delays ?? {})
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
let _scxmlSessionId = 'session_' + Math.random().toString(36).slice(2);
|
|
172
|
+
let _scxmlMachineName = '';
|
|
173
|
+
|
|
174
|
+
/** Evaluates an SCXML expression with context variables available via `with`. */
|
|
175
|
+
function evaluateExpr(context, expr, event, self) {
|
|
176
|
+
const fnBody = `
|
|
177
|
+
with (context) {
|
|
178
|
+
return (${expr});
|
|
179
|
+
}
|
|
180
|
+
`.trim();
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
182
|
+
const fn = new Function('context', '_event', '_sessionid', '_name', '_ioprocessors', 'In', fnBody);
|
|
183
|
+
const In = stateId => {
|
|
184
|
+
if (!self) return false;
|
|
185
|
+
try {
|
|
186
|
+
const snap = self.getSnapshot?.();
|
|
187
|
+
if (snap?._nodes) {
|
|
188
|
+
const sanitized = stateId.replace(/\./g, '$');
|
|
189
|
+
return snap._nodes.some(node => node.id === sanitized || node.id.endsWith('.' + sanitized));
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// ignore
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
};
|
|
196
|
+
const SCXML_ORIGIN = '#_scxml_session';
|
|
197
|
+
const SCXML_ORIGIN_TYPE = 'http://www.w3.org/TR/scxml/#SCXMLEventProcessor';
|
|
198
|
+
const isExternal = event && event._scxmlExternal;
|
|
199
|
+
const isInitEvent = event && event.type === '@xstate.init';
|
|
200
|
+
// Per SCXML spec, _event has these fields. Fields that don't apply to a
|
|
201
|
+
// particular event are present with value `undefined` (so `'name' in _event`
|
|
202
|
+
// is true but `typeof _event.name === 'undefined'` for unset fields).
|
|
203
|
+
// For xstate.done.* events, _event.data is the `output` payload (matches
|
|
204
|
+
// SCXML's done event semantics where data is the donedata).
|
|
205
|
+
const isDoneEvent = event && typeof event.type === 'string' && event.type.startsWith('xstate.done.');
|
|
206
|
+
const explicitType = event && event._scxmlEventType;
|
|
207
|
+
const scxmlEvent = event && !isInitEvent ? {
|
|
208
|
+
name: event.type,
|
|
209
|
+
type: explicitType || (isExternal ? 'external' : 'internal'),
|
|
210
|
+
sendid: event._scxmlSendId,
|
|
211
|
+
origin: isExternal ? SCXML_ORIGIN : undefined,
|
|
212
|
+
origintype: isExternal ? SCXML_ORIGIN_TYPE : undefined,
|
|
213
|
+
invokeid: event._scxmlInvokeId,
|
|
214
|
+
data: isDoneEvent ? event.output : event._scxmlEventData !== undefined ? event._scxmlEventData : event
|
|
215
|
+
} : undefined;
|
|
216
|
+
const result = fn(context, scxmlEvent, _scxmlSessionId, _scxmlMachineName, {
|
|
217
|
+
scxml: {
|
|
218
|
+
location: `#_scxml_${_scxmlSessionId}`
|
|
219
|
+
}
|
|
220
|
+
}, In);
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Executes an SCXML script block and returns updated context values. */
|
|
225
|
+
function executeScript(context, code) {
|
|
226
|
+
// Create a proxy to track which properties are modified
|
|
227
|
+
const updates = {};
|
|
228
|
+
const contextKeys = Object.keys(context);
|
|
229
|
+
|
|
230
|
+
// Build variable declarations and reassignment capture
|
|
231
|
+
const varDeclarations = contextKeys.map(k => `let ${k} = context.${k};`).join('\n');
|
|
232
|
+
const captureUpdates = contextKeys.map(k => `updates.${k} = ${k};`).join('\n');
|
|
233
|
+
const fnBody = `
|
|
234
|
+
${varDeclarations}
|
|
235
|
+
${code}
|
|
236
|
+
${captureUpdates}
|
|
237
|
+
return updates;
|
|
238
|
+
`;
|
|
239
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
240
|
+
const fn = new Function('context', 'updates', fnBody);
|
|
241
|
+
return fn(context, updates);
|
|
242
|
+
}
|
|
243
|
+
function createMachineFromConfig(json, implementations = {}) {
|
|
244
|
+
assertRootImplementationMarkers(json, implementations);
|
|
245
|
+
assertNoUnresolvedMarkers(json);
|
|
246
|
+
const resolvedImplementations = mergeImplementations(json, implementations);
|
|
247
|
+
|
|
248
|
+
// Pending transition actions: set by .to functions, consumed by entry functions.
|
|
249
|
+
// This bridges SCXML's exit→transition→entry action ordering with XState's
|
|
250
|
+
// .to function receiving pre-exit context.
|
|
251
|
+
// Map keyed by target state ID so parallel transitions don't overwrite each other.
|
|
252
|
+
const pendingTransitionActionsMap = {};
|
|
253
|
+
|
|
254
|
+
// Ordered queue of ALL transition actions (targeted + targetless) for parallel
|
|
255
|
+
// context sharing. In SCXML, all transition actions execute sequentially in
|
|
256
|
+
// document order with a shared evolving data model.
|
|
257
|
+
const allTransitionActions = [];
|
|
258
|
+
// Pre-transition context saved when a targetless .to executes. Used by entry
|
|
259
|
+
// functions to re-execute all transition actions from scratch when parallel
|
|
260
|
+
// targetless transitions coexist with targeted transitions.
|
|
261
|
+
let contextBeforeTargetless = null;
|
|
262
|
+
// Platform errors collected during transition selection (e.g., failing
|
|
263
|
+
// <transition cond>). Drained by the next state's entry into the internal
|
|
264
|
+
// event queue so they're processed before any external events.
|
|
265
|
+
const pendingPlatformErrors = [];
|
|
266
|
+
function iterNode(node, nodeKey) {
|
|
267
|
+
const originalEntryActions = node.entry;
|
|
268
|
+
const stateId = node.id || nodeKey;
|
|
269
|
+
|
|
270
|
+
// Wrap entry to first execute any pending transition actions, then normal entry.
|
|
271
|
+
// This ensures SCXML execution order: exit → transition_actions → entry_actions
|
|
272
|
+
// because pending transition actions are set by .to (before exit) but executed
|
|
273
|
+
// in entry (after exit), thus seeing post-exit context.
|
|
274
|
+
const entryFn = (x, enq) => {
|
|
275
|
+
// Drain any platform errors queued during transition selection (e.g.,
|
|
276
|
+
// failing <transition cond="..."> evaluations). These must be raised
|
|
277
|
+
// internally so they're processed before subsequent external events.
|
|
278
|
+
while (pendingPlatformErrors.length) {
|
|
279
|
+
const err = pendingPlatformErrors.shift();
|
|
280
|
+
enq.raise({
|
|
281
|
+
type: 'error.execution',
|
|
282
|
+
_scxmlEventType: 'platform',
|
|
283
|
+
_scxmlEventData: err
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
let context;
|
|
287
|
+
|
|
288
|
+
// If targetless transitions were interleaved with targeted transitions
|
|
289
|
+
// (parallel state), re-execute ALL transition actions from the original
|
|
290
|
+
// pre-transition context. This overrides any .to-produced context with
|
|
291
|
+
// the correct SCXML document-order sequential execution.
|
|
292
|
+
// Only trigger when BOTH targeted (pending in map) and targetless fired
|
|
293
|
+
// in the same microstep — prevents stale data from previous events.
|
|
294
|
+
if (contextBeforeTargetless && allTransitionActions.length > 0 && Object.keys(pendingTransitionActionsMap).length > 0) {
|
|
295
|
+
let ctx = contextBeforeTargetless;
|
|
296
|
+
for (const actions of allTransitionActions) {
|
|
297
|
+
const mergedX = {
|
|
298
|
+
...x,
|
|
299
|
+
context: ctx
|
|
300
|
+
};
|
|
301
|
+
const result = executeActions(actions, mergedX, enq);
|
|
302
|
+
if (result.context) {
|
|
303
|
+
ctx = result.context;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
allTransitionActions.length = 0;
|
|
307
|
+
contextBeforeTargetless = null;
|
|
308
|
+
// Clear per-target map since we re-processed everything
|
|
309
|
+
for (const key of Object.keys(pendingTransitionActionsMap)) {
|
|
310
|
+
delete pendingTransitionActionsMap[key];
|
|
311
|
+
}
|
|
312
|
+
context = ctx;
|
|
313
|
+
} else {
|
|
314
|
+
// Normal path: consume pending transition actions for THIS state.
|
|
315
|
+
// In parallel states, each target gets its own pending actions.
|
|
316
|
+
const transActions = stateId ? pendingTransitionActionsMap[stateId] : undefined;
|
|
317
|
+
if (transActions) {
|
|
318
|
+
delete pendingTransitionActionsMap[stateId];
|
|
319
|
+
const result = executeActions(transActions, x, enq);
|
|
320
|
+
if (result.context) {
|
|
321
|
+
context = result.context;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Clear stale targetless data from previous microsteps
|
|
325
|
+
contextBeforeTargetless = null;
|
|
326
|
+
allTransitionActions.length = 0;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Execute normal entry actions
|
|
330
|
+
if (originalEntryActions?.length) {
|
|
331
|
+
const mergedX = context ? {
|
|
332
|
+
...x,
|
|
333
|
+
context
|
|
334
|
+
} : x;
|
|
335
|
+
const result = executeActions(originalEntryActions, mergedX, enq);
|
|
336
|
+
if (result.context) {
|
|
337
|
+
context = result.context;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
context
|
|
342
|
+
};
|
|
343
|
+
};
|
|
344
|
+
const nodeConfig = {
|
|
345
|
+
id: node.id,
|
|
346
|
+
initial: node.initial,
|
|
347
|
+
type: node.type,
|
|
348
|
+
history: node.history,
|
|
349
|
+
target: node.target,
|
|
350
|
+
description: node.description,
|
|
351
|
+
tags: node.tags,
|
|
352
|
+
input: node.input,
|
|
353
|
+
timeout: node.timeout,
|
|
354
|
+
states: node.states ? Object.entries(node.states).reduce((acc, [key, value]) => {
|
|
355
|
+
acc[key] = iterNode(value, key);
|
|
356
|
+
return acc;
|
|
357
|
+
}, {}) : undefined,
|
|
358
|
+
on: node.on ? Object.entries(node.on).reduce((acc, [key, value]) => {
|
|
359
|
+
acc[key] = getTransitionConfig(value);
|
|
360
|
+
return acc;
|
|
361
|
+
}, {}) : undefined,
|
|
362
|
+
always: node.always ? getTransitionConfig(node.always) : undefined,
|
|
363
|
+
choice: node.choice ? getTransitionConfig(node.choice) : undefined,
|
|
364
|
+
route: node.route,
|
|
365
|
+
after: node.after ? Object.entries(node.after).reduce((acc, [key, value]) => {
|
|
366
|
+
acc[key] = getTransitionConfig(value);
|
|
367
|
+
return acc;
|
|
368
|
+
}, {}) : undefined,
|
|
369
|
+
onTimeout: node.onTimeout ? getTransitionConfig(node.onTimeout) : undefined,
|
|
370
|
+
entry: entryFn,
|
|
371
|
+
exit: node.exit ? iterActions(node.exit) : undefined,
|
|
372
|
+
invoke: node.invoke ? iterInvokeConfigs(node.invoke) : undefined,
|
|
373
|
+
meta: node.meta,
|
|
374
|
+
output: node._scxmlDonedata ? makeDonedataOutput(node._scxmlDonedata) : node.output
|
|
375
|
+
};
|
|
376
|
+
return nodeConfig;
|
|
377
|
+
}
|
|
378
|
+
function makeDonedataOutput(donedata) {
|
|
379
|
+
return ({
|
|
380
|
+
context,
|
|
381
|
+
event,
|
|
382
|
+
self
|
|
383
|
+
}) => {
|
|
384
|
+
try {
|
|
385
|
+
if (donedata.params && donedata.params.length) {
|
|
386
|
+
const out = {};
|
|
387
|
+
for (const param of donedata.params) {
|
|
388
|
+
out[param.name] = evaluateExpr(context, param.expr, event, self);
|
|
389
|
+
}
|
|
390
|
+
return out;
|
|
391
|
+
}
|
|
392
|
+
if (donedata.contentExpr !== undefined) {
|
|
393
|
+
return evaluateExpr(context, donedata.contentExpr, event, self);
|
|
394
|
+
}
|
|
395
|
+
if (donedata.contentText !== undefined) {
|
|
396
|
+
// Try parsing as a JS expression (number/object/array literals);
|
|
397
|
+
// fall back to the raw string.
|
|
398
|
+
try {
|
|
399
|
+
return evaluateExpr(context, donedata.contentText, event, self);
|
|
400
|
+
} catch {
|
|
401
|
+
return donedata.contentText;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
} catch (err) {
|
|
405
|
+
// Per SCXML, donedata expression errors raise error.execution and
|
|
406
|
+
// result in undefined event.data. Queue for the next entry to drain.
|
|
407
|
+
const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
|
|
408
|
+
pendingPlatformErrors.push({
|
|
409
|
+
tagname: 'donedata',
|
|
410
|
+
message,
|
|
411
|
+
line: NaN,
|
|
412
|
+
column: NaN,
|
|
413
|
+
reason: message
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return undefined;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
function iterInvokeConfigs(invokes) {
|
|
420
|
+
const invokeArray = Array.isArray(invokes) ? invokes : [invokes];
|
|
421
|
+
return invokeArray.map(inv => {
|
|
422
|
+
const extInv = inv;
|
|
423
|
+
// Create child machine from nested SCXML JSON
|
|
424
|
+
let src;
|
|
425
|
+
if (extInv._nestedMachineJSON) {
|
|
426
|
+
src = createMachineFromConfig(extInv._nestedMachineJSON, resolvedImplementations);
|
|
427
|
+
} else if (isUnserializableMarker(inv.src)) {
|
|
428
|
+
src = inv.src.id ? resolvedImplementations.actors[inv.src.id] : undefined;
|
|
429
|
+
if (!src) {
|
|
430
|
+
throw new Error(`Missing actors.${inv.src.id ?? ''}`);
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
src = inv.src;
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
src,
|
|
437
|
+
id: inv.id,
|
|
438
|
+
systemId: inv.systemId,
|
|
439
|
+
input: inv.input,
|
|
440
|
+
onDone: inv.onDone ? getTransitionConfig(inv.onDone) : undefined,
|
|
441
|
+
onError: inv.onError ? getTransitionConfig(inv.onError) : undefined,
|
|
442
|
+
onSnapshot: inv.onSnapshot ? getTransitionConfig(inv.onSnapshot) : undefined,
|
|
443
|
+
timeout: inv.timeout,
|
|
444
|
+
onTimeout: inv.onTimeout ? getTransitionConfig(inv.onTimeout) : undefined
|
|
445
|
+
};
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Raise an SCXML error.execution platform event onto the internal queue. Per
|
|
451
|
+
* spec, error.execution is type='platform' and _event.data contains error
|
|
452
|
+
* info. The surrounding block of executable content stops.
|
|
453
|
+
*/
|
|
454
|
+
function raiseErrorExecution(state, tagname, err) {
|
|
455
|
+
const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
|
|
456
|
+
state.enq.raise({
|
|
457
|
+
type: 'error.execution',
|
|
458
|
+
_scxmlEventType: 'platform',
|
|
459
|
+
_scxmlEventData: {
|
|
460
|
+
tagname,
|
|
461
|
+
message,
|
|
462
|
+
line: NaN,
|
|
463
|
+
column: NaN,
|
|
464
|
+
reason: message
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
state.errored = true;
|
|
468
|
+
}
|
|
469
|
+
/** Execute an array of SCXML action JSON descriptors with context and enqueue. */
|
|
470
|
+
function executeActions(actions, x, enq, parentState) {
|
|
471
|
+
const state = parentState ?? {
|
|
472
|
+
enq,
|
|
473
|
+
errored: false
|
|
474
|
+
};
|
|
475
|
+
let context;
|
|
476
|
+
for (const action of actions) {
|
|
477
|
+
if (state.errored) break;
|
|
478
|
+
if (isBuiltInActionJSON(action)) {
|
|
479
|
+
switch (action.type) {
|
|
480
|
+
case '@xstate.raise':
|
|
481
|
+
{
|
|
482
|
+
// Tag as external if it has a delay (from <send>, not <raise>).
|
|
483
|
+
// Attach _scxmlSendId so _event.sendid can be read in expressions.
|
|
484
|
+
const isExternal = action.delay !== undefined;
|
|
485
|
+
const event = isExternal || action.id !== undefined ? {
|
|
486
|
+
...action.event,
|
|
487
|
+
...(isExternal ? {
|
|
488
|
+
_scxmlExternal: true
|
|
489
|
+
} : {}),
|
|
490
|
+
...(action.id !== undefined ? {
|
|
491
|
+
_scxmlSendId: action.id
|
|
492
|
+
} : {})
|
|
493
|
+
} : action.event;
|
|
494
|
+
enq.raise(event, {
|
|
495
|
+
id: action.id,
|
|
496
|
+
delay: action.delay
|
|
497
|
+
});
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
case '@xstate.cancel':
|
|
501
|
+
enq.cancel(action.id);
|
|
502
|
+
break;
|
|
503
|
+
case '@xstate.log':
|
|
504
|
+
enq.log(...action.args);
|
|
505
|
+
break;
|
|
506
|
+
case '@xstate.emit':
|
|
507
|
+
enq.emit(action.event);
|
|
508
|
+
break;
|
|
509
|
+
case '@xstate.assign':
|
|
510
|
+
context ??= {};
|
|
511
|
+
Object.assign(context, action.context);
|
|
512
|
+
break;
|
|
513
|
+
default:
|
|
514
|
+
throw new Error(`Unknown built-in action: ${action.type}`);
|
|
515
|
+
}
|
|
516
|
+
} else if (action.type === 'scxml.assign') {
|
|
517
|
+
const scxmlAction = action;
|
|
518
|
+
const mergedContext = {
|
|
519
|
+
...x.context,
|
|
520
|
+
...context
|
|
521
|
+
};
|
|
522
|
+
let value;
|
|
523
|
+
try {
|
|
524
|
+
value = evaluateExpr(mergedContext, scxmlAction.expr, x.event, x.self);
|
|
525
|
+
} catch (err) {
|
|
526
|
+
raiseErrorExecution(state, 'assign', err);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
// Per SCXML, assigning to a location that doesn't exist on the
|
|
530
|
+
// datamodel is an error. Allow setting an own property via context;
|
|
531
|
+
// detect deep paths (a.b.c) which we don't support.
|
|
532
|
+
if (scxmlAction.location.includes('.')) {
|
|
533
|
+
raiseErrorExecution(state, 'assign', new Error(`Invalid assign location: ${scxmlAction.location}`));
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
context ??= {};
|
|
537
|
+
context[scxmlAction.location] = value;
|
|
538
|
+
} else if (action.type === 'scxml.raise') {
|
|
539
|
+
const scxmlAction = action;
|
|
540
|
+
const mergedContext = {
|
|
541
|
+
...x.context,
|
|
542
|
+
...context
|
|
543
|
+
};
|
|
544
|
+
let eventType;
|
|
545
|
+
let eventData;
|
|
546
|
+
let target;
|
|
547
|
+
let delay;
|
|
548
|
+
try {
|
|
549
|
+
eventType = scxmlAction.eventexpr ? evaluateExpr(mergedContext, scxmlAction.eventexpr, x.event, x.self) : scxmlAction.event || 'unknown';
|
|
550
|
+
eventData = {
|
|
551
|
+
type: eventType
|
|
552
|
+
};
|
|
553
|
+
if (scxmlAction.params) {
|
|
554
|
+
for (const param of scxmlAction.params) {
|
|
555
|
+
eventData[param.name] = evaluateExpr(mergedContext, param.expr, x.event, x.self);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
target = scxmlAction.targetexpr ? evaluateExpr(mergedContext, scxmlAction.targetexpr, x.event, x.self) : scxmlAction.target;
|
|
559
|
+
const isInternalTarget = target === '#_internal';
|
|
560
|
+
delay = scxmlAction.delayexpr ? delayToMs(evaluateExpr(mergedContext, scxmlAction.delayexpr, x.event, x.self)) : isInternalTarget ? undefined : scxmlAction.delay;
|
|
561
|
+
} catch (err) {
|
|
562
|
+
raiseErrorExecution(state, 'send', err);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const isInternalTarget = target === '#_internal';
|
|
566
|
+
const isParentTarget = target === '#_parent';
|
|
567
|
+
|
|
568
|
+
// Validate target. SCXML targets must be '#_internal', '#_parent',
|
|
569
|
+
// or '#_<id>'. A bare string without the '#_' prefix is invalid and
|
|
570
|
+
// raises error.execution.
|
|
571
|
+
if (typeof target === 'string' && target.length > 0 && !target.startsWith('#_')) {
|
|
572
|
+
raiseErrorExecution(state, 'send', new Error(`Invalid send target: ${target}`));
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
// Attach send id so _event.sendid can be read in expressions.
|
|
576
|
+
if (scxmlAction.id !== undefined) {
|
|
577
|
+
eventData._scxmlSendId = scxmlAction.id;
|
|
578
|
+
}
|
|
579
|
+
// Resolve target at runtime: parent, child, or self
|
|
580
|
+
if (isParentTarget && x.parent) {
|
|
581
|
+
// Send to parent via sendTo; pass delay if present
|
|
582
|
+
enq.sendTo(x.parent, eventData, delay !== undefined ? {
|
|
583
|
+
delay
|
|
584
|
+
} : undefined);
|
|
585
|
+
} else if (typeof target === 'string' && target.startsWith('#_') && !isParentTarget && !isInternalTarget) {
|
|
586
|
+
// #_<invokeId> → try to send to child actor, fall back to self-raise
|
|
587
|
+
const childId = target.slice(2); // strip '#_'
|
|
588
|
+
const childRef = x.children?.[childId];
|
|
589
|
+
if (childRef) {
|
|
590
|
+
enq.sendTo(childRef, eventData);
|
|
591
|
+
} else {
|
|
592
|
+
// Not a known child (e.g. #_scxml_sessionid) → self-raise
|
|
593
|
+
if (delay !== undefined) {
|
|
594
|
+
eventData._scxmlExternal = true;
|
|
595
|
+
}
|
|
596
|
+
enq.raise(eventData, {
|
|
597
|
+
id: scxmlAction.id,
|
|
598
|
+
delay
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
// Self-send or no special target: raise as external event
|
|
603
|
+
if (delay !== undefined) {
|
|
604
|
+
eventData._scxmlExternal = true;
|
|
605
|
+
}
|
|
606
|
+
enq.raise(eventData, {
|
|
607
|
+
id: scxmlAction.id,
|
|
608
|
+
delay
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
} else if (action.type === 'scxml.cancel') {
|
|
612
|
+
const scxmlAction = action;
|
|
613
|
+
const mergedContext = {
|
|
614
|
+
...x.context,
|
|
615
|
+
...context
|
|
616
|
+
};
|
|
617
|
+
try {
|
|
618
|
+
const sendId = evaluateExpr(mergedContext, scxmlAction.sendidexpr, x.event, x.self);
|
|
619
|
+
if (sendId) {
|
|
620
|
+
enq.cancel(sendId);
|
|
621
|
+
}
|
|
622
|
+
} catch {
|
|
623
|
+
// ignore evaluation errors
|
|
624
|
+
}
|
|
625
|
+
} else if (action.type === 'scxml.script') {
|
|
626
|
+
const scxmlAction = action;
|
|
627
|
+
const mergedContext = {
|
|
628
|
+
...x.context,
|
|
629
|
+
...context
|
|
630
|
+
};
|
|
631
|
+
try {
|
|
632
|
+
const updatedContext = executeScript(mergedContext, scxmlAction.code);
|
|
633
|
+
context ??= {};
|
|
634
|
+
Object.assign(context, updatedContext);
|
|
635
|
+
} catch (err) {
|
|
636
|
+
raiseErrorExecution(state, 'script', err);
|
|
637
|
+
}
|
|
638
|
+
} else if (action.type === 'scxml.foreach') {
|
|
639
|
+
const scxmlAction = action;
|
|
640
|
+
const mergedContext = {
|
|
641
|
+
...x.context,
|
|
642
|
+
...context
|
|
643
|
+
};
|
|
644
|
+
// SCXML: `item` (and `index` if present) must be valid datamodel
|
|
645
|
+
// locations — i.e. legal identifiers. Reject things like 'continue'
|
|
646
|
+
// (string literal) or '7' (number).
|
|
647
|
+
const isLegalIdentifier = s => /^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(s);
|
|
648
|
+
if (!isLegalIdentifier(scxmlAction.item) || scxmlAction.index && !isLegalIdentifier(scxmlAction.index)) {
|
|
649
|
+
raiseErrorExecution(state, 'foreach', new Error('foreach item/index is not a legal location'));
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
let arr;
|
|
653
|
+
try {
|
|
654
|
+
arr = evaluateExpr(mergedContext, scxmlAction.array, x.event, x.self);
|
|
655
|
+
} catch (err) {
|
|
656
|
+
raiseErrorExecution(state, 'foreach', err);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
if (!Array.isArray(arr)) {
|
|
660
|
+
raiseErrorExecution(state, 'foreach', new Error('foreach array is not iterable'));
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
context ??= {};
|
|
664
|
+
for (let i = 0; i < arr.length; i++) {
|
|
665
|
+
context[scxmlAction.item] = arr[i];
|
|
666
|
+
if (scxmlAction.index) {
|
|
667
|
+
context[scxmlAction.index] = i;
|
|
668
|
+
}
|
|
669
|
+
const iterX = {
|
|
670
|
+
...x,
|
|
671
|
+
context: {
|
|
672
|
+
...x.context,
|
|
673
|
+
...context
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
const iterResult = executeActions(scxmlAction.actions, iterX, enq, state);
|
|
677
|
+
if (iterResult.context) {
|
|
678
|
+
Object.assign(context, iterResult.context);
|
|
679
|
+
}
|
|
680
|
+
if (state.errored) break;
|
|
681
|
+
}
|
|
682
|
+
} else if (action.type === 'scxml.block') {
|
|
683
|
+
const scxmlAction = action;
|
|
684
|
+
const blockX = {
|
|
685
|
+
...x,
|
|
686
|
+
context: {
|
|
687
|
+
...x.context,
|
|
688
|
+
...context
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
// Block has its own ExecState — errors don't propagate to parent.
|
|
692
|
+
const blockResult = executeActions(scxmlAction.actions, blockX, enq);
|
|
693
|
+
if (blockResult.context) {
|
|
694
|
+
context ??= {};
|
|
695
|
+
for (const key of Object.keys(blockResult.context)) {
|
|
696
|
+
if (blockResult.context[key] !== x.context[key]) {
|
|
697
|
+
context[key] = blockResult.context[key];
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
} else if (action.type === 'scxml.if') {
|
|
702
|
+
const scxmlAction = action;
|
|
703
|
+
const mergedContext = {
|
|
704
|
+
...x.context,
|
|
705
|
+
...context
|
|
706
|
+
};
|
|
707
|
+
for (const branch of scxmlAction.branches) {
|
|
708
|
+
let condResult;
|
|
709
|
+
if (branch.cond) {
|
|
710
|
+
try {
|
|
711
|
+
condResult = !!evaluateExpr(mergedContext, branch.cond, x.event, x.self);
|
|
712
|
+
} catch (err) {
|
|
713
|
+
raiseErrorExecution(state, 'if', err);
|
|
714
|
+
condResult = false;
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
condResult = true;
|
|
718
|
+
}
|
|
719
|
+
if (condResult) {
|
|
720
|
+
if (branch.actions.length) {
|
|
721
|
+
const branchX = {
|
|
722
|
+
...x,
|
|
723
|
+
context: mergedContext
|
|
724
|
+
};
|
|
725
|
+
const branchResult = executeActions(branch.actions, branchX, enq, state);
|
|
726
|
+
if (branchResult.context) {
|
|
727
|
+
context ??= {};
|
|
728
|
+
for (const key of Object.keys(branchResult.context)) {
|
|
729
|
+
if (branchResult.context[key] !== x.context[key]) {
|
|
730
|
+
context[key] = branchResult.context[key];
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
break;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
} else {
|
|
739
|
+
enq(x.actions[action.type], action.params);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
context: context ? {
|
|
744
|
+
...x.context,
|
|
745
|
+
...context
|
|
746
|
+
} : undefined,
|
|
747
|
+
errored: state.errored
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
function iterActions(actions) {
|
|
751
|
+
return (x, enq) => executeActions(actions, x, enq);
|
|
752
|
+
}
|
|
753
|
+
function getTransitionConfig(transition) {
|
|
754
|
+
const transitions = Array.isArray(transition) ? transition : [transition];
|
|
755
|
+
|
|
756
|
+
// Return an array of transition configs. Each SCXML transition becomes
|
|
757
|
+
// a separate XState transition with its own guard and optional .to.
|
|
758
|
+
// This ensures guards are evaluated by XState's evaluateCandidate (once,
|
|
759
|
+
// with pre-exit context) and NOT re-evaluated in computeEntrySet.
|
|
760
|
+
return transitions.map(t => {
|
|
761
|
+
const target = Array.isArray(t.target) ? t.target[0] : t.target;
|
|
762
|
+
const targetConfig = t.target;
|
|
763
|
+
|
|
764
|
+
// No guard and no actions: simple static config
|
|
765
|
+
if (!t.guard && !t.actions?.length) {
|
|
766
|
+
return {
|
|
767
|
+
target: targetConfig,
|
|
768
|
+
description: t.description,
|
|
769
|
+
reenter: t.reenter,
|
|
770
|
+
meta: t.meta,
|
|
771
|
+
input: t.input
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Guard but no actions: static config with guard
|
|
776
|
+
if (t.guard && !t.actions?.length) {
|
|
777
|
+
return {
|
|
778
|
+
target: targetConfig,
|
|
779
|
+
guard: t.guard,
|
|
780
|
+
description: t.description,
|
|
781
|
+
reenter: t.reenter,
|
|
782
|
+
meta: t.meta,
|
|
783
|
+
input: t.input
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Targetless transitions: execute actions directly in .to AND track for
|
|
788
|
+
// parallel re-execution. For non-parallel, .to result is used directly.
|
|
789
|
+
// For parallel (entries exist), first entry re-executes all in document order.
|
|
790
|
+
if (!target) {
|
|
791
|
+
return {
|
|
792
|
+
guard: t.guard,
|
|
793
|
+
description: t.description,
|
|
794
|
+
reenter: t.reenter,
|
|
795
|
+
meta: t.meta,
|
|
796
|
+
input: t.input,
|
|
797
|
+
to: (x, enq) => {
|
|
798
|
+
if (t.actions?.length) {
|
|
799
|
+
// Track for parallel re-execution (dedup by reference)
|
|
800
|
+
if (!allTransitionActions.includes(t.actions)) {
|
|
801
|
+
allTransitionActions.push(t.actions);
|
|
802
|
+
}
|
|
803
|
+
// Save pre-transition context for parallel override
|
|
804
|
+
contextBeforeTargetless ??= x.context;
|
|
805
|
+
// Execute immediately (fallback for non-parallel case)
|
|
806
|
+
const result = executeActions(t.actions, x, enq);
|
|
807
|
+
if (result.context) {
|
|
808
|
+
return {
|
|
809
|
+
context: result.context
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return {};
|
|
814
|
+
}
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// Has target + actions: use guard (for XState evaluation) + .to (for pending actions).
|
|
819
|
+
// The .to function does NOT re-check the guard — XState's evaluateCandidate
|
|
820
|
+
// already validated it. The .to just stores actions for entry to execute.
|
|
821
|
+
// Map by target state ID so parallel transitions each get their own actions.
|
|
822
|
+
return {
|
|
823
|
+
guard: t.guard,
|
|
824
|
+
description: t.description,
|
|
825
|
+
reenter: t.reenter,
|
|
826
|
+
meta: t.meta,
|
|
827
|
+
input: t.input,
|
|
828
|
+
to: (_x, _enq) => {
|
|
829
|
+
if (t.actions?.length) {
|
|
830
|
+
const targetId = target.replace(/^#/, '');
|
|
831
|
+
pendingTransitionActionsMap[targetId] = t.actions;
|
|
832
|
+
// Track for parallel re-execution (dedup by reference)
|
|
833
|
+
if (!allTransitionActions.includes(t.actions)) {
|
|
834
|
+
allTransitionActions.push(t.actions);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return {
|
|
838
|
+
target: targetConfig,
|
|
839
|
+
reenter: t.reenter
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
const rootNodeConfig = iterNode(json);
|
|
846
|
+
const contextConfig = json.context ? {
|
|
847
|
+
context: json.context
|
|
848
|
+
} : {};
|
|
849
|
+
_scxmlMachineName = json.id || '';
|
|
850
|
+
_scxmlSessionId = 'session_' + Math.random().toString(36).slice(2);
|
|
851
|
+
const machine = createMachine({
|
|
852
|
+
...rootNodeConfig,
|
|
853
|
+
...contextConfig,
|
|
854
|
+
version: json.version
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
// Register SCXML guard implementations
|
|
858
|
+
const providedGuards = {
|
|
859
|
+
'scxml.cond': ({
|
|
860
|
+
context,
|
|
861
|
+
event,
|
|
862
|
+
self
|
|
863
|
+
}, params) => {
|
|
864
|
+
const expr = params?.expr;
|
|
865
|
+
if (!expr) return true;
|
|
866
|
+
try {
|
|
867
|
+
return !!evaluateExpr(context, expr, event, self);
|
|
868
|
+
} catch (err) {
|
|
869
|
+
// Per SCXML spec, a cond that fails to evaluate is treated as false
|
|
870
|
+
// AND raises error.execution. We can't enqueue from a guard, so
|
|
871
|
+
// queue it for the next entry to drain.
|
|
872
|
+
const message = err instanceof Error ? err.message : typeof err === 'string' ? err : 'unknown error';
|
|
873
|
+
pendingPlatformErrors.push({
|
|
874
|
+
tagname: 'cond',
|
|
875
|
+
message,
|
|
876
|
+
line: NaN,
|
|
877
|
+
column: NaN,
|
|
878
|
+
reason: message
|
|
879
|
+
});
|
|
880
|
+
return false;
|
|
881
|
+
}
|
|
882
|
+
},
|
|
883
|
+
'xstate.stateIn': (args, params) => {
|
|
884
|
+
const stateId = params?.stateId;
|
|
885
|
+
if (!stateId) return false;
|
|
886
|
+
const normalizedId = stateId.replace(/^#/, '');
|
|
887
|
+
const snapshot = args._snapshot;
|
|
888
|
+
if (snapshot?._nodes) {
|
|
889
|
+
return snapshot._nodes.some(node => node.id === normalizedId);
|
|
890
|
+
}
|
|
891
|
+
return false;
|
|
892
|
+
},
|
|
893
|
+
'xstate.not': (args, params) => {
|
|
894
|
+
const inner = params?.guard;
|
|
895
|
+
const innerImpl = inner && providedGuards[inner.type];
|
|
896
|
+
if (!innerImpl) {
|
|
897
|
+
throw new Error(`Guard '${inner?.type}' referenced by 'xstate.not' is not implemented.`);
|
|
898
|
+
}
|
|
899
|
+
return !innerImpl(args, inner.params);
|
|
900
|
+
}
|
|
901
|
+
};
|
|
902
|
+
const provided = machine.provide({
|
|
903
|
+
actions: resolvedImplementations.actions,
|
|
904
|
+
actors: resolvedImplementations.actors,
|
|
905
|
+
guards: {
|
|
906
|
+
...providedGuards,
|
|
907
|
+
...resolvedImplementations.guards
|
|
908
|
+
},
|
|
909
|
+
delays: resolvedImplementations.delays
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
// Keep the original JSON so `serializeMachine(machine)`
|
|
913
|
+
// round-trip losslessly.
|
|
914
|
+
provided._json = json;
|
|
915
|
+
return provided;
|
|
916
|
+
}
|
|
917
|
+
function isBuiltInActionJSON(action) {
|
|
918
|
+
return action.type.startsWith('@xstate.');
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Machine-definition serialization.
|
|
923
|
+
*
|
|
924
|
+
* `machineConfigToJSON` converts a machine config into a JSON-safe structure
|
|
925
|
+
* (the `MachineJSON` shape accepted by `createMachineFromConfig`). The boundary
|
|
926
|
+
* between serializable structure and runtime implementations is explicit: any
|
|
927
|
+
* value that cannot be represented as data (inline functions, actor logic,
|
|
928
|
+
* runtime schemas) is replaced with an `{ "$unserializable": <kind> }` marker
|
|
929
|
+
* rather than silently dropped.
|
|
930
|
+
*
|
|
931
|
+
* A machine definition is fully portable iff its JSON contains no
|
|
932
|
+
* `$unserializable` markers; reviving a definition that has markers requires
|
|
933
|
+
* re-providing those implementations (e.g. via `machine.provide(...)` or the
|
|
934
|
+
* `actions`/`guards`/`actors` maps on `createMachineFromConfig` revival).
|
|
935
|
+
*/
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* Returns the JSON-serializable definition of a machine.
|
|
939
|
+
*
|
|
940
|
+
* Inline functions, actor logic, and runtime schemas cannot be represented as
|
|
941
|
+
* data; they appear as `{ "$unserializable": ... }` markers. A machine created
|
|
942
|
+
* via `createMachineFromConfig` returns its original JSON config (lossless
|
|
943
|
+
* round-trip):
|
|
944
|
+
*
|
|
945
|
+
* ```ts
|
|
946
|
+
* import { serializeMachine, createMachineFromConfig } from 'xstate';
|
|
947
|
+
*
|
|
948
|
+
* const json = serializeMachine(machine);
|
|
949
|
+
* const revived = createMachineFromConfig(
|
|
950
|
+
* JSON.parse(JSON.stringify(json))
|
|
951
|
+
* );
|
|
952
|
+
* ```
|
|
953
|
+
*/
|
|
954
|
+
function serializeMachine(machine) {
|
|
955
|
+
return machine._json ?? machineConfigToJSON(machine.config);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* State-node config keys that are part of the serializable definition. Keys
|
|
960
|
+
* outside this list (unknown/internal properties) are dropped.
|
|
961
|
+
*/
|
|
962
|
+
const STATE_NODE_KEYS = ['id', 'key', 'type', 'history', 'description', 'target', 'version', 'initial', 'context', 'on', 'after', 'always', 'choice', 'route', 'onTimeout', 'timeout', 'entry', 'exit', 'meta', 'output', 'tags', 'input', '_scxmlDonedata'];
|
|
963
|
+
function marker(kind, id) {
|
|
964
|
+
const m = {
|
|
965
|
+
$unserializable: kind
|
|
966
|
+
};
|
|
967
|
+
if (id) {
|
|
968
|
+
m.id = id;
|
|
969
|
+
}
|
|
970
|
+
return m;
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/** JSON-safe deep copy of a plain value; functions become markers. */
|
|
974
|
+
function valueToJSON(value) {
|
|
975
|
+
if (typeof value === 'function') {
|
|
976
|
+
return marker('function', value.name || undefined);
|
|
977
|
+
}
|
|
978
|
+
if (value === null || typeof value !== 'object') {
|
|
979
|
+
return typeof value === 'bigint' || typeof value === 'symbol' ? marker('value') : value;
|
|
980
|
+
}
|
|
981
|
+
if (Array.isArray(value)) {
|
|
982
|
+
return value.map(valueToJSON);
|
|
983
|
+
}
|
|
984
|
+
if (value.constructor !== Object && value.constructor !== undefined) {
|
|
985
|
+
// Class instances (actor logic, schemas, dates, ...) are not portable.
|
|
986
|
+
return marker('value');
|
|
987
|
+
}
|
|
988
|
+
const result = {};
|
|
989
|
+
for (const key of Object.keys(value)) {
|
|
990
|
+
const v = value[key];
|
|
991
|
+
if (v !== undefined) {
|
|
992
|
+
result[key] = valueToJSON(v);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return result;
|
|
996
|
+
}
|
|
997
|
+
function invokeToJSON(invoke) {
|
|
998
|
+
if (Array.isArray(invoke)) {
|
|
999
|
+
return invoke.map(invokeToJSON);
|
|
1000
|
+
}
|
|
1001
|
+
const def = invoke;
|
|
1002
|
+
const result = {};
|
|
1003
|
+
for (const key of Object.keys(def)) {
|
|
1004
|
+
const value = def[key];
|
|
1005
|
+
if (value === undefined) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
result[key] = key === 'src' && typeof value !== 'string' ?
|
|
1009
|
+
// Actor logic keeps its dedicated marker kind so revival knows the
|
|
1010
|
+
// contract is an actor, not a plain function.
|
|
1011
|
+
marker('actor', value.id) : valueToJSON(value);
|
|
1012
|
+
}
|
|
1013
|
+
return result;
|
|
1014
|
+
}
|
|
1015
|
+
function implementationsToJSON(map, kind) {
|
|
1016
|
+
if (!map) {
|
|
1017
|
+
return undefined;
|
|
1018
|
+
}
|
|
1019
|
+
// Keys are preserved — they are the contract a revived machine must
|
|
1020
|
+
// fulfill via provide() — while values become markers (or stay, for
|
|
1021
|
+
// JSON-safe values like numeric delays).
|
|
1022
|
+
const result = {};
|
|
1023
|
+
for (const key of Object.keys(map)) {
|
|
1024
|
+
const value = map[key];
|
|
1025
|
+
result[key] = typeof value === 'function' ? marker('function', key) : value && typeof value === 'object' ? marker(kind, key) : value;
|
|
1026
|
+
}
|
|
1027
|
+
return result;
|
|
1028
|
+
}
|
|
1029
|
+
function stateNodeConfigToJSON(config) {
|
|
1030
|
+
const result = {};
|
|
1031
|
+
for (const key of STATE_NODE_KEYS) {
|
|
1032
|
+
const value = config[key];
|
|
1033
|
+
if (value !== undefined) {
|
|
1034
|
+
result[key] = valueToJSON(value);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (config.invoke !== undefined) {
|
|
1038
|
+
result.invoke = invokeToJSON(config.invoke);
|
|
1039
|
+
}
|
|
1040
|
+
if (config.states) {
|
|
1041
|
+
const states = {};
|
|
1042
|
+
for (const key of Object.keys(config.states)) {
|
|
1043
|
+
states[key] = stateNodeConfigToJSON(config.states[key]);
|
|
1044
|
+
}
|
|
1045
|
+
result.states = states;
|
|
1046
|
+
}
|
|
1047
|
+
return result;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Converts a machine config (as passed to `createMachine`) to its JSON-safe
|
|
1052
|
+
* definition. See module docs for the `$unserializable` marker contract.
|
|
1053
|
+
*/
|
|
1054
|
+
function machineConfigToJSON(config) {
|
|
1055
|
+
const result = stateNodeConfigToJSON(config);
|
|
1056
|
+
if (config.internalEvents !== undefined) {
|
|
1057
|
+
result.internalEvents = valueToJSON(config.internalEvents);
|
|
1058
|
+
}
|
|
1059
|
+
if (config.schemas) {
|
|
1060
|
+
const schemas = {};
|
|
1061
|
+
for (const key of Object.keys(config.schemas)) {
|
|
1062
|
+
const value = config.schemas[key];
|
|
1063
|
+
if (value && typeof value === 'object' && !('~standard' in value)) {
|
|
1064
|
+
// Map-form schemas (events/emitted): preserve event-type keys.
|
|
1065
|
+
schemas[key] = implementationsToJSON(value, 'schema');
|
|
1066
|
+
} else {
|
|
1067
|
+
schemas[key] = marker('schema', key);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
result.schemas = schemas;
|
|
1071
|
+
}
|
|
1072
|
+
for (const key of ['actions', 'guards', 'actors', 'delays']) {
|
|
1073
|
+
if (config[key]) {
|
|
1074
|
+
result[key] = implementationsToJSON(config[key], key === 'actors' ? 'actor' : 'function');
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return result;
|
|
88
1078
|
}
|
|
89
1079
|
|
|
90
1080
|
/**
|
|
@@ -133,11 +1123,141 @@ function mapState(snapshot, mapper) {
|
|
|
133
1123
|
return results;
|
|
134
1124
|
}
|
|
135
1125
|
|
|
1126
|
+
/** The Standard Schema interface. */
|
|
1127
|
+
|
|
1128
|
+
/** A type-only Standard Schema produced by {@link types}. */
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* Declares schema types for inference only — no runtime validation.
|
|
1132
|
+
*
|
|
1133
|
+
* Use in a machine's `schemas` when you want TypeScript types without pulling
|
|
1134
|
+
* in a schema library like Zod. The returned value is a valid Standard Schema
|
|
1135
|
+
* whose validation is the identity function, so it carries types but never
|
|
1136
|
+
* rejects input.
|
|
1137
|
+
*
|
|
1138
|
+
* @example
|
|
1139
|
+
*
|
|
1140
|
+
* ```ts
|
|
1141
|
+
* import { createMachine, types } from 'xstate';
|
|
1142
|
+
*
|
|
1143
|
+
* const machine = createMachine({
|
|
1144
|
+
* schemas: {
|
|
1145
|
+
* context: types<{ count: number }>(),
|
|
1146
|
+
* events: {
|
|
1147
|
+
* inc: types<{ by: number }>(),
|
|
1148
|
+
* reset: types<{}>()
|
|
1149
|
+
* }
|
|
1150
|
+
* },
|
|
1151
|
+
* context: { count: 0 }
|
|
1152
|
+
* // ...
|
|
1153
|
+
* });
|
|
1154
|
+
* ```
|
|
1155
|
+
*/
|
|
1156
|
+
function types() {
|
|
1157
|
+
return {
|
|
1158
|
+
'~standard': {
|
|
1159
|
+
version: 1,
|
|
1160
|
+
vendor: 'xstate.types',
|
|
1161
|
+
validate: value => ({
|
|
1162
|
+
value: value
|
|
1163
|
+
})
|
|
1164
|
+
}
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/** Returns true if the value is a type-only schema created by {@link types}. */
|
|
1169
|
+
function isTypeSchema(value) {
|
|
1170
|
+
return !!value && typeof value === 'object' && '~standard' in value && value['~standard'].vendor === 'xstate.types';
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// eslint-disable-next-line @typescript-eslint/no-namespace
|
|
1174
|
+
|
|
1175
|
+
/** State schema with optional schemas.input and nested states */
|
|
1176
|
+
|
|
1177
|
+
/** Extracts input type from a state schema */
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Converts SetupStateSchema to StateSchema with input types included. This
|
|
1181
|
+
* allows getInputs() to be strongly typed.
|
|
1182
|
+
*/
|
|
1183
|
+
|
|
1184
|
+
/** Converts the root setup states config to a StateSchema. */
|
|
1185
|
+
|
|
1186
|
+
/** Machine config with typed state input */
|
|
1187
|
+
|
|
1188
|
+
/** States config type that provides typed input for known states */
|
|
1189
|
+
|
|
1190
|
+
/** State node config that recursively applies typed input for nested states */
|
|
1191
|
+
|
|
1192
|
+
/** Initial transition with typed input based on target state */
|
|
1193
|
+
|
|
1194
|
+
/** Return type of setup() */
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* Sets up a state machine with state input schemas and other configuration.
|
|
1198
|
+
*
|
|
1199
|
+
* @example
|
|
1200
|
+
*
|
|
1201
|
+
* ```ts
|
|
1202
|
+
* import { setup } from 'xstate';
|
|
1203
|
+
* import z from 'zod';
|
|
1204
|
+
*
|
|
1205
|
+
* const s = setup({
|
|
1206
|
+
* states: {
|
|
1207
|
+
* loading: {
|
|
1208
|
+
* schemas: {
|
|
1209
|
+
* input: z.object({
|
|
1210
|
+
* userId: z.string()
|
|
1211
|
+
* })
|
|
1212
|
+
* }
|
|
1213
|
+
* }
|
|
1214
|
+
* }
|
|
1215
|
+
* });
|
|
1216
|
+
*
|
|
1217
|
+
* const machine = s.createMachine({
|
|
1218
|
+
* initial: {
|
|
1219
|
+
* target: 'loading',
|
|
1220
|
+
* input: { userId: '123' }
|
|
1221
|
+
* },
|
|
1222
|
+
* states: {
|
|
1223
|
+
* loading: {
|
|
1224
|
+
* entry: ({ input }) => {
|
|
1225
|
+
* console.log(input.userId);
|
|
1226
|
+
* }
|
|
1227
|
+
* }
|
|
1228
|
+
* }
|
|
1229
|
+
* });
|
|
1230
|
+
* ```
|
|
1231
|
+
*/
|
|
1232
|
+
function setup(config = {}) {
|
|
1233
|
+
const {
|
|
1234
|
+
states = {},
|
|
1235
|
+
schemas
|
|
1236
|
+
} = config;
|
|
1237
|
+
return {
|
|
1238
|
+
createMachine(machineConfig) {
|
|
1239
|
+
const configSchemas = machineConfig.schemas;
|
|
1240
|
+
const mergedSchemas = schemas || configSchemas ? {
|
|
1241
|
+
...configSchemas,
|
|
1242
|
+
...schemas
|
|
1243
|
+
} : undefined;
|
|
1244
|
+
return new StateMachine(mergedSchemas ? {
|
|
1245
|
+
...machineConfig,
|
|
1246
|
+
schemas: mergedSchemas
|
|
1247
|
+
} : machineConfig);
|
|
1248
|
+
},
|
|
1249
|
+
createStateConfig(stateConfig) {
|
|
1250
|
+
return stateConfig;
|
|
1251
|
+
},
|
|
1252
|
+
states
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
|
|
136
1256
|
/** @internal */
|
|
137
1257
|
function createInertActorScope(actorLogic) {
|
|
138
1258
|
const self = createActor(actorLogic);
|
|
139
1259
|
const inertActorScope = {
|
|
140
|
-
self,
|
|
1260
|
+
self: self,
|
|
141
1261
|
defer: () => {},
|
|
142
1262
|
id: '',
|
|
143
1263
|
logger: () => {},
|
|
@@ -192,60 +1312,8 @@ function getInitialSnapshot(actorLogic, ...[input]) {
|
|
|
192
1312
|
function getNextSnapshot(actorLogic, snapshot, event) {
|
|
193
1313
|
const inertActorScope = createInertActorScope(actorLogic);
|
|
194
1314
|
inertActorScope.self._snapshot = snapshot;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// at the moment we allow extra actors - ones that are not specified by `children`
|
|
199
|
-
// this could be reconsidered in the future
|
|
200
|
-
|
|
201
|
-
// used to keep only StateSchema relevant keys
|
|
202
|
-
// this helps with type serialization as it makes the inferred type much shorter when dealing with huge configs
|
|
203
|
-
|
|
204
|
-
function setup({
|
|
205
|
-
schemas,
|
|
206
|
-
actors,
|
|
207
|
-
actions,
|
|
208
|
-
guards,
|
|
209
|
-
delays
|
|
210
|
-
}) {
|
|
211
|
-
return {
|
|
212
|
-
assign,
|
|
213
|
-
sendTo,
|
|
214
|
-
raise,
|
|
215
|
-
log,
|
|
216
|
-
cancel,
|
|
217
|
-
stopChild,
|
|
218
|
-
enqueueActions,
|
|
219
|
-
emit,
|
|
220
|
-
spawnChild,
|
|
221
|
-
createStateConfig: config => config,
|
|
222
|
-
createAction: fn => fn,
|
|
223
|
-
createMachine: config => createMachine({
|
|
224
|
-
...config,
|
|
225
|
-
schemas
|
|
226
|
-
}, {
|
|
227
|
-
actors,
|
|
228
|
-
actions,
|
|
229
|
-
guards,
|
|
230
|
-
delays
|
|
231
|
-
}),
|
|
232
|
-
extend: extended => setup({
|
|
233
|
-
schemas,
|
|
234
|
-
actors,
|
|
235
|
-
actions: {
|
|
236
|
-
...actions,
|
|
237
|
-
...extended.actions
|
|
238
|
-
},
|
|
239
|
-
guards: {
|
|
240
|
-
...guards,
|
|
241
|
-
...extended.guards
|
|
242
|
-
},
|
|
243
|
-
delays: {
|
|
244
|
-
...delays,
|
|
245
|
-
...extended.delays
|
|
246
|
-
}
|
|
247
|
-
})
|
|
248
|
-
};
|
|
1315
|
+
const transitionResult = actorLogic.transition(snapshot, event, inertActorScope);
|
|
1316
|
+
return Array.isArray(transitionResult) ? transitionResult[0] : transitionResult;
|
|
249
1317
|
}
|
|
250
1318
|
|
|
251
1319
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging
|
|
@@ -310,6 +1378,11 @@ class SimulatedClock {
|
|
|
310
1378
|
}
|
|
311
1379
|
}
|
|
312
1380
|
this._flushing = false;
|
|
1381
|
+
// Check if new timeouts were added during the last iteration
|
|
1382
|
+
if (this._flushingInvalidated) {
|
|
1383
|
+
this._flushingInvalidated = false;
|
|
1384
|
+
this.flushTimeouts();
|
|
1385
|
+
}
|
|
313
1386
|
}
|
|
314
1387
|
increment(ms) {
|
|
315
1388
|
this._now += ms;
|
|
@@ -351,6 +1424,138 @@ function toPromise(actor) {
|
|
|
351
1424
|
});
|
|
352
1425
|
}
|
|
353
1426
|
|
|
1427
|
+
// this is needed to make JSDoc `@link` work properly
|
|
1428
|
+
|
|
1429
|
+
/**
|
|
1430
|
+
* @remarks
|
|
1431
|
+
* `T | unknown` reduces to `unknown` and that can be problematic when it comes
|
|
1432
|
+
* to contextual typing. It especially is a problem when the union has a
|
|
1433
|
+
* function member, like here:
|
|
1434
|
+
*
|
|
1435
|
+
* ```ts
|
|
1436
|
+
* declare function test(
|
|
1437
|
+
* cbOrVal: ((arg: number) => unknown) | unknown
|
|
1438
|
+
* ): void;
|
|
1439
|
+
* test((arg) => {}); // oops, implicit any
|
|
1440
|
+
* ```
|
|
1441
|
+
*
|
|
1442
|
+
* This type can be used to avoid this problem. This union represents the same
|
|
1443
|
+
* value space as `unknown`.
|
|
1444
|
+
*/
|
|
1445
|
+
|
|
1446
|
+
// https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887
|
|
1447
|
+
|
|
1448
|
+
// @TODO: we can't use native `NoInfer` as we need those:
|
|
1449
|
+
// https://github.com/microsoft/TypeScript/pull/61092
|
|
1450
|
+
// https://github.com/microsoft/TypeScript/pull/61077
|
|
1451
|
+
// but even with those fixes native NoInfer still doesn't work - further issues have to be reproduced and fixed
|
|
1452
|
+
|
|
1453
|
+
/** @deprecated Use the built-in `NoInfer` type instead */
|
|
1454
|
+
|
|
1455
|
+
/** The full definition of an event, with a string `type`. */
|
|
1456
|
+
|
|
1457
|
+
/**
|
|
1458
|
+
* The string or object representing the state value relative to the parent
|
|
1459
|
+
* state node.
|
|
1460
|
+
*
|
|
1461
|
+
* @remarks
|
|
1462
|
+
* - For a child atomic state node, this is a string, e.g., `"pending"`.
|
|
1463
|
+
* - For complex state nodes, this is an object, e.g., `{ success:
|
|
1464
|
+
* "someChildState" }`.
|
|
1465
|
+
*/
|
|
1466
|
+
|
|
1467
|
+
// Accept any StateNode instance regardless of generic parameters
|
|
1468
|
+
// Using a union type to handle variance issues with machine.resolveState
|
|
1469
|
+
|
|
1470
|
+
let SpecialTargets = /*#__PURE__*/function (SpecialTargets) {
|
|
1471
|
+
SpecialTargets["Internal"] = "#_internal";
|
|
1472
|
+
return SpecialTargets;
|
|
1473
|
+
}({});
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* A pending effect (delayed event/transition that has not fired yet) captured
|
|
1477
|
+
* in a persisted snapshot — a serialized built-in action descriptor plus its
|
|
1478
|
+
* runtime progress, with enough information to restore it under any strategy.
|
|
1479
|
+
*/
|
|
1480
|
+
|
|
1481
|
+
/**
|
|
1482
|
+
* How pending effects (timers) from a persisted snapshot are restored on
|
|
1483
|
+
* `actor.start()`:
|
|
1484
|
+
*
|
|
1485
|
+
* - `'resume'` (default) — the clock was "frozen" while persisted: a 5-minute
|
|
1486
|
+
* timer persisted with 1 minute elapsed fires after 4 more minutes.
|
|
1487
|
+
* - `'restart'` — timers start over with their full delay.
|
|
1488
|
+
* - `'absolute'` — timers honor the original wall-clock expiry (`startedAt +
|
|
1489
|
+
* delay`); already-expired timers fire immediately.
|
|
1490
|
+
* - A custom function receiving the pending effect and returning the remaining
|
|
1491
|
+
* delay in ms.
|
|
1492
|
+
*/
|
|
1493
|
+
|
|
1494
|
+
/** @deprecated Use `AnyActor` instead. */
|
|
1495
|
+
|
|
1496
|
+
// Based on RxJS types
|
|
1497
|
+
|
|
1498
|
+
/**
|
|
1499
|
+
* A consumer-facing actor handle.
|
|
1500
|
+
*
|
|
1501
|
+
* `ActorRef` describes the contract a consumer needs: which events can be sent
|
|
1502
|
+
* to the actor, which snapshot it publishes for observation, and which emitted
|
|
1503
|
+
* events can be listened to. It intentionally does not expose lifecycle control
|
|
1504
|
+
* or runtime internals. A concrete `Actor` satisfies this interface, so APIs
|
|
1505
|
+
* should accept `ActorRef` whenever they only need to send events, read
|
|
1506
|
+
* snapshots, or listen to emitted events.
|
|
1507
|
+
*/
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* Runtime-only actor capabilities.
|
|
1511
|
+
*
|
|
1512
|
+
* These members are needed by the interpreter, scheduler, inspection, and child
|
|
1513
|
+
* management code. Public consumer APIs should prefer `ActorRef` unless they
|
|
1514
|
+
* genuinely need lifecycle control such as starting, stopping, or accessing
|
|
1515
|
+
* system-owned runtime state.
|
|
1516
|
+
*/
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* A concrete actor instance type.
|
|
1520
|
+
*
|
|
1521
|
+
* `ActorInstance` combines the consumer `ActorRef` contract with runtime
|
|
1522
|
+
* lifecycle capabilities. Values returned by `createActor(...)` and
|
|
1523
|
+
* `spawn(...)` naturally satisfy narrower `ActorRef` contracts.
|
|
1524
|
+
*/
|
|
1525
|
+
|
|
1526
|
+
/**
|
|
1527
|
+
* The actor's own full self handle.
|
|
1528
|
+
*
|
|
1529
|
+
* Internals and action/guard implementations receive this shape because `self`
|
|
1530
|
+
* can participate in runtime-owned behavior while still being usable wherever
|
|
1531
|
+
* an `ActorRef` is expected.
|
|
1532
|
+
*/
|
|
1533
|
+
|
|
1534
|
+
// TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
|
|
1535
|
+
|
|
1536
|
+
/** The concrete actor instance type produced from actor logic. */
|
|
1537
|
+
|
|
1538
|
+
// TODO: in v6, this should only accept AnyActorLogic, like ActorRefFromLogic
|
|
1539
|
+
|
|
1540
|
+
/**
|
|
1541
|
+
* Represents logic which can be used by an actor.
|
|
1542
|
+
*
|
|
1543
|
+
* @template TSnapshot - The type of the snapshot.
|
|
1544
|
+
* @template TEvent - The type of the event object.
|
|
1545
|
+
* @template TInput - The type of the input.
|
|
1546
|
+
* @template TSystem - The type of the actor system.
|
|
1547
|
+
*/
|
|
1548
|
+
|
|
1549
|
+
// Actors that don't have literal string IDs — these are the only ones
|
|
1550
|
+
// that should appear in the index signature fallback, since actors with
|
|
1551
|
+
// literal IDs are already covered by ToConcreteChildren.
|
|
1552
|
+
|
|
1553
|
+
/** Maps state IDs to their input types based on the StateSchema. */
|
|
1554
|
+
|
|
1555
|
+
/** @deprecated */
|
|
1556
|
+
|
|
1557
|
+
/** Mappers for subscribeTo - maps lifecycle events to machine events */
|
|
1558
|
+
|
|
354
1559
|
/**
|
|
355
1560
|
* Given actor `logic`, a `snapshot`, and an `event`, returns a tuple of the
|
|
356
1561
|
* `nextSnapshot` and `actions` to execute.
|
|
@@ -363,8 +1568,9 @@ function transition(logic, snapshot, event) {
|
|
|
363
1568
|
actorScope.actionExecutor = action => {
|
|
364
1569
|
executableActions.push(action);
|
|
365
1570
|
};
|
|
366
|
-
const
|
|
367
|
-
|
|
1571
|
+
const transitionResult = logic.transition(snapshot, event, actorScope);
|
|
1572
|
+
const [nextSnapshot, effects] = Array.isArray(transitionResult) ? transitionResult : [transitionResult, []];
|
|
1573
|
+
return [nextSnapshot, executableActions.concat(effects)];
|
|
368
1574
|
}
|
|
369
1575
|
|
|
370
1576
|
/**
|
|
@@ -409,7 +1615,7 @@ function getInitialMicrosteps(machine, ...[input]) {
|
|
|
409
1615
|
const actorScope = createInertActorScope(machine);
|
|
410
1616
|
const initEvent = createInitEvent(input);
|
|
411
1617
|
const internalQueue = [];
|
|
412
|
-
const preInitialSnapshot = machine._getPreInitialState(actorScope, initEvent
|
|
1618
|
+
const preInitialSnapshot = machine._getPreInitialState(actorScope, initEvent);
|
|
413
1619
|
const first = initialMicrostep(machine.root, preInitialSnapshot, actorScope, initEvent, internalQueue);
|
|
414
1620
|
const {
|
|
415
1621
|
microsteps
|
|
@@ -460,7 +1666,7 @@ function getNextTransitions(state) {
|
|
|
460
1666
|
// Get all transitions for each event type
|
|
461
1667
|
// Include ALL transitions, even if the same event type appears in multiple state nodes
|
|
462
1668
|
// This is important for guarded transitions - all are "potential" regardless of guard evaluation
|
|
463
|
-
for (const [, transitions] of s.transitions) {
|
|
1669
|
+
for (const [, transitions] of s.transitions.entries()) {
|
|
464
1670
|
potentialTransitions.push(...transitions);
|
|
465
1671
|
}
|
|
466
1672
|
|
|
@@ -577,4 +1783,4 @@ function waitFor(actorRef, predicate, options) {
|
|
|
577
1783
|
});
|
|
578
1784
|
}
|
|
579
1785
|
|
|
580
|
-
export { SimulatedClock, assertEvent, createMachine, getInitialMicrosteps, getInitialSnapshot, getMicrosteps, getNextSnapshot, getNextTransitions, initialTransition, mapState, setup, toPromise, transition, waitFor };
|
|
1786
|
+
export { SimulatedClock, SpecialTargets, assertEvent, createMachine, createMachineFromConfig, createStateConfig, getInitialMicrosteps, getInitialSnapshot, getMicrosteps, getNextSnapshot, getNextTransitions, initialTransition, isTypeSchema, machineConfigToJSON, mapState, serializeMachine, setup, toPromise, transition, types, waitFor };
|