xstate 6.0.0-alpha.70 → 6.0.0-alpha.71
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 +28 -0
- package/bin/agent.js +347 -0
- package/bin/xstate.js +16 -0
- package/dist/{createActor-11e36865.development.cjs.js → createActor-2136a8ad.development.cjs.js} +528 -461
- package/dist/{createActor-eaa95ae0.esm.js → createActor-22a0534b.esm.js} +528 -456
- package/dist/{createActor-9529bbaf.development.esm.js → createActor-286a0dbb.development.esm.js} +528 -462
- package/dist/{createActor-54296cf2.cjs.js → createActor-f4978450.cjs.js} +528 -455
- package/dist/declarations/src/StateMachine.d.ts +5 -3
- package/dist/declarations/src/base.types.d.ts +20 -20
- package/dist/declarations/src/graph/types.d.ts +22 -1
- package/dist/declarations/src/index.d.ts +2 -2
- package/dist/declarations/src/serialize.d.ts +14 -0
- package/dist/declarations/src/setup.d.ts +3 -1
- package/dist/declarations/src/system.d.ts +14 -1
- package/dist/declarations/src/types.d.ts +5 -2
- package/dist/{effectDescriptor-97802bb8.esm.js → effectDescriptor-2ae7e5e3.esm.js} +1 -1
- package/dist/{effectDescriptor-df768c71.development.esm.js → effectDescriptor-86f04325.development.esm.js} +1 -1
- package/dist/{effectDescriptor-425644eb.cjs.js → effectDescriptor-a6247f92.cjs.js} +1 -1
- package/dist/{effectDescriptor-fd2aaf45.development.cjs.js → effectDescriptor-e41e1627.development.cjs.js} +1 -1
- package/dist/{index-9826ee1d.development.cjs.js → index-31b3c878.cjs.js} +3 -3
- package/dist/{index-986372f2.cjs.js → index-5ce9cbf9.development.cjs.js} +3 -3
- package/dist/{index-e30fe161.development.esm.js → index-6923de25.development.esm.js} +3 -3
- package/dist/{index-967ad7fa.esm.js → index-74b77771.esm.js} +3 -3
- package/dist/xstate-actors.cjs.js +2 -2
- package/dist/xstate-actors.development.cjs.js +2 -2
- package/dist/xstate-actors.development.esm.js +2 -2
- package/dist/xstate-actors.esm.js +2 -2
- package/dist/xstate-actors.umd.min.js +1 -1
- package/dist/xstate-actors.umd.min.js.map +1 -1
- package/dist/xstate-durable.cjs.js +2 -2
- package/dist/xstate-durable.development.cjs.js +2 -2
- package/dist/xstate-durable.development.esm.js +2 -2
- package/dist/xstate-durable.esm.js +2 -2
- package/dist/xstate-durable.umd.min.js +1 -1
- package/dist/xstate-durable.umd.min.js.map +1 -1
- package/dist/xstate-graph.cjs.js +32 -6
- package/dist/xstate-graph.development.cjs.js +32 -6
- package/dist/xstate-graph.development.esm.js +33 -7
- package/dist/xstate-graph.esm.js +33 -7
- package/dist/xstate-graph.umd.min.js +1 -1
- package/dist/xstate-graph.umd.min.js.map +1 -1
- package/dist/xstate-validation.cjs.js +1 -1
- package/dist/xstate-validation.development.cjs.js +1 -1
- package/dist/xstate-validation.development.esm.js +1 -1
- package/dist/xstate-validation.esm.js +1 -1
- package/dist/xstate.cjs.js +161 -49
- package/dist/xstate.cjs.mjs +1 -0
- package/dist/xstate.development.cjs.js +161 -49
- package/dist/xstate.development.cjs.mjs +1 -0
- package/dist/xstate.development.esm.js +163 -52
- package/dist/xstate.esm.js +163 -52
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/docs/agents.md +132 -0
- package/docs/api-stability.json +3672 -0
- package/docs/context.md +4 -0
- package/docs/create-actor.md +3 -2
- package/docs/emitted-events.md +20 -1
- package/docs/index.md +4 -2
- package/docs/model-based-testing.md +3 -1
- package/docs/persist-and-restore-actors.md +1 -1
- package/docs/persistence.md +2 -2
- package/docs/scxml.md +32 -0
- package/docs/serialization.md +3 -0
- package/docs/systems.md +33 -1
- package/docs/transition-functions.md +3 -1
- package/docs/transitions.md +4 -2
- package/docs/xstate-v5-to-v6.md +16 -1
- package/package.json +1 -1
package/dist/{createActor-11e36865.development.cjs.js → createActor-2136a8ad.development.cjs.js}
RENAMED
|
@@ -1,5 +1,392 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const STATE_DELIMITER = '.';
|
|
4
|
+
const TARGETLESS_KEY = '';
|
|
5
|
+
const NULL_EVENT = '';
|
|
6
|
+
const STATE_IDENTIFIER = '#';
|
|
7
|
+
const WILDCARD = '*';
|
|
8
|
+
const XSTATE_INIT = 'xstate.init';
|
|
9
|
+
const XSTATE_START = '@xstate.start';
|
|
10
|
+
const XSTATE_SPAWN = '@xstate.spawn';
|
|
11
|
+
/** The event an actor receives when it is stopped. */
|
|
12
|
+
const XSTATE_STOP = 'xstate.stop';
|
|
13
|
+
/** The effect that stops a child actor. */
|
|
14
|
+
const XSTATE_STOP_EFFECT = '@xstate.stop';
|
|
15
|
+
const XSTATE_TERMINATE = '@xstate.terminate';
|
|
16
|
+
const XSTATE_TIMER = 'xstate.timer';
|
|
17
|
+
const XSTATE_LOGIC_EFFECT_RESOLVE = 'xstate.logic.effect.resolve';
|
|
18
|
+
const XSTATE_LOGIC_EFFECT_REJECT = 'xstate.logic.effect.reject';
|
|
19
|
+
const XSTATE_LOGIC_EFFECT_START = 'xstate.logic.effect.start';
|
|
20
|
+
|
|
21
|
+
const emptySubscription = {
|
|
22
|
+
unsubscribe() {}
|
|
23
|
+
};
|
|
24
|
+
const restoreHint = 'or restore this snapshot with embedded children on the runtime that owns them.';
|
|
25
|
+
|
|
26
|
+
/** @internal */
|
|
27
|
+
function isRemoteActorRef(actorRef) {
|
|
28
|
+
return actorRef._remote === true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @internal Whether a send target is an actor reference that went through
|
|
33
|
+
* JSON (`actor.toJSON()`): plain data carrying an address, not an actor.
|
|
34
|
+
*/
|
|
35
|
+
function isSerializedActorRef(target) {
|
|
36
|
+
if (typeof target !== 'object' || target === null || Array.isArray(target) || typeof target.$actor !== 'string') {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
for (const key in target) {
|
|
40
|
+
if (key !== '$actor' && key !== 'incarnation') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const incarnation = target.incarnation;
|
|
45
|
+
return incarnation === undefined || typeof incarnation === 'string';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @internal The actor id that the last segment of an address encodes. */
|
|
49
|
+
function getAddressId(address) {
|
|
50
|
+
const segment = address.slice(address.lastIndexOf('/') + 1);
|
|
51
|
+
return segment.includes('%') ? segment.replaceAll('%2F', '/').replaceAll('%25', '%') : segment;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** @internal The error reported when no live actor has a serialized address. */
|
|
55
|
+
function createMissingAddressError(target) {
|
|
56
|
+
return new Error(target.incarnation === undefined ? `Unable to send event to actor '${target.$actor}': no live actor has that address` : `Unable to send event to actor '${target.$actor}' (incarnation '${target.incarnation}'): no live actor has that address and incarnation`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @internal The live actor a serialized ref names: the actor at its address,
|
|
61
|
+
* and, when the ref carries an `incarnation`, only that incarnation. A ref
|
|
62
|
+
* without one follows whichever actor is live at the address.
|
|
63
|
+
*/
|
|
64
|
+
function resolveSerializedActorRef(system, target) {
|
|
65
|
+
const live = system._getByAddress?.(target.$actor);
|
|
66
|
+
return live && (target.incarnation === undefined || live.sessionId === target.incarnation) ? live : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The lifecycle-only snapshot a remote handle exposes. `active` is accurate
|
|
71
|
+
* by construction: a completion removes the child from its parent's
|
|
72
|
+
* `children`, so a handle only exists while the child is presumed active,
|
|
73
|
+
* and the terminal result arrives as a completion event instead of a
|
|
74
|
+
* snapshot. Full snapshots are a co-location capability.
|
|
75
|
+
*/
|
|
76
|
+
const remoteSnapshot = Object.freeze({
|
|
77
|
+
status: 'active',
|
|
78
|
+
output: undefined,
|
|
79
|
+
error: undefined
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates the inert ref `enq.spawn(...)` returns while a transition is only
|
|
84
|
+
* evaluated (for example by `snapshot.can(...)`, or to resolve conflicting
|
|
85
|
+
* transitions) and no actor is created. It carries the identity the spawn
|
|
86
|
+
* would get, accepts and drops events, exposes the same lifecycle-only
|
|
87
|
+
* snapshot as a remote handle, and serializes like any actor ref. Its address
|
|
88
|
+
* is unknown when the evaluating scope is detached from the parent actor.
|
|
89
|
+
*
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
function createInertActorRef(options) {
|
|
93
|
+
const {
|
|
94
|
+
id,
|
|
95
|
+
address
|
|
96
|
+
} = options;
|
|
97
|
+
const handle = {
|
|
98
|
+
ref: undefined,
|
|
99
|
+
id,
|
|
100
|
+
address,
|
|
101
|
+
src: options.src,
|
|
102
|
+
sessionId: undefined,
|
|
103
|
+
send() {},
|
|
104
|
+
getSnapshot() {
|
|
105
|
+
return remoteSnapshot;
|
|
106
|
+
},
|
|
107
|
+
subscribe() {
|
|
108
|
+
return emptySubscription;
|
|
109
|
+
},
|
|
110
|
+
on() {
|
|
111
|
+
return emptySubscription;
|
|
112
|
+
},
|
|
113
|
+
toJSON() {
|
|
114
|
+
if (address === undefined) {
|
|
115
|
+
throw new Error(`Cannot serialize actor ref '${id}': it was returned by a spawn during speculative transition evaluation, which creates no actor and has no parent address.`);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
$actor: address
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const ref = handle;
|
|
123
|
+
handle.ref = ref;
|
|
124
|
+
return ref;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates a location-transparent handle to an actor whose state lives with
|
|
129
|
+
* another runtime. The handle is constructed from identity alone — no lookup
|
|
130
|
+
* — and every send routes through the system's runtime; its state is not
|
|
131
|
+
* synchronously readable.
|
|
132
|
+
*
|
|
133
|
+
* The surface is deliberately minimal: it covers every member the runtime
|
|
134
|
+
* reaches for a restored child (send/_send, lifecycle via start/_stop and
|
|
135
|
+
* system.stopActor, persistence, subscribe/on as inert subscriptions).
|
|
136
|
+
* Members that only make sense for a co-located actor (public stop(),
|
|
137
|
+
* select(), trigger) throw a descriptive error rather than being absent, so
|
|
138
|
+
* calling one reports what went wrong instead of a bare TypeError. Observable
|
|
139
|
+
* interop and _processingStatus are omitted entirely.
|
|
140
|
+
*
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
function createRemoteActorRef(system, options) {
|
|
144
|
+
const handle = {
|
|
145
|
+
_remote: true,
|
|
146
|
+
// Self-reference so context persistence recognizes the handle as an
|
|
147
|
+
// actor reference instead of recursing into it.
|
|
148
|
+
ref: undefined,
|
|
149
|
+
id: options.id,
|
|
150
|
+
address: options.address,
|
|
151
|
+
src: options.src,
|
|
152
|
+
registryKey: options.registryKey,
|
|
153
|
+
// The host-supplied incarnation token, when present: the owner's
|
|
154
|
+
// sessionId for this child, letting this side drop stale completions and
|
|
155
|
+
// journaled sends name the intended incarnation. Without one the handle
|
|
156
|
+
// does not know the child's incarnation and the owning runtime is the
|
|
157
|
+
// authority on staleness.
|
|
158
|
+
sessionId: options.incarnation,
|
|
159
|
+
system,
|
|
160
|
+
_parent: options.parent,
|
|
161
|
+
// Round-trips through persistence verbatim (undefined stays undefined,
|
|
162
|
+
// so re-persisting is byte-stable); a remote handle cannot act on it
|
|
163
|
+
// locally.
|
|
164
|
+
_syncSnapshot: options.syncSnapshot,
|
|
165
|
+
send(event) {
|
|
166
|
+
void system.sendEvent(undefined, ref, event);
|
|
167
|
+
},
|
|
168
|
+
_send(event) {
|
|
169
|
+
throw new Error(`Remote actor '${options.address}' has no local mailbox to receive "${event.type}". Its state lives with another runtime; install a runtime that can reach it (via \`createDurable\`'s adapter runtime operations, or \`system.runtime\`) before sending, ${restoreHint}`);
|
|
170
|
+
},
|
|
171
|
+
getSnapshot() {
|
|
172
|
+
return remoteSnapshot;
|
|
173
|
+
},
|
|
174
|
+
getPersistedSnapshot() {
|
|
175
|
+
throw new Error(`Cannot persist remote actor '${options.address}' from here: its state lives with the runtime that owns it. Persist it there, ${restoreHint}`);
|
|
176
|
+
},
|
|
177
|
+
start() {},
|
|
178
|
+
_stop() {},
|
|
179
|
+
stop() {
|
|
180
|
+
throw new Error(`Cannot stop remote actor '${options.address}' directly: stopping is a co-located operation. Stop it through the system runtime that owns it (\`system.stopActor(ref)\`), ${restoreHint}`);
|
|
181
|
+
},
|
|
182
|
+
select() {
|
|
183
|
+
throw new Error(`Cannot select from remote actor '${options.address}': its snapshot is not synchronously readable because its state lives with another runtime. Read it on the runtime that owns it, ${restoreHint}`);
|
|
184
|
+
},
|
|
185
|
+
get trigger() {
|
|
186
|
+
throw new Error(`Remote actor '${options.address}' has no \`trigger\` shorthand: it requires a co-located actor. Use \`send(...)\`, which routes through the system runtime.`);
|
|
187
|
+
},
|
|
188
|
+
// Observation is a co-location capability: a remote handle never emits,
|
|
189
|
+
// so subscriptions are inert rather than errors — generic observers may
|
|
190
|
+
// attach to any ref.
|
|
191
|
+
subscribe() {
|
|
192
|
+
return emptySubscription;
|
|
193
|
+
},
|
|
194
|
+
on() {
|
|
195
|
+
return emptySubscription;
|
|
196
|
+
},
|
|
197
|
+
_isRunning() {
|
|
198
|
+
return false;
|
|
199
|
+
},
|
|
200
|
+
// The same shape Actor.toJSON produces, so serialized snapshots carry
|
|
201
|
+
// one actor-reference shape whether a child is co-located or remote.
|
|
202
|
+
toJSON() {
|
|
203
|
+
return {
|
|
204
|
+
$actor: options.address
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const ref = handle;
|
|
209
|
+
handle.ref = ref;
|
|
210
|
+
return ref;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** @internal */
|
|
214
|
+
function publishEmittedEvent(source, event, runtime) {
|
|
215
|
+
source.system._notifyEmitted(source, event);
|
|
216
|
+
return runtime.emitEvent(source, event);
|
|
217
|
+
}
|
|
218
|
+
function getStepEffect(actor, key) {
|
|
219
|
+
const effects = actor.getSnapshot().effects;
|
|
220
|
+
return effects && Object.prototype.hasOwnProperty.call(effects, key) ? effects[key] : undefined;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Live promises belong to an actor incarnation, unlike its persisted journal.
|
|
224
|
+
// A restored active record has no promise here and must be retried locally.
|
|
225
|
+
const pendingSteps = new WeakMap();
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Runs one keyed step of an async actor with the built-in journal: the
|
|
229
|
+
* result is memoized in the actor's own snapshot (`effects[key]`), so a
|
|
230
|
+
* restored actor skips completed steps when its logic function re-runs.
|
|
231
|
+
* Custom runtimes that implement `runStep` use this for the default local
|
|
232
|
+
* behavior, the same way `deliverEvent` exposes local delivery.
|
|
233
|
+
*
|
|
234
|
+
* @experimental
|
|
235
|
+
*/
|
|
236
|
+
async function runStep(actor, key, exec, sendSelf = event => void actor.system.sendEvent(actor, actor, event)) {
|
|
237
|
+
const effect = getStepEffect(actor, key);
|
|
238
|
+
if (effect?.status === 'done') {
|
|
239
|
+
return effect.output;
|
|
240
|
+
}
|
|
241
|
+
if (effect?.status === 'error') {
|
|
242
|
+
throw effect.error;
|
|
243
|
+
}
|
|
244
|
+
const running = pendingSteps.get(actor)?.get(key);
|
|
245
|
+
if (running) {
|
|
246
|
+
return running;
|
|
247
|
+
}
|
|
248
|
+
if (actor.getSnapshot().status !== 'active') {
|
|
249
|
+
throw new Error(`Actor terminated before step "${key}" completed`);
|
|
250
|
+
}
|
|
251
|
+
let resolve;
|
|
252
|
+
let reject;
|
|
253
|
+
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
254
|
+
resolve = resolvePromise;
|
|
255
|
+
reject = rejectPromise;
|
|
256
|
+
});
|
|
257
|
+
let steps = pendingSteps.get(actor);
|
|
258
|
+
if (!steps) {
|
|
259
|
+
steps = new Map();
|
|
260
|
+
pendingSteps.set(actor, steps);
|
|
261
|
+
}
|
|
262
|
+
steps.set(key, promise);
|
|
263
|
+
// oxlint-disable-next-line prefer-const
|
|
264
|
+
let subscription;
|
|
265
|
+
const cleanup = () => {
|
|
266
|
+
subscription?.unsubscribe();
|
|
267
|
+
steps.delete(key);
|
|
268
|
+
if (!steps.size) {
|
|
269
|
+
pendingSteps.delete(actor);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
void promise.then(cleanup, cleanup);
|
|
273
|
+
subscription = actor.subscribe({
|
|
274
|
+
error: reject,
|
|
275
|
+
complete: () => reject(new Error(`Actor terminated before step "${key}" completed`))
|
|
276
|
+
});
|
|
277
|
+
void (async () => {
|
|
278
|
+
try {
|
|
279
|
+
sendSelf({
|
|
280
|
+
type: XSTATE_LOGIC_EFFECT_START,
|
|
281
|
+
key
|
|
282
|
+
});
|
|
283
|
+
const output = await exec();
|
|
284
|
+
if (actor.getSnapshot().status === 'active') {
|
|
285
|
+
sendSelf({
|
|
286
|
+
type: XSTATE_LOGIC_EFFECT_RESOLVE,
|
|
287
|
+
key,
|
|
288
|
+
output
|
|
289
|
+
});
|
|
290
|
+
resolve(output);
|
|
291
|
+
}
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (actor.getSnapshot().status === 'active') {
|
|
294
|
+
sendSelf({
|
|
295
|
+
type: XSTATE_LOGIC_EFFECT_REJECT,
|
|
296
|
+
key,
|
|
297
|
+
error
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
reject(error);
|
|
301
|
+
}
|
|
302
|
+
})().catch(reject);
|
|
303
|
+
return promise;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Delivers an event directly to a target actor, bypassing any installed host
|
|
308
|
+
* runtime. Custom runtimes use this for the default local delivery behavior
|
|
309
|
+
* inside their own `sendEvent` implementations.
|
|
310
|
+
*
|
|
311
|
+
* @experimental
|
|
312
|
+
*/
|
|
313
|
+
function deliverEvent(source, target, event) {
|
|
314
|
+
if (isSerializedActorRef(target)) {
|
|
315
|
+
const serialized = target;
|
|
316
|
+
const system = source?.system;
|
|
317
|
+
const live = system && resolveSerializedActorRef(system, serialized);
|
|
318
|
+
if (!live) {
|
|
319
|
+
const error = createMissingAddressError(serialized);
|
|
320
|
+
if (!system) {
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
void system.deadLetter(source, undefined, event, 'missingTarget', {
|
|
324
|
+
targetId: getAddressId(serialized.$actor),
|
|
325
|
+
error
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
target = live;
|
|
330
|
+
}
|
|
331
|
+
if (rejectUndeliverableEvent(source, target, event)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
const runtimeTarget = target;
|
|
335
|
+
runtimeTarget._send(event, source);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Returns the boundary error preventing this delivery, if any: an internal
|
|
340
|
+
* event type cannot cross an actor boundary from an external sender.
|
|
341
|
+
*/
|
|
342
|
+
function getEventBoundaryError(source, target, event) {
|
|
343
|
+
const runtimeTarget = target;
|
|
344
|
+
if (source !== target && runtimeTarget.logic?.isInternalEventType?.(event.type)) {
|
|
345
|
+
return new Error(`Internal event "${event.type}" cannot be sent to actor "${target.id}" from outside.`);
|
|
346
|
+
}
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Rejects an event that must not cross the delivery boundary — an internal
|
|
352
|
+
* event type sent from outside its owning actor — by reporting it as a dead
|
|
353
|
+
* letter. Returns `true` when the event was rejected and must not be
|
|
354
|
+
* delivered. Host runtimes must call this before taking ownership of
|
|
355
|
+
* delivery.
|
|
356
|
+
*/
|
|
357
|
+
function rejectUndeliverableEvent(source, target, event) {
|
|
358
|
+
const error = getEventBoundaryError(source, target, event);
|
|
359
|
+
if (error) {
|
|
360
|
+
void target.system.deadLetter(source, target, event, 'internalEvent', {
|
|
361
|
+
error
|
|
362
|
+
});
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Publishes an actor's terminal result, notifying its parent and observers.
|
|
370
|
+
* Custom runtimes use this for the default local behavior inside their own
|
|
371
|
+
* `terminateActor` implementations.
|
|
372
|
+
*
|
|
373
|
+
* @experimental
|
|
374
|
+
*/
|
|
375
|
+
function terminateActor(actor, termination) {
|
|
376
|
+
actor._terminate(termination);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Stops an actor without producing a completion result. Custom runtimes use
|
|
381
|
+
* this for the default local behavior inside their own `stopActor`
|
|
382
|
+
* implementations.
|
|
383
|
+
*
|
|
384
|
+
* @experimental
|
|
385
|
+
*/
|
|
386
|
+
function stopActor(actor) {
|
|
387
|
+
actor._stop();
|
|
388
|
+
}
|
|
389
|
+
|
|
3
390
|
class Mailbox {
|
|
4
391
|
constructor(_process) {
|
|
5
392
|
this._process = _process;
|
|
@@ -36,34 +423,16 @@ class Mailbox {
|
|
|
36
423
|
}
|
|
37
424
|
}
|
|
38
425
|
flush() {
|
|
39
|
-
while (this._current) {
|
|
40
|
-
// atm the given _process is responsible for implementing proper try/catch handling
|
|
41
|
-
// we assume here that this won't throw in a way that can affect this mailbox
|
|
42
|
-
const consumed = this._current;
|
|
43
|
-
this._process(consumed.value);
|
|
44
|
-
this._current = consumed.next;
|
|
45
|
-
}
|
|
46
|
-
this._last = null;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const STATE_DELIMITER = '.';
|
|
51
|
-
const TARGETLESS_KEY = '';
|
|
52
|
-
const NULL_EVENT = '';
|
|
53
|
-
const STATE_IDENTIFIER = '#';
|
|
54
|
-
const WILDCARD = '*';
|
|
55
|
-
const XSTATE_INIT = 'xstate.init';
|
|
56
|
-
const XSTATE_START = '@xstate.start';
|
|
57
|
-
const XSTATE_SPAWN = '@xstate.spawn';
|
|
58
|
-
/** The event an actor receives when it is stopped. */
|
|
59
|
-
const XSTATE_STOP = 'xstate.stop';
|
|
60
|
-
/** The effect that stops a child actor. */
|
|
61
|
-
const XSTATE_STOP_EFFECT = '@xstate.stop';
|
|
62
|
-
const XSTATE_TERMINATE = '@xstate.terminate';
|
|
63
|
-
const XSTATE_TIMER = 'xstate.timer';
|
|
64
|
-
const XSTATE_LOGIC_EFFECT_RESOLVE = 'xstate.logic.effect.resolve';
|
|
65
|
-
const XSTATE_LOGIC_EFFECT_REJECT = 'xstate.logic.effect.reject';
|
|
66
|
-
const XSTATE_LOGIC_EFFECT_START = 'xstate.logic.effect.start';
|
|
426
|
+
while (this._current) {
|
|
427
|
+
// atm the given _process is responsible for implementing proper try/catch handling
|
|
428
|
+
// we assume here that this won't throw in a way that can affect this mailbox
|
|
429
|
+
const consumed = this._current;
|
|
430
|
+
this._process(consumed.value);
|
|
431
|
+
this._current = consumed.next;
|
|
432
|
+
}
|
|
433
|
+
this._last = null;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
67
436
|
|
|
68
437
|
/**
|
|
69
438
|
* Returns an event that represents an implicit event that is sent after the
|
|
@@ -543,254 +912,62 @@ function createListenerLogic() {
|
|
|
543
912
|
}
|
|
544
913
|
|
|
545
914
|
/** @internal The logic of helper actors created by `enq.listen()`. */
|
|
546
|
-
const listenerLogic = /* #__PURE__ */createListenerLogic();
|
|
547
|
-
|
|
548
|
-
/** @public */
|
|
549
|
-
|
|
550
|
-
/** @public */
|
|
551
|
-
|
|
552
|
-
/** @public */
|
|
553
|
-
|
|
554
|
-
/** @public */
|
|
555
|
-
|
|
556
|
-
/** @public */
|
|
557
|
-
|
|
558
|
-
/**
|
|
559
|
-
* Creates actor logic for subscribing to lifecycle events (done/error/snapshot)
|
|
560
|
-
* from another actor. Used internally by `enq.subscribeTo()`.
|
|
561
|
-
*
|
|
562
|
-
* @public
|
|
563
|
-
*/
|
|
564
|
-
function createSubscriptionLogic() {
|
|
565
|
-
return createAttachedLogic(({
|
|
566
|
-
actor,
|
|
567
|
-
mappers
|
|
568
|
-
}, {
|
|
569
|
-
self,
|
|
570
|
-
system
|
|
571
|
-
}) => {
|
|
572
|
-
const {
|
|
573
|
-
done,
|
|
574
|
-
error,
|
|
575
|
-
snapshot: onSnapshot
|
|
576
|
-
} = mappers;
|
|
577
|
-
return actor.subscribe({
|
|
578
|
-
next: snapshot => {
|
|
579
|
-
if (snapshot.status === 'done' && done) {
|
|
580
|
-
relayMappedToParent(self, system, () => done(snapshot.output));
|
|
581
|
-
} else if (snapshot.status === 'error' && error) {
|
|
582
|
-
relayMappedToParent(self, system, () => error(snapshot.error));
|
|
583
|
-
} else if (snapshot.status === 'active' && onSnapshot) {
|
|
584
|
-
relayMappedToParent(self, system, () => onSnapshot(snapshot));
|
|
585
|
-
}
|
|
586
|
-
},
|
|
587
|
-
error: err => {
|
|
588
|
-
if (error) {
|
|
589
|
-
relayMappedToParent(self, system, () => error(err));
|
|
590
|
-
}
|
|
591
|
-
},
|
|
592
|
-
complete: () => {
|
|
593
|
-
// Actor completed without output (stopped); no action needed
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
}, '@xstate.subscription');
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/** @internal The logic of helper actors created by `enq.subscribeTo()`. */
|
|
600
|
-
const subscriptionLogic = /* #__PURE__ */createSubscriptionLogic();
|
|
601
|
-
|
|
602
|
-
const emptySubscription = {
|
|
603
|
-
unsubscribe() {}
|
|
604
|
-
};
|
|
605
|
-
const restoreHint = 'or restore this snapshot with embedded children on the runtime that owns them.';
|
|
606
|
-
|
|
607
|
-
/** @internal */
|
|
608
|
-
function isRemoteActorRef(actorRef) {
|
|
609
|
-
return actorRef._remote === true;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* @internal Whether a send target is an actor reference that went through
|
|
614
|
-
* JSON (`actor.toJSON()`): plain data carrying an address, not an actor.
|
|
615
|
-
*/
|
|
616
|
-
function isSerializedActorRef(target) {
|
|
617
|
-
if (typeof target !== 'object' || target === null || Array.isArray(target) || typeof target.$actor !== 'string') {
|
|
618
|
-
return false;
|
|
619
|
-
}
|
|
620
|
-
for (const key in target) {
|
|
621
|
-
if (key !== '$actor' && key !== 'incarnation') {
|
|
622
|
-
return false;
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
const incarnation = target.incarnation;
|
|
626
|
-
return incarnation === undefined || typeof incarnation === 'string';
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
/** @internal The actor id that the last segment of an address encodes. */
|
|
630
|
-
function getAddressId(address) {
|
|
631
|
-
const segment = address.slice(address.lastIndexOf('/') + 1);
|
|
632
|
-
return segment.includes('%') ? segment.replaceAll('%2F', '/').replaceAll('%25', '%') : segment;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/** @internal The error reported when no live actor has a serialized address. */
|
|
636
|
-
function createMissingAddressError(target) {
|
|
637
|
-
return new Error(target.incarnation === undefined ? `Unable to send event to actor '${target.$actor}': no live actor has that address` : `Unable to send event to actor '${target.$actor}' (incarnation '${target.incarnation}'): no live actor has that address and incarnation`);
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
/**
|
|
641
|
-
* @internal The live actor a serialized ref names: the actor at its address,
|
|
642
|
-
* and, when the ref carries an `incarnation`, only that incarnation. A ref
|
|
643
|
-
* without one follows whichever actor is live at the address.
|
|
644
|
-
*/
|
|
645
|
-
function resolveSerializedActorRef(system, target) {
|
|
646
|
-
const live = system._getByAddress?.(target.$actor);
|
|
647
|
-
return live && (target.incarnation === undefined || live.sessionId === target.incarnation) ? live : undefined;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
/**
|
|
651
|
-
* The lifecycle-only snapshot a remote handle exposes. `active` is accurate
|
|
652
|
-
* by construction: a completion removes the child from its parent's
|
|
653
|
-
* `children`, so a handle only exists while the child is presumed active,
|
|
654
|
-
* and the terminal result arrives as a completion event instead of a
|
|
655
|
-
* snapshot. Full snapshots are a co-location capability.
|
|
656
|
-
*/
|
|
657
|
-
const remoteSnapshot = Object.freeze({
|
|
658
|
-
status: 'active',
|
|
659
|
-
output: undefined,
|
|
660
|
-
error: undefined
|
|
661
|
-
});
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Creates the inert ref `enq.spawn(...)` returns while a transition is only
|
|
665
|
-
* evaluated (for example by `snapshot.can(...)`, or to resolve conflicting
|
|
666
|
-
* transitions) and no actor is created. It carries the identity the spawn
|
|
667
|
-
* would get, accepts and drops events, exposes the same lifecycle-only
|
|
668
|
-
* snapshot as a remote handle, and serializes like any actor ref. Its address
|
|
669
|
-
* is unknown when the evaluating scope is detached from the parent actor.
|
|
670
|
-
*
|
|
671
|
-
* @internal
|
|
672
|
-
*/
|
|
673
|
-
function createInertActorRef(options) {
|
|
674
|
-
const {
|
|
675
|
-
id,
|
|
676
|
-
address
|
|
677
|
-
} = options;
|
|
678
|
-
const handle = {
|
|
679
|
-
ref: undefined,
|
|
680
|
-
id,
|
|
681
|
-
address,
|
|
682
|
-
src: options.src,
|
|
683
|
-
sessionId: undefined,
|
|
684
|
-
send() {},
|
|
685
|
-
getSnapshot() {
|
|
686
|
-
return remoteSnapshot;
|
|
687
|
-
},
|
|
688
|
-
subscribe() {
|
|
689
|
-
return emptySubscription;
|
|
690
|
-
},
|
|
691
|
-
on() {
|
|
692
|
-
return emptySubscription;
|
|
693
|
-
},
|
|
694
|
-
toJSON() {
|
|
695
|
-
if (address === undefined) {
|
|
696
|
-
throw new Error(`Cannot serialize actor ref '${id}': it was returned by a spawn during speculative transition evaluation, which creates no actor and has no parent address.`);
|
|
697
|
-
}
|
|
698
|
-
return {
|
|
699
|
-
$actor: address
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
};
|
|
703
|
-
const ref = handle;
|
|
704
|
-
handle.ref = ref;
|
|
705
|
-
return ref;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Creates a location-transparent handle to an actor whose state lives with
|
|
710
|
-
* another runtime. The handle is constructed from identity alone — no lookup
|
|
711
|
-
* — and every send routes through the system's runtime; its state is not
|
|
712
|
-
* synchronously readable.
|
|
713
|
-
*
|
|
714
|
-
* The surface is deliberately minimal: it covers every member the runtime
|
|
715
|
-
* reaches for a restored child (send/_send, lifecycle via start/_stop and
|
|
716
|
-
* system.stopActor, persistence, subscribe/on as inert subscriptions).
|
|
717
|
-
* Members that only make sense for a co-located actor (public stop(),
|
|
718
|
-
* select(), trigger) throw a descriptive error rather than being absent, so
|
|
719
|
-
* calling one reports what went wrong instead of a bare TypeError. Observable
|
|
720
|
-
* interop and _processingStatus are omitted entirely.
|
|
721
|
-
*
|
|
722
|
-
* @internal
|
|
723
|
-
*/
|
|
724
|
-
function createRemoteActorRef(system, options) {
|
|
725
|
-
const handle = {
|
|
726
|
-
_remote: true,
|
|
727
|
-
// Self-reference so context persistence recognizes the handle as an
|
|
728
|
-
// actor reference instead of recursing into it.
|
|
729
|
-
ref: undefined,
|
|
730
|
-
id: options.id,
|
|
731
|
-
address: options.address,
|
|
732
|
-
src: options.src,
|
|
733
|
-
registryKey: options.registryKey,
|
|
734
|
-
// The host-supplied incarnation token, when present: the owner's
|
|
735
|
-
// sessionId for this child, letting this side drop stale completions and
|
|
736
|
-
// journaled sends name the intended incarnation. Without one the handle
|
|
737
|
-
// does not know the child's incarnation and the owning runtime is the
|
|
738
|
-
// authority on staleness.
|
|
739
|
-
sessionId: options.incarnation,
|
|
740
|
-
system,
|
|
741
|
-
_parent: options.parent,
|
|
742
|
-
// Round-trips through persistence verbatim (undefined stays undefined,
|
|
743
|
-
// so re-persisting is byte-stable); a remote handle cannot act on it
|
|
744
|
-
// locally.
|
|
745
|
-
_syncSnapshot: options.syncSnapshot,
|
|
746
|
-
send(event) {
|
|
747
|
-
void system.sendEvent(undefined, ref, event);
|
|
748
|
-
},
|
|
749
|
-
_send(event) {
|
|
750
|
-
throw new Error(`Remote actor '${options.address}' has no local mailbox to receive "${event.type}". Its state lives with another runtime; install a runtime that can reach it (via \`createDurable\`'s adapter runtime operations, or \`system.runtime\`) before sending, ${restoreHint}`);
|
|
751
|
-
},
|
|
752
|
-
getSnapshot() {
|
|
753
|
-
return remoteSnapshot;
|
|
754
|
-
},
|
|
755
|
-
getPersistedSnapshot() {
|
|
756
|
-
throw new Error(`Cannot persist remote actor '${options.address}' from here: its state lives with the runtime that owns it. Persist it there, ${restoreHint}`);
|
|
757
|
-
},
|
|
758
|
-
start() {},
|
|
759
|
-
_stop() {},
|
|
760
|
-
stop() {
|
|
761
|
-
throw new Error(`Cannot stop remote actor '${options.address}' directly: stopping is a co-located operation. Stop it through the system runtime that owns it (\`system.stopActor(ref)\`), ${restoreHint}`);
|
|
762
|
-
},
|
|
763
|
-
select() {
|
|
764
|
-
throw new Error(`Cannot select from remote actor '${options.address}': its snapshot is not synchronously readable because its state lives with another runtime. Read it on the runtime that owns it, ${restoreHint}`);
|
|
765
|
-
},
|
|
766
|
-
get trigger() {
|
|
767
|
-
throw new Error(`Remote actor '${options.address}' has no \`trigger\` shorthand: it requires a co-located actor. Use \`send(...)\`, which routes through the system runtime.`);
|
|
768
|
-
},
|
|
769
|
-
// Observation is a co-location capability: a remote handle never emits,
|
|
770
|
-
// so subscriptions are inert rather than errors — generic observers may
|
|
771
|
-
// attach to any ref.
|
|
772
|
-
subscribe() {
|
|
773
|
-
return emptySubscription;
|
|
774
|
-
},
|
|
775
|
-
on() {
|
|
776
|
-
return emptySubscription;
|
|
777
|
-
},
|
|
778
|
-
_isRunning() {
|
|
779
|
-
return false;
|
|
780
|
-
},
|
|
781
|
-
// The same shape Actor.toJSON produces, so serialized snapshots carry
|
|
782
|
-
// one actor-reference shape whether a child is co-located or remote.
|
|
783
|
-
toJSON() {
|
|
784
|
-
return {
|
|
785
|
-
$actor: options.address
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
};
|
|
789
|
-
const ref = handle;
|
|
790
|
-
handle.ref = ref;
|
|
791
|
-
return ref;
|
|
915
|
+
const listenerLogic = /* #__PURE__ */createListenerLogic();
|
|
916
|
+
|
|
917
|
+
/** @public */
|
|
918
|
+
|
|
919
|
+
/** @public */
|
|
920
|
+
|
|
921
|
+
/** @public */
|
|
922
|
+
|
|
923
|
+
/** @public */
|
|
924
|
+
|
|
925
|
+
/** @public */
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* Creates actor logic for subscribing to lifecycle events (done/error/snapshot)
|
|
929
|
+
* from another actor. Used internally by `enq.subscribeTo()`.
|
|
930
|
+
*
|
|
931
|
+
* @public
|
|
932
|
+
*/
|
|
933
|
+
function createSubscriptionLogic() {
|
|
934
|
+
return createAttachedLogic(({
|
|
935
|
+
actor,
|
|
936
|
+
mappers
|
|
937
|
+
}, {
|
|
938
|
+
self,
|
|
939
|
+
system
|
|
940
|
+
}) => {
|
|
941
|
+
const {
|
|
942
|
+
done,
|
|
943
|
+
error,
|
|
944
|
+
snapshot: onSnapshot
|
|
945
|
+
} = mappers;
|
|
946
|
+
return actor.subscribe({
|
|
947
|
+
next: snapshot => {
|
|
948
|
+
if (snapshot.status === 'done' && done) {
|
|
949
|
+
relayMappedToParent(self, system, () => done(snapshot.output));
|
|
950
|
+
} else if (snapshot.status === 'error' && error) {
|
|
951
|
+
relayMappedToParent(self, system, () => error(snapshot.error));
|
|
952
|
+
} else if (snapshot.status === 'active' && onSnapshot) {
|
|
953
|
+
relayMappedToParent(self, system, () => onSnapshot(snapshot));
|
|
954
|
+
}
|
|
955
|
+
},
|
|
956
|
+
error: err => {
|
|
957
|
+
if (error) {
|
|
958
|
+
relayMappedToParent(self, system, () => error(err));
|
|
959
|
+
}
|
|
960
|
+
},
|
|
961
|
+
complete: () => {
|
|
962
|
+
// Actor completed without output (stopped); no action needed
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
}, '@xstate.subscription');
|
|
792
966
|
}
|
|
793
967
|
|
|
968
|
+
/** @internal The logic of helper actors created by `enq.subscribeTo()`. */
|
|
969
|
+
const subscriptionLogic = /* #__PURE__ */createSubscriptionLogic();
|
|
970
|
+
|
|
794
971
|
function execCustomEffect() {
|
|
795
972
|
const result = this.action?.(...this.args);
|
|
796
973
|
{
|
|
@@ -821,7 +998,7 @@ function warnOnIgnoredEffectResult(effect, result) {
|
|
|
821
998
|
console.warn(`Action "${effect.type || '(anonymous)'}" returned { ${keys.join(', ')} }, which is ignored: functions passed to enq(...) run as effects after the transition. Return the patch from the transition, entry or exit function instead; a named action can be called there directly and its result returned.`);
|
|
822
999
|
}
|
|
823
1000
|
function execEmitEffect(runtime = this.source.system) {
|
|
824
|
-
return
|
|
1001
|
+
return publishEmittedEvent(this.source, this.event, runtime);
|
|
825
1002
|
}
|
|
826
1003
|
|
|
827
1004
|
/** @internal Creates an emitted-event effect. */
|
|
@@ -1945,6 +2122,8 @@ function createSnapshotSystem(baseSystem, children, baseState) {
|
|
|
1945
2122
|
},
|
|
1946
2123
|
get: registryKey => keyedActors.get(registryKey),
|
|
1947
2124
|
getAll: () => Object.fromEntries(keyedActors),
|
|
2125
|
+
onEmitted: listener => baseSystem.onEmitted(listener),
|
|
2126
|
+
_notifyEmitted: (actorRef, event) => baseSystem._notifyEmitted(actorRef, event),
|
|
1948
2127
|
// Pure transition resolution must not leak topology inspection events
|
|
1949
2128
|
// into a live runtime system — planning branches stay silent. The one
|
|
1950
2129
|
// exception is a durable execution's own transitions (marked by the
|
|
@@ -3574,10 +3753,9 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
|
|
|
3574
3753
|
}
|
|
3575
3754
|
}
|
|
3576
3755
|
if (contextChangedByEntry) {
|
|
3577
|
-
nextState
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
}
|
|
3756
|
+
nextState = cloneMachineSnapshot(nextState, {
|
|
3757
|
+
context
|
|
3758
|
+
});
|
|
3581
3759
|
}
|
|
3582
3760
|
if (statesForDefaultEntry.has(stateNodeToEnter)) {
|
|
3583
3761
|
const defaultTransitions = [stateNodeToEnter.initial, ...(historyDefaultsByParent.get(stateNodeToEnter) ?? [])].filter(Boolean);
|
|
@@ -3595,10 +3773,9 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
|
|
|
3595
3773
|
internalQueue.push(...initialInternalEvents);
|
|
3596
3774
|
}
|
|
3597
3775
|
if (initialContext !== undefined) {
|
|
3598
|
-
nextState
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
}
|
|
3776
|
+
nextState = cloneMachineSnapshot(nextState, {
|
|
3777
|
+
context: mergeContextPatch(nextState.context, initialContext)
|
|
3778
|
+
});
|
|
3602
3779
|
}
|
|
3603
3780
|
if (initialInput && defaultTransition.target) {
|
|
3604
3781
|
for (const targetNode of defaultTransition.target) {
|
|
@@ -3665,6 +3842,27 @@ function microstep(transitions, currentSnapshot, actorScope, event, isInitial, i
|
|
|
3665
3842
|
context
|
|
3666
3843
|
});
|
|
3667
3844
|
}
|
|
3845
|
+
const transitionContent = nextState.machine._microstepHooks?.transitionContent?.(filteredTransitions, [...mutStateNodeSet]);
|
|
3846
|
+
if (transitionContent) {
|
|
3847
|
+
const source = filteredTransitions[0].source;
|
|
3848
|
+
const [actions, nextContext, raisedEvents, nextChildren] = getStateActionsAndContext(transitionContent, nextState.context, nextState.children, getStateInput(nextState, source.id), 'transition', source);
|
|
3849
|
+
if (raisedEvents?.length) {
|
|
3850
|
+
internalQueue.push(...raisedEvents);
|
|
3851
|
+
}
|
|
3852
|
+
if (nextContext || nextChildren) {
|
|
3853
|
+
nextState = cloneMachineSnapshot(nextState, {
|
|
3854
|
+
...(nextContext ? {
|
|
3855
|
+
context: nextContext
|
|
3856
|
+
} : {}),
|
|
3857
|
+
...(nextChildren ? {
|
|
3858
|
+
children: nextChildren
|
|
3859
|
+
} : {})
|
|
3860
|
+
});
|
|
3861
|
+
}
|
|
3862
|
+
const [resolvedState, effects] = resolveActionsWithContext(nextState, event, actorScope, actions);
|
|
3863
|
+
nextState = resolvedState;
|
|
3864
|
+
executableActions.push(...effects);
|
|
3865
|
+
}
|
|
3668
3866
|
|
|
3669
3867
|
// Enter states
|
|
3670
3868
|
if (isInitial || changesState) {
|
|
@@ -4055,7 +4253,7 @@ function hasEffect(transition, context, event, snapshot, actorScope) {
|
|
|
4055
4253
|
function assertSyncTransitionResult(res, event, sourceId, kind) {
|
|
4056
4254
|
if (res && typeof res.then === 'function') {
|
|
4057
4255
|
void Promise.resolve(res).catch(() => {});
|
|
4058
|
-
throw new Error(kind ? `${kind === 'entry' ? 'Entry' : 'Exit'} functions must be synchronous. The ${kind} function of state "${sourceId}" returned a promise (event "${event.type}"). Move async work into an invoked or spawned actor, or enq.effect.` : `Transition functions must be synchronous. Transition for event "${event.type}" in state "${sourceId}" returned a promise. Move async work into an invoked or spawned actor, or enq.effect.`);
|
|
4256
|
+
throw new Error(kind && kind !== 'transition' ? `${kind === 'entry' ? 'Entry' : 'Exit'} functions must be synchronous. The ${kind} function of state "${sourceId}" returned a promise (event "${event.type}"). Move async work into an invoked or spawned actor, or enq.effect.` : `Transition functions must be synchronous. Transition for event "${event.type}" in state "${sourceId}" returned a promise. Move async work into an invoked or spawned actor, or enq.effect.`);
|
|
4059
4257
|
}
|
|
4060
4258
|
}
|
|
4061
4259
|
function createTransitionSelection(parent) {
|
|
@@ -5042,178 +5240,6 @@ function matchesEventDescriptor(eventType, descriptor) {
|
|
|
5042
5240
|
return true;
|
|
5043
5241
|
}
|
|
5044
5242
|
|
|
5045
|
-
function getStepEffect(actor, key) {
|
|
5046
|
-
const effects = actor.getSnapshot().effects;
|
|
5047
|
-
return effects && Object.prototype.hasOwnProperty.call(effects, key) ? effects[key] : undefined;
|
|
5048
|
-
}
|
|
5049
|
-
|
|
5050
|
-
// Live promises belong to an actor incarnation, unlike its persisted journal.
|
|
5051
|
-
// A restored active record has no promise here and must be retried locally.
|
|
5052
|
-
const pendingSteps = new WeakMap();
|
|
5053
|
-
|
|
5054
|
-
/**
|
|
5055
|
-
* Runs one keyed step of an async actor with the built-in journal: the
|
|
5056
|
-
* result is memoized in the actor's own snapshot (`effects[key]`), so a
|
|
5057
|
-
* restored actor skips completed steps when its logic function re-runs.
|
|
5058
|
-
* Custom runtimes that implement `runStep` use this for the default local
|
|
5059
|
-
* behavior, the same way `deliverEvent` exposes local delivery.
|
|
5060
|
-
*
|
|
5061
|
-
* @experimental
|
|
5062
|
-
*/
|
|
5063
|
-
async function runStep(actor, key, exec, sendSelf = event => void actor.system.sendEvent(actor, actor, event)) {
|
|
5064
|
-
const effect = getStepEffect(actor, key);
|
|
5065
|
-
if (effect?.status === 'done') {
|
|
5066
|
-
return effect.output;
|
|
5067
|
-
}
|
|
5068
|
-
if (effect?.status === 'error') {
|
|
5069
|
-
throw effect.error;
|
|
5070
|
-
}
|
|
5071
|
-
const running = pendingSteps.get(actor)?.get(key);
|
|
5072
|
-
if (running) {
|
|
5073
|
-
return running;
|
|
5074
|
-
}
|
|
5075
|
-
if (actor.getSnapshot().status !== 'active') {
|
|
5076
|
-
throw new Error(`Actor terminated before step "${key}" completed`);
|
|
5077
|
-
}
|
|
5078
|
-
let resolve;
|
|
5079
|
-
let reject;
|
|
5080
|
-
const promise = new Promise((resolvePromise, rejectPromise) => {
|
|
5081
|
-
resolve = resolvePromise;
|
|
5082
|
-
reject = rejectPromise;
|
|
5083
|
-
});
|
|
5084
|
-
let steps = pendingSteps.get(actor);
|
|
5085
|
-
if (!steps) {
|
|
5086
|
-
steps = new Map();
|
|
5087
|
-
pendingSteps.set(actor, steps);
|
|
5088
|
-
}
|
|
5089
|
-
steps.set(key, promise);
|
|
5090
|
-
// oxlint-disable-next-line prefer-const
|
|
5091
|
-
let subscription;
|
|
5092
|
-
const cleanup = () => {
|
|
5093
|
-
subscription?.unsubscribe();
|
|
5094
|
-
steps.delete(key);
|
|
5095
|
-
if (!steps.size) {
|
|
5096
|
-
pendingSteps.delete(actor);
|
|
5097
|
-
}
|
|
5098
|
-
};
|
|
5099
|
-
void promise.then(cleanup, cleanup);
|
|
5100
|
-
subscription = actor.subscribe({
|
|
5101
|
-
error: reject,
|
|
5102
|
-
complete: () => reject(new Error(`Actor terminated before step "${key}" completed`))
|
|
5103
|
-
});
|
|
5104
|
-
void (async () => {
|
|
5105
|
-
try {
|
|
5106
|
-
sendSelf({
|
|
5107
|
-
type: XSTATE_LOGIC_EFFECT_START,
|
|
5108
|
-
key
|
|
5109
|
-
});
|
|
5110
|
-
const output = await exec();
|
|
5111
|
-
if (actor.getSnapshot().status === 'active') {
|
|
5112
|
-
sendSelf({
|
|
5113
|
-
type: XSTATE_LOGIC_EFFECT_RESOLVE,
|
|
5114
|
-
key,
|
|
5115
|
-
output
|
|
5116
|
-
});
|
|
5117
|
-
resolve(output);
|
|
5118
|
-
}
|
|
5119
|
-
} catch (error) {
|
|
5120
|
-
if (actor.getSnapshot().status === 'active') {
|
|
5121
|
-
sendSelf({
|
|
5122
|
-
type: XSTATE_LOGIC_EFFECT_REJECT,
|
|
5123
|
-
key,
|
|
5124
|
-
error
|
|
5125
|
-
});
|
|
5126
|
-
}
|
|
5127
|
-
reject(error);
|
|
5128
|
-
}
|
|
5129
|
-
})().catch(reject);
|
|
5130
|
-
return promise;
|
|
5131
|
-
}
|
|
5132
|
-
|
|
5133
|
-
/**
|
|
5134
|
-
* Delivers an event directly to a target actor, bypassing any installed host
|
|
5135
|
-
* runtime. Custom runtimes use this for the default local delivery behavior
|
|
5136
|
-
* inside their own `sendEvent` implementations.
|
|
5137
|
-
*
|
|
5138
|
-
* @experimental
|
|
5139
|
-
*/
|
|
5140
|
-
function deliverEvent(source, target, event) {
|
|
5141
|
-
if (isSerializedActorRef(target)) {
|
|
5142
|
-
const serialized = target;
|
|
5143
|
-
const system = source?.system;
|
|
5144
|
-
const live = system && resolveSerializedActorRef(system, serialized);
|
|
5145
|
-
if (!live) {
|
|
5146
|
-
const error = createMissingAddressError(serialized);
|
|
5147
|
-
if (!system) {
|
|
5148
|
-
throw error;
|
|
5149
|
-
}
|
|
5150
|
-
void system.deadLetter(source, undefined, event, 'missingTarget', {
|
|
5151
|
-
targetId: getAddressId(serialized.$actor),
|
|
5152
|
-
error
|
|
5153
|
-
});
|
|
5154
|
-
return;
|
|
5155
|
-
}
|
|
5156
|
-
target = live;
|
|
5157
|
-
}
|
|
5158
|
-
if (rejectUndeliverableEvent(source, target, event)) {
|
|
5159
|
-
return;
|
|
5160
|
-
}
|
|
5161
|
-
const runtimeTarget = target;
|
|
5162
|
-
runtimeTarget._send(event, source);
|
|
5163
|
-
}
|
|
5164
|
-
|
|
5165
|
-
/**
|
|
5166
|
-
* Returns the boundary error preventing this delivery, if any: an internal
|
|
5167
|
-
* event type cannot cross an actor boundary from an external sender.
|
|
5168
|
-
*/
|
|
5169
|
-
function getEventBoundaryError(source, target, event) {
|
|
5170
|
-
const runtimeTarget = target;
|
|
5171
|
-
if (source !== target && runtimeTarget.logic?.isInternalEventType?.(event.type)) {
|
|
5172
|
-
return new Error(`Internal event "${event.type}" cannot be sent to actor "${target.id}" from outside.`);
|
|
5173
|
-
}
|
|
5174
|
-
return undefined;
|
|
5175
|
-
}
|
|
5176
|
-
|
|
5177
|
-
/**
|
|
5178
|
-
* Rejects an event that must not cross the delivery boundary — an internal
|
|
5179
|
-
* event type sent from outside its owning actor — by reporting it as a dead
|
|
5180
|
-
* letter. Returns `true` when the event was rejected and must not be
|
|
5181
|
-
* delivered. Host runtimes must call this before taking ownership of
|
|
5182
|
-
* delivery.
|
|
5183
|
-
*/
|
|
5184
|
-
function rejectUndeliverableEvent(source, target, event) {
|
|
5185
|
-
const error = getEventBoundaryError(source, target, event);
|
|
5186
|
-
if (error) {
|
|
5187
|
-
void target.system.deadLetter(source, target, event, 'internalEvent', {
|
|
5188
|
-
error
|
|
5189
|
-
});
|
|
5190
|
-
return true;
|
|
5191
|
-
}
|
|
5192
|
-
return false;
|
|
5193
|
-
}
|
|
5194
|
-
|
|
5195
|
-
/**
|
|
5196
|
-
* Publishes an actor's terminal result, notifying its parent and observers.
|
|
5197
|
-
* Custom runtimes use this for the default local behavior inside their own
|
|
5198
|
-
* `terminateActor` implementations.
|
|
5199
|
-
*
|
|
5200
|
-
* @experimental
|
|
5201
|
-
*/
|
|
5202
|
-
function terminateActor(actor, termination) {
|
|
5203
|
-
actor._terminate(termination);
|
|
5204
|
-
}
|
|
5205
|
-
|
|
5206
|
-
/**
|
|
5207
|
-
* Stops an actor without producing a completion result. Custom runtimes use
|
|
5208
|
-
* this for the default local behavior inside their own `stopActor`
|
|
5209
|
-
* implementations.
|
|
5210
|
-
*
|
|
5211
|
-
* @experimental
|
|
5212
|
-
*/
|
|
5213
|
-
function stopActor(actor) {
|
|
5214
|
-
actor._stop();
|
|
5215
|
-
}
|
|
5216
|
-
|
|
5217
5243
|
let executionIdPrefix;
|
|
5218
5244
|
let nextExecutionId = 0;
|
|
5219
5245
|
function createExecutionIdPrefix() {
|
|
@@ -5409,6 +5435,8 @@ function bookSessionId(system) {
|
|
|
5409
5435
|
* @public
|
|
5410
5436
|
*/
|
|
5411
5437
|
|
|
5438
|
+
/** @public */
|
|
5439
|
+
|
|
5412
5440
|
/** @experimental */
|
|
5413
5441
|
|
|
5414
5442
|
/** @internal Every operation of `ActorSystemRuntime`, for runtime wrappers. */
|
|
@@ -5656,6 +5684,32 @@ class RuntimeSystem {
|
|
|
5656
5684
|
}
|
|
5657
5685
|
};
|
|
5658
5686
|
}
|
|
5687
|
+
onEmitted(listener) {
|
|
5688
|
+
const entry = emission => listener(emission);
|
|
5689
|
+
(this._emissionListeners ??= new Set()).add(entry);
|
|
5690
|
+
return {
|
|
5691
|
+
unsubscribe: () => {
|
|
5692
|
+
this._emissionListeners?.delete(entry);
|
|
5693
|
+
}
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5696
|
+
_notifyEmitted(actorRef, event) {
|
|
5697
|
+
const listeners = this._emissionListeners;
|
|
5698
|
+
if (!listeners?.size) {
|
|
5699
|
+
return;
|
|
5700
|
+
}
|
|
5701
|
+
const emission = {
|
|
5702
|
+
actorRef,
|
|
5703
|
+
event
|
|
5704
|
+
};
|
|
5705
|
+
for (const listener of Array.from(listeners)) {
|
|
5706
|
+
try {
|
|
5707
|
+
listener(emission);
|
|
5708
|
+
} catch (err) {
|
|
5709
|
+
reportUnhandledError(err);
|
|
5710
|
+
}
|
|
5711
|
+
}
|
|
5712
|
+
}
|
|
5659
5713
|
_hasInspectionObservers() {
|
|
5660
5714
|
return !!this._inspectionObservers?.size;
|
|
5661
5715
|
}
|
|
@@ -5858,6 +5912,17 @@ function createRuntimeSystem(rootActor, options) {
|
|
|
5858
5912
|
|
|
5859
5913
|
let executingCustomAction = false;
|
|
5860
5914
|
const emptyInspectionRecords = Object.freeze([]);
|
|
5915
|
+
const reservedMachineEventTypes = {
|
|
5916
|
+
'xstate.done.actor': true,
|
|
5917
|
+
'xstate.error.actor': true,
|
|
5918
|
+
'xstate.error.execution': true,
|
|
5919
|
+
'xstate.snapshot.actor': true,
|
|
5920
|
+
'xstate.done.state': true,
|
|
5921
|
+
'xstate.after': true,
|
|
5922
|
+
'xstate.timeout.state': true,
|
|
5923
|
+
'xstate.timeout.actor': true,
|
|
5924
|
+
[XSTATE_TIMER]: true
|
|
5925
|
+
};
|
|
5861
5926
|
|
|
5862
5927
|
// those values are currently used by @xstate/react directly so it's important to keep the assigned values in sync
|
|
5863
5928
|
let ProcessingStatus = /*#__PURE__*/function (ProcessingStatus) {
|
|
@@ -6029,6 +6094,7 @@ class Actor {
|
|
|
6029
6094
|
});
|
|
6030
6095
|
if (!parent && resolvedOptions._systemRef && !resolvedOptions._systemRef.current) {
|
|
6031
6096
|
resolvedOptions._systemRef.current = this.system;
|
|
6097
|
+
resolvedOptions._systemRef.initialize?.();
|
|
6032
6098
|
}
|
|
6033
6099
|
if (inspect && !parent) {
|
|
6034
6100
|
// Always inspect at the system-level
|
|
@@ -6132,7 +6198,7 @@ class Actor {
|
|
|
6132
6198
|
return stopChild;
|
|
6133
6199
|
}
|
|
6134
6200
|
get emit() {
|
|
6135
|
-
const emit = event =>
|
|
6201
|
+
const emit = event => publishEmittedEvent(this, event, this.system);
|
|
6136
6202
|
Object.defineProperty(this, 'emit', {
|
|
6137
6203
|
value: emit
|
|
6138
6204
|
});
|
|
@@ -6595,10 +6661,7 @@ class Actor {
|
|
|
6595
6661
|
const [nextSnapshot, effects] = nextState;
|
|
6596
6662
|
if (nextSnapshot === previousSnapshot && !effects.length &&
|
|
6597
6663
|
// State machine snapshots only; other logic may ignore events freely.
|
|
6598
|
-
'machine' in previousSnapshot && (
|
|
6599
|
-
// `xstate.route` is sent by users, so a route that matches nothing is
|
|
6600
|
-
// reported like any other unhandled event.
|
|
6601
|
-
!event.type.startsWith('xstate.') || event.type === 'xstate.route')) {
|
|
6664
|
+
'machine' in previousSnapshot && event.type !== XSTATE_STOP && (!sourceRef || !Object.hasOwn(reservedMachineEventTypes, event.type))) {
|
|
6602
6665
|
this._reportUnhandledEvent(event);
|
|
6603
6666
|
}
|
|
6604
6667
|
snapshot = nextSnapshot;
|
|
@@ -6929,6 +6992,9 @@ class Actor {
|
|
|
6929
6992
|
* @public
|
|
6930
6993
|
*/
|
|
6931
6994
|
function createActor(logic, ...[options]) {
|
|
6995
|
+
if (options && 'parent' in options) {
|
|
6996
|
+
throw new Error('The "parent" actor option was removed. Create child actors through invoke, spawn, or enq.spawn instead.');
|
|
6997
|
+
}
|
|
6932
6998
|
return new Actor(logic, options);
|
|
6933
6999
|
}
|
|
6934
7000
|
|
|
@@ -7017,6 +7083,7 @@ exports.matchesTimerKey = matchesTimerKey;
|
|
|
7017
7083
|
exports.mergeActorIdCounters = mergeActorIdCounters;
|
|
7018
7084
|
exports.parseDelayToMilliseconds = parseDelayToMilliseconds;
|
|
7019
7085
|
exports.parseGeneratedActorId = parseGeneratedActorId;
|
|
7086
|
+
exports.publishEmittedEvent = publishEmittedEvent;
|
|
7020
7087
|
exports.readTimerKey = readTimerKey;
|
|
7021
7088
|
exports.recordTimerStart = recordTimerStart;
|
|
7022
7089
|
exports.reserveChildId = reserveChildId;
|