xstate 6.0.0-beta.1 → 6.0.0-beta.2
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/dist/{createActor-551369be.development.esm.js → createActor-0250284a.development.esm.js} +244 -41
- package/dist/{createActor-cbe3abac.esm.js → createActor-20a97166.esm.js} +243 -40
- package/dist/{createActor-a49ae0ce.development.cjs.js → createActor-c029d29f.development.cjs.js} +245 -40
- package/dist/{createActor-25900f76.cjs.js → createActor-c91c9ffe.cjs.js} +244 -39
- package/dist/declarations/src/createActor.d.ts +3 -1
- package/dist/declarations/src/createMachine.d.ts +6 -3
- package/dist/declarations/src/durable/index.d.ts +43 -8
- package/dist/declarations/src/effectDescriptor.d.ts +2 -2
- package/dist/declarations/src/machineVersion.types.d.ts +2 -2
- package/dist/declarations/src/setup.d.ts +2 -2
- package/dist/declarations/src/systemTransition.d.ts +1 -1
- package/dist/declarations/src/types.d.ts +4 -2
- package/dist/declarations/src/types.v6.d.ts +37 -2
- package/dist/{effectDescriptor-f73da4ec.development.cjs.js → effectDescriptor-3fff5c51.development.cjs.js} +5 -5
- package/dist/{effectDescriptor-2fa27c54.cjs.js → effectDescriptor-47f73e7e.cjs.js} +5 -5
- package/dist/{effectDescriptor-321a8ce9.development.esm.js → effectDescriptor-5ac7dccb.development.esm.js} +5 -5
- package/dist/{effectDescriptor-d8b0f749.esm.js → effectDescriptor-e93df910.esm.js} +5 -5
- package/dist/{machineSchemas-88d01821.cjs.js → legacySnapshot-8d595687.cjs.js} +117 -1
- package/dist/{machineSchemas-45c21bc2.esm.js → legacySnapshot-9e06feb4.esm.js} +116 -2
- package/dist/{machineSchemas-b5736804.development.esm.js → legacySnapshot-ae6594a7.development.esm.js} +117 -3
- package/dist/{machineSchemas-6d96bef4.development.cjs.js → legacySnapshot-ebf0b2b8.development.cjs.js} +118 -2
- package/dist/xstate-actors.cjs.js +1 -1
- package/dist/xstate-actors.development.cjs.js +1 -1
- 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 +120 -10
- package/dist/xstate-durable.development.cjs.js +120 -10
- package/dist/xstate-durable.development.esm.js +120 -10
- package/dist/xstate-durable.esm.js +120 -10
- package/dist/xstate-durable.umd.min.js +1 -1
- package/dist/xstate-durable.umd.min.js.map +1 -1
- package/dist/xstate-fsm.cjs.js +4 -4
- package/dist/xstate-fsm.development.cjs.js +4 -4
- package/dist/xstate-fsm.development.esm.js +4 -4
- package/dist/xstate-fsm.esm.js +4 -4
- package/dist/xstate-fsm.umd.min.js +1 -1
- package/dist/xstate-fsm.umd.min.js.map +1 -1
- package/dist/xstate-graph.cjs.js +1 -1
- package/dist/xstate-graph.development.cjs.js +1 -1
- package/dist/xstate-graph.development.esm.js +2 -2
- package/dist/xstate-graph.esm.js +2 -2
- package/dist/xstate-graph.umd.min.js +1 -1
- package/dist/xstate-graph.umd.min.js.map +1 -1
- package/dist/xstate-persist.cjs.js +23 -12
- package/dist/xstate-persist.development.cjs.js +23 -12
- package/dist/xstate-persist.development.esm.js +17 -6
- package/dist/xstate-persist.esm.js +17 -6
- package/dist/xstate-persist.umd.min.js +1 -1
- package/dist/xstate-persist.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 +63 -51
- package/dist/xstate.development.cjs.js +63 -51
- package/dist/xstate.development.esm.js +53 -41
- package/dist/xstate.esm.js +53 -41
- package/dist/xstate.umd.min.js +1 -1
- package/dist/xstate.umd.min.js.map +1 -1
- package/docs/api-stability.json +267 -249
- package/docs/backend-workflows.md +3 -3
- package/docs/durable-execution.md +72 -48
- package/docs/errors.md +2 -2
- package/docs/examples.md +34 -34
- package/docs/fsm.md +2 -1
- package/docs/guards.md +1 -1
- package/docs/index.md +1 -1
- package/docs/invoke.md +34 -2
- package/docs/persistence.md +16 -10
- package/docs/serialization.md +1 -1
- package/docs/setup-and-provide.md +3 -3
- package/docs/spawn.md +1 -1
- package/docs/system-transitions.md +6 -6
- package/docs/tutorial.md +5 -5
- package/docs/typescript.md +9 -0
- package/docs/xstate-v5-to-v6.md +17 -17
- package/package.json +1 -1
|
@@ -56,7 +56,7 @@ The same rule applies to writes the workflow makes. Pass an idempotency key to t
|
|
|
56
56
|
|
|
57
57
|
## Durable steps
|
|
58
58
|
|
|
59
|
-
Async logic that runs several external calls can record each one on the snapshot with `enq.step(...)
|
|
59
|
+
Async logic that runs several external calls can record each one on the snapshot with `enq.step(...)`. This page's pattern is hostless durability, where the snapshot is the journal, and `enq.step` is built for it (on a [durable host](durable-execution.md), write plain promise actors instead):
|
|
60
60
|
|
|
61
61
|
```ts
|
|
62
62
|
const fulfillOrder = createAsyncLogic({
|
|
@@ -91,7 +91,7 @@ Store the version alongside the snapshot so a restored workflow is either migrat
|
|
|
91
91
|
|
|
92
92
|
## Request-response endpoints
|
|
93
93
|
|
|
94
|
-
Some endpoints have to answer with the result, not
|
|
94
|
+
Some endpoints have to answer with the result, not only acknowledge the event. Start the actor, send the event and wait for the snapshot you need:
|
|
95
95
|
|
|
96
96
|
```ts
|
|
97
97
|
import { waitFor } from 'xstate';
|
|
@@ -111,7 +111,7 @@ const settled = await waitFor(
|
|
|
111
111
|
|
|
112
112
|
## Long delays across restarts
|
|
113
113
|
|
|
114
|
-
Pending delayed transitions are part of the snapshot: `snapshot.timers` lists each one with its declared delay and the event it will deliver. A snapshot persisted from a running actor also records each timer's wall-clock start (`scheduledAt`), and restoring it schedules the remaining time toward the original deadline
|
|
114
|
+
Pending delayed transitions are part of the snapshot: `snapshot.timers` lists each one with its declared delay and the event it will deliver. A snapshot persisted from a running actor also records each timer's wall-clock start (`scheduledAt`), and restoring it schedules the remaining time toward the original deadline. A timer already past due fires immediately, so a three-day `after` survives deploys. The deadline only holds while some process restores the actor and stays up; for timers that must fire with no process running, use a durable host that owns timer scheduling.
|
|
115
115
|
|
|
116
116
|
For deadlines that must respect real time, persist the scheduled and due times next to the snapshot and have the host (a scheduler, a queue with a visibility timeout, or a cron job) deliver the event when it is due. Keep short delays such as debounces and retry backoffs inside the machine, where restarting the delay has little effect.
|
|
117
117
|
|
|
@@ -39,8 +39,8 @@ const durable = createDurable(machine, {
|
|
|
39
39
|
const output = await durable.run(input);
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
Runtime operations you omit keep their local behavior
|
|
43
|
-
children run in this process
|
|
42
|
+
Runtime operations you omit keep their local behavior. For example, spawned machine
|
|
43
|
+
children run in this process.
|
|
44
44
|
|
|
45
45
|
`enqueueRootEvent` is the narrow hook most co-located hosts need: it receives
|
|
46
46
|
events sent to the execution root while the loop is parked, or at any time
|
|
@@ -53,8 +53,8 @@ runtime.
|
|
|
53
53
|
The primary durable unit for async work is the actor itself: a developer
|
|
54
54
|
writes a normal promise (`fromPromise`, `createAsyncLogic`) and invokes it;
|
|
55
55
|
the `runLogic` operation hands the host the whole body as one journal
|
|
56
|
-
entry. The actor's identity
|
|
57
|
-
`input`
|
|
56
|
+
entry. The actor's identity (`address`, string `src` key and serializable
|
|
57
|
+
`input`) crosses any boundary, so a host either wraps the provided `exec`
|
|
58
58
|
in its own step primitive, or ignores it entirely and re-runs the
|
|
59
59
|
registered logic on a remote executor from `(src, input)` alone; the output
|
|
60
60
|
flows back as the actor's ordinary completion event. No step vocabulary
|
|
@@ -65,7 +65,7 @@ runLogic: (actor, exec) => ctx.run(actor.address, exec) // in-process j
|
|
|
65
65
|
runLogic: (actor) => activities.runLogic(actor.src, actor.getSnapshot().input) // remote executor
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
Custom actions follow the same rule
|
|
68
|
+
Custom actions follow the same rule: durability crosses a boundary as
|
|
69
69
|
declared identity plus serializable data, never as a closure. An action
|
|
70
70
|
enqueued as a named function with serializable arguments
|
|
71
71
|
(`enq(notifyApprover, context.orderId)`) carries `(type, args)` in its
|
|
@@ -75,19 +75,19 @@ descriptor, so a remote-executor host can dispatch it worker-side and ignore
|
|
|
75
75
|
by its effect ID. Work heavy enough to deserve an activity is usually an
|
|
76
76
|
actor, not an action.
|
|
77
77
|
|
|
78
|
-
For finer granularity
|
|
78
|
+
For finer granularity (several independently-journaled awaits), split into
|
|
79
79
|
separate invoked actors: each actor is its own journal entry, and the states
|
|
80
80
|
between them make the progress visible. Do not reach for `enq.step` on a
|
|
81
|
-
durable host; it is the [hostless durability](actor-logic.md) tool
|
|
82
|
-
snapshot is its journal
|
|
81
|
+
durable host; it is the [hostless durability](actor-logic.md) tool (the
|
|
82
|
+
snapshot is its journal), and on a durable host it only duplicates what
|
|
83
83
|
`runLogic` already provides. When restored snapshots that carry step results
|
|
84
84
|
do run under a durable host, `enq.step` routes through the `runStep`
|
|
85
85
|
operation:
|
|
86
|
-
implement it to journal steps in the host's journal
|
|
87
|
-
replays without re-running the step
|
|
86
|
+
implement it to journal steps in the host's journal (a memoized result
|
|
87
|
+
replays without re-running the step), and the built-in
|
|
88
88
|
snapshot memoization steps aside. Unlike other operations, a step is an
|
|
89
89
|
orchestration frame that may itself await runtime operations of the same
|
|
90
|
-
execution, so it is never serialized behind them
|
|
90
|
+
execution, so it is never serialized behind them. For the same reason,
|
|
91
91
|
`executeEffects` resolves without awaiting it. The `runStep` helper exported
|
|
92
92
|
from `xstate` exposes the built-in behavior. `exec` is a closure over live
|
|
93
93
|
execution state: run it in this process and journal its result; it cannot be
|
|
@@ -99,7 +99,7 @@ either race its durable wait against the pending `runLogic`/`runStep`
|
|
|
99
99
|
promises it created (the natural shape when they map to the engine's own
|
|
100
100
|
step primitives), or drain them to empty first. An in-flight step ends by
|
|
101
101
|
completing its actor, and that completion event cannot satisfy a durable
|
|
102
|
-
wait that was already registered with the engine
|
|
102
|
+
wait that was already registered with the engine, so the run deadlocks until
|
|
103
103
|
its idle timeout. The in-process loop above needs none of this, because any
|
|
104
104
|
later event resolves its wait. Operations initiated by live child
|
|
105
105
|
actors (parent sends, timers, terminations) route to your implementations
|
|
@@ -120,19 +120,38 @@ while (state.status === 'active') {
|
|
|
120
120
|
reporting up), and only when none are queued defers to the adapter's durable
|
|
121
121
|
wait.
|
|
122
122
|
|
|
123
|
-
Checkpoint with `
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
Checkpoint with `durable.checkpoint(state)` after `executeEffects` resolves,
|
|
124
|
+
and persist the whole result atomically. A checkpoint holds three things:
|
|
125
|
+
|
|
126
|
+
- `snapshot`: the persisted snapshot. `checkpoint(state, options)` passes
|
|
127
|
+
`options` to the logic's `getPersistedSnapshot`.
|
|
128
|
+
- `nextTransitionIndex`: the index the next transition gets.
|
|
129
|
+
- `inbox`: root-addressed events that settled batches captured and
|
|
130
|
+
`waitForEvent()` has not handed out yet, such as a child's
|
|
131
|
+
`xstate.done.actor` or an event a child sent to its parent. Each entry's
|
|
132
|
+
`source` is the sender's address, `{ '@actor': address }`.
|
|
133
|
+
|
|
134
|
+
Without the inbox, an event captured in the last batch exists only in memory.
|
|
135
|
+
A child that completed during that batch is not restarted on restore, so its
|
|
136
|
+
completion would never arrive.
|
|
137
|
+
|
|
138
|
+
Inbox event payloads are stored as they are and must be serializable data,
|
|
139
|
+
like timer events in a persisted snapshot. Child snapshot updates
|
|
140
|
+
(`xstate.snapshot.actor`) are not stored: a restored active child reports its
|
|
141
|
+
current snapshot when it starts. Completion, error and timeout events for a
|
|
142
|
+
child that is no longer in the snapshot, or that a later transition replaced,
|
|
143
|
+
are dropped.
|
|
144
|
+
|
|
145
|
+
Restore the checkpoint on a fresh execution before entering the loop:
|
|
127
146
|
|
|
128
147
|
<!-- checkpoint restoration contract from packages/core/src/durable/index.ts -->
|
|
129
148
|
|
|
130
149
|
```ts
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
let [state, effects] = resumed.restore(
|
|
150
|
+
await db.save(durable.checkpoint(state));
|
|
151
|
+
|
|
152
|
+
// later, on any worker
|
|
153
|
+
const resumed = createDurable(machine, adapter);
|
|
154
|
+
let [state, effects] = resumed.restore(await db.load());
|
|
136
155
|
await resumed.executeEffects(effects);
|
|
137
156
|
|
|
138
157
|
while (state.status === 'active') {
|
|
@@ -141,6 +160,12 @@ while (state.status === 'active') {
|
|
|
141
160
|
}
|
|
142
161
|
```
|
|
143
162
|
|
|
163
|
+
The resumed execution continues from the checkpoint's `nextTransitionIndex`,
|
|
164
|
+
and `waitForEvent()` hands out the inbox before the adapter's wait. If the
|
|
165
|
+
adapter also sets `transitionIndex`, it must equal the checkpoint's
|
|
166
|
+
`nextTransitionIndex`. `restore()` also accepts a bare persisted snapshot; pass
|
|
167
|
+
`transitionIndex` to `createDurable` in that case.
|
|
168
|
+
|
|
144
169
|
`restore()` rehydrates the snapshot and attaches the adapter across the actor
|
|
145
170
|
tree. It returns effects that start active embedded children and re-arm pending
|
|
146
171
|
root timers. Nothing starts until `executeEffects()` runs. Completed children
|
|
@@ -188,14 +213,14 @@ of a local child. Session ids embed a random per-process system id, so a
|
|
|
188
213
|
host that journals the execution's own events (rather than only external
|
|
189
214
|
ones) must pin `executionId` on the adapter: session ids become
|
|
190
215
|
`<executionId>:<n>`, a deterministic function of actor-creation order, and a
|
|
191
|
-
replayed execution re-creates the same ids
|
|
216
|
+
replayed execution re-creates the same ids, so a journaled completion still
|
|
192
217
|
matches the child the replay re-creates. Uniqueness across executions is the
|
|
193
218
|
host's responsibility. For a remote child the owning runtime is the authority on
|
|
194
219
|
staleness by default; a host that stores an opaque `incarnation` token on
|
|
195
220
|
the persisted child entry gets the same guard on the referencing side. The
|
|
196
221
|
token is the owner's `sessionId` for the child and the restored handle
|
|
197
|
-
carries it as its own `sessionId
|
|
198
|
-
ref that knows its incarnation compares it
|
|
222
|
+
carries it as its own `sessionId`. A
|
|
223
|
+
ref that knows its incarnation compares it; one that does not defers to the
|
|
199
224
|
owner. `sendTo` effect descriptors journal the target's token so a send
|
|
200
225
|
replayed after the address was reincarnated is detectable.
|
|
201
226
|
|
|
@@ -277,11 +302,11 @@ timer is no longer pending is ignored.
|
|
|
277
302
|
ordered effects with stable IDs such as `0:0` and `1:0`, and event waits with
|
|
278
303
|
IDs such as `event:0`. Memoize or deduplicate each operation using that ID:
|
|
279
304
|
replaying the same events from the beginning reconstructs the same snapshots,
|
|
280
|
-
effects, waits and IDs
|
|
281
|
-
|
|
305
|
+
effects, waits and IDs. This means the same effects in the same order, not
|
|
306
|
+
only the same resulting snapshot, which is what hosts that match journal entries
|
|
282
307
|
positionally (Temporal, Restate) depend on. Each `DurableEffect` also carries a serializable
|
|
283
|
-
`descriptor`
|
|
284
|
-
source keys
|
|
308
|
+
`descriptor` for journaling, with actor references replaced by `{ '@actor': address, incarnation? }` and actor sources by
|
|
309
|
+
source keys; payload fields such as `event` and `input` pass
|
|
285
310
|
through by reference and are only as serializable as their values.
|
|
286
311
|
|
|
287
312
|
Every inter-actor edge is an asynchronous handoff to the runtime.
|
|
@@ -305,9 +330,9 @@ pairwise sender-to-receiver ordering: for a given pair of actors, events sent
|
|
|
305
330
|
from the first to the second are enqueued in send order, and an undeliverable
|
|
306
331
|
event is dead-lettered rather than retried. This matches the Erlang and Akka
|
|
307
332
|
defaults. A dead letter is reported through the `deadLetter` runtime
|
|
308
|
-
operation (and the `onRejectedEvent` hook)
|
|
333
|
+
operation (and the `onRejectedEvent` hook). This is for observability, not
|
|
309
334
|
retry. Ordering is not transitive
|
|
310
|
-
across intermediaries. The durable path is stronger
|
|
335
|
+
across intermediaries. The durable path is stronger: the handoff queue serializes
|
|
311
336
|
every operation of an execution globally. A host `sendEvent` that routes
|
|
312
337
|
remotely is responsible for preserving pairwise ordering on its transport.
|
|
313
338
|
|
|
@@ -320,11 +345,11 @@ children. Incarnations of local children do not survive persist and restore
|
|
|
320
345
|
unless `executionId` is pinned.
|
|
321
346
|
|
|
322
347
|
Because every handoff queues, a runtime operation must never await another
|
|
323
|
-
runtime operation of the same execution through the actor system
|
|
348
|
+
runtime operation of the same execution through the actor system, because it would
|
|
324
349
|
wait behind itself. The `deliverEvent`, `stopActor` and `terminateActor`
|
|
325
350
|
helpers exported from `xstate` expose the local behaviors and are always safe
|
|
326
351
|
to call directly. An implemented runtime operation replaces the local
|
|
327
|
-
behavior entirely, including its bookkeeping
|
|
352
|
+
behavior entirely, including its bookkeeping. A `stopActor` that only journals
|
|
328
353
|
must call the `stopActor` helper for the local stop cascade, and a `sendEvent`
|
|
329
354
|
that routes locally must call `deliverEvent`. When a remote runtime can
|
|
330
355
|
deliver the same completion more than once (retries, replays), the host is
|
|
@@ -332,7 +357,7 @@ responsible for deduplicating before handing it to the execution.
|
|
|
332
357
|
|
|
333
358
|
By default, events addressed to the root actor do not reach `sendEvent` during
|
|
334
359
|
`executeEffects`: the execution captures and retains them, and
|
|
335
|
-
`waitForEvent()` hands them out
|
|
360
|
+
`waitForEvent()` hands them out in capture order before deferring to the
|
|
336
361
|
adapter. While the loop is parked in the adapter's wait, a root-addressed
|
|
337
362
|
event reaches `enqueueRootEvent`, or `sendEvent` when the adapter implements
|
|
338
363
|
the broader routing override, and belongs in the host's mailbox. Producing one
|
|
@@ -360,7 +385,7 @@ Change the state of other actors only at the top of the drive loop.
|
|
|
360
385
|
|
|
361
386
|
### Journaling rules
|
|
362
387
|
|
|
363
|
-
A journaled step's closure must contain only the external side effect
|
|
388
|
+
A journaled step's closure must contain only the external side effect:
|
|
364
389
|
nothing that re-enters the engine's journal, and nothing that mutates local
|
|
365
390
|
actors.
|
|
366
391
|
|
|
@@ -373,8 +398,8 @@ actors.
|
|
|
373
398
|
the runtime operations make.
|
|
374
399
|
- **Local actor mutations stay outside the journal.** A journaled closure is
|
|
375
400
|
skipped on replay: calling `deliverEvent`, `stopActor`, or starting an
|
|
376
|
-
actor inside one rebuilds a different actor tree than the recorded run
|
|
377
|
-
silently
|
|
401
|
+
actor inside one rebuilds a different actor tree than the recorded run,
|
|
402
|
+
silently and with no error. Re-run local mutations unjournaled on every
|
|
378
403
|
replay; journal only work that must not run twice.
|
|
379
404
|
|
|
380
405
|
A per-effect `runtime(metadata, effect)` factory is available for hosts that
|
|
@@ -396,7 +421,7 @@ I/O:
|
|
|
396
421
|
different effect sequence on replay, and nothing detects the divergence.
|
|
397
422
|
Perform such work inside journaled operations (the host's `executeAction`,
|
|
398
423
|
where the recorded result replays), or derive values deterministically from
|
|
399
|
-
what the snapshot already carries
|
|
424
|
+
what the snapshot already carries. Addresses and effect IDs are stable
|
|
400
425
|
across replays and make good seeds and idempotency keys.
|
|
401
426
|
|
|
402
427
|
```ts
|
|
@@ -416,27 +441,26 @@ entry: ({ context }, enq) => {
|
|
|
416
441
|
`getPersistedSnapshot(snapshot)` embeds each child's persisted state: the
|
|
417
442
|
whole-tree checkpoint of a runtime that co-locates the tree.
|
|
418
443
|
`getPersistedSnapshot(snapshot, { embedChildren: false })` instead references
|
|
419
|
-
children by logical address, leaving each child's state
|
|
420
|
-
subtree
|
|
444
|
+
children by logical address, leaving each child's state (including its own
|
|
445
|
+
subtree) with the runtime that owns it; the option applies to the whole
|
|
421
446
|
tree, not per placement boundary. Restoring an address-only child produces a
|
|
422
447
|
location-transparent handle: sends route through the system runtime, its
|
|
423
448
|
snapshot exposes lifecycle only (a full snapshot is the last value an actor
|
|
424
449
|
published, which only co-located actors observe), `subscribe` and `on` are
|
|
425
450
|
inert (observation is co-location), and completion staleness for it is the
|
|
426
451
|
owning runtime's responsibility. Persisting a handle from the referencing
|
|
427
|
-
side throws
|
|
428
|
-
persisted address verbatim
|
|
452
|
+
side throws, because its state lives with the runtime that owns it. A restored handle keeps its
|
|
453
|
+
persisted address verbatim as the owning runtime's identity for the child.
|
|
429
454
|
|
|
430
455
|
`durable.getActorRef(snapshot)` returns the root actor reference behind a
|
|
431
456
|
snapshot this execution produced, for addressing and inspection.
|
|
432
457
|
`durable.getActorRef(snapshot, address)` resolves a logical address against
|
|
433
|
-
the snapshot's live actor tree instead
|
|
434
|
-
stores addresses as strings and must deliver to the actor behind one
|
|
435
|
-
returning `undefined` when no live actor has that address (a timer firing
|
|
458
|
+
the snapshot's live actor tree instead. Use it in hosts whose mailbox
|
|
459
|
+
stores addresses as strings and must deliver to the actor behind one. It returns `undefined` when no live actor has that address (a timer firing
|
|
436
460
|
for an actor that already completed).
|
|
437
461
|
|
|
438
462
|
`createDurable(logic, adapter, { inspect })` observes the execution's
|
|
439
|
-
inspection events
|
|
463
|
+
inspection events (the `@xstate.actor` / `@xstate.transition` protocol)
|
|
440
464
|
across the whole live actor tree, including transitions computed by the pure
|
|
441
465
|
path. This is host observability, not part of the durable contract: use it
|
|
442
466
|
for operation logs, tracing and test instrumentation, and keep the adapter
|
|
@@ -447,7 +471,7 @@ itself pure physics.
|
|
|
447
471
|
When the machine declares a runtime validator, an external event whose payload
|
|
448
472
|
fails its schema is rejected at the boundary: `transition()` returns the
|
|
449
473
|
snapshot unchanged together with a `@xstate.deadLetter` effect, and never
|
|
450
|
-
throws. Replay stays total
|
|
474
|
+
throws. Replay stays total: replaying a poisoned queued event produces the
|
|
451
475
|
same unchanged snapshot and the same rejection effect every time.
|
|
452
476
|
|
|
453
477
|
The effect executes through the `deadLetter` runtime operation, so a host
|
|
@@ -491,8 +515,8 @@ the effects and the runtime installation. `createActor` is the built-in
|
|
|
491
515
|
in-process runtime over the same pure relations; `createDurable` is the
|
|
492
516
|
journaled one.
|
|
493
517
|
|
|
494
|
-
Effects are plain objects
|
|
495
|
-
`exec(runtime)` method
|
|
518
|
+
Effects are plain objects. Each has a serializable `descriptor` and an
|
|
519
|
+
`exec(runtime)` method, so a host may take `[snapshot, effects]` from the
|
|
496
520
|
pure APIs and execute them entirely its own way. Dropping `executeEffects`
|
|
497
521
|
means providing its four services yourself: transitive settlement (an
|
|
498
522
|
effect's operations trigger further operations; "executed" must mean the
|
package/docs/errors.md
CHANGED
|
@@ -551,7 +551,7 @@ Unable to send event to actor '{address}' (incarnation '{incarnation}'): no live
|
|
|
551
551
|
|
|
552
552
|
Values: the address, and the incarnation when the reference has one.
|
|
553
553
|
|
|
554
|
-
A serialized actor reference (`{
|
|
554
|
+
A serialized actor reference (`{ '@actor' }`) points to an actor that is not running in this system, or to an earlier incarnation of it. Check that the actor is running, or refresh the stored reference.
|
|
555
555
|
|
|
556
556
|
## E51
|
|
557
557
|
|
|
@@ -616,7 +616,7 @@ Values: the address.
|
|
|
616
616
|
## E57
|
|
617
617
|
|
|
618
618
|
```text
|
|
619
|
-
State key "{key}" contains "."
|
|
619
|
+
State key "{key}" contains ".". Nest the states or escape it as "{escapedKey}".
|
|
620
620
|
```
|
|
621
621
|
|
|
622
622
|
Values: the state key.
|
package/docs/examples.md
CHANGED
|
@@ -9,61 +9,61 @@ These examples use XState v6 syntax: transition functions that return `{ target,
|
|
|
9
9
|
|
|
10
10
|
## Start here
|
|
11
11
|
|
|
12
|
-
- [counter](https://github.com/statelyai/xstate/tree/next/examples/counter)
|
|
13
|
-
- [toggle](https://github.com/statelyai/xstate/tree/next/examples/toggle)
|
|
14
|
-
- [fetch](https://github.com/statelyai/xstate/tree/next/examples/fetch)
|
|
12
|
+
- [counter](https://github.com/statelyai/xstate/tree/next/examples/counter): the smallest useful machine: `increment` and `decrement` transitions updating one context value, wired to plain DOM.
|
|
13
|
+
- [toggle](https://github.com/statelyai/xstate/tree/next/examples/toggle): two states and one event, with no context at all.
|
|
14
|
+
- [fetch](https://github.com/statelyai/xstate/tree/next/examples/fetch): `idle` / `loading` / `success` / `failure` around `createAsyncLogic(...)` invoked by the `loading` state.
|
|
15
15
|
|
|
16
16
|
## UI patterns
|
|
17
17
|
|
|
18
|
-
- [stopwatch](https://github.com/statelyai/xstate/tree/next/examples/stopwatch)
|
|
19
|
-
- [timer](https://github.com/statelyai/xstate/tree/next/examples/timer)
|
|
20
|
-
- [tiles](https://github.com/statelyai/xstate/tree/next/examples/tiles)
|
|
21
|
-
- [snake-react](https://github.com/statelyai/xstate/tree/next/examples/snake-react)
|
|
22
|
-
- [tic-tac-toe-react](https://github.com/statelyai/xstate/tree/next/examples/tic-tac-toe-react)
|
|
23
|
-
- [todomvc-react](https://github.com/statelyai/xstate/tree/next/examples/todomvc-react)
|
|
24
|
-
- [friends-list-react](https://github.com/statelyai/xstate/tree/next/examples/friends-list-react)
|
|
25
|
-
- [trivia-game-example](https://github.com/statelyai/xstate/tree/next/examples/trivia-game-example)
|
|
18
|
+
- [stopwatch](https://github.com/statelyai/xstate/tree/next/examples/stopwatch): `createCallbackLogic(...)` invoked by the `running` state sends a tick every 10ms; leaving the state clears the interval.
|
|
19
|
+
- [timer](https://github.com/statelyai/xstate/tree/next/examples/timer): the same ticking pattern, plus events that adjust the duration while the timer is stopped.
|
|
20
|
+
- [tiles](https://github.com/statelyai/xstate/tree/next/examples/tiles): a sliding tile puzzle with named guards deciding whether two tiles are adjacent.
|
|
21
|
+
- [snake-react](https://github.com/statelyai/xstate/tree/next/examples/snake-react): a game loop as an invoked callback actor, with the whole board in context.
|
|
22
|
+
- [tic-tac-toe-react](https://github.com/statelyai/xstate/tree/next/examples/tic-tac-toe-react): turn taking and win detection in a `playing` state, with a `RESET` transition on the machine root.
|
|
23
|
+
- [todomvc-react](https://github.com/statelyai/xstate/tree/next/examples/todomvc-react): the TodoMVC spec, with the todo list and the active filter held in context.
|
|
24
|
+
- [friends-list-react](https://github.com/statelyai/xstate/tree/next/examples/friends-list-react): a parent machine that spawns one child actor per friend and keeps their refs in context.
|
|
25
|
+
- [trivia-game-example](https://github.com/statelyai/xstate/tree/next/examples/trivia-game-example): a quiz whose navigation actions are supplied with `machine.provide(...)` so the machine stays free of router imports.
|
|
26
26
|
|
|
27
27
|
## 7GUIs
|
|
28
28
|
|
|
29
29
|
Ports of the [7GUIs](https://eugenkiss.github.io/7guis/) benchmark tasks, in React and Vue:
|
|
30
30
|
|
|
31
|
-
- [7guis-counter-react](https://github.com/statelyai/xstate/tree/next/examples/7guis-counter-react) and [7guis-1-counter-vue](https://github.com/statelyai/xstate/tree/next/examples/7guis-1-counter-vue)
|
|
32
|
-
- [7guis-temperature-react](https://github.com/statelyai/xstate/tree/next/examples/7guis-temperature-react) and [7guis-2-temperature-vue](https://github.com/statelyai/xstate/tree/next/examples/7guis-2-temperature-vue)
|
|
33
|
-
- [7guis-flight-booker-react](https://github.com/statelyai/xstate/tree/next/examples/7guis-flight-booker-react)
|
|
31
|
+
- [7guis-counter-react](https://github.com/statelyai/xstate/tree/next/examples/7guis-counter-react) and [7guis-1-counter-vue](https://github.com/statelyai/xstate/tree/next/examples/7guis-1-counter-vue): the counter task.
|
|
32
|
+
- [7guis-temperature-react](https://github.com/statelyai/xstate/tree/next/examples/7guis-temperature-react) and [7guis-2-temperature-vue](https://github.com/statelyai/xstate/tree/next/examples/7guis-2-temperature-vue): two fields that convert into each other without looping.
|
|
33
|
+
- [7guis-flight-booker-react](https://github.com/statelyai/xstate/tree/next/examples/7guis-flight-booker-react): trip type, date validation and a booking request as invoked async logic.
|
|
34
34
|
|
|
35
35
|
## XState Store
|
|
36
36
|
|
|
37
37
|
[XState Store](choose-xstate.md) examples, for state that needs events but not states:
|
|
38
38
|
|
|
39
|
-
- [store-counter-react](https://github.com/statelyai/xstate/tree/next/examples/store-counter-react)
|
|
40
|
-
- [local-store-counter-react](https://github.com/statelyai/xstate/tree/next/examples/local-store-counter-react)
|
|
41
|
-
- [store-tic-tac-toe](https://github.com/statelyai/xstate/tree/next/examples/store-tic-tac-toe)
|
|
39
|
+
- [store-counter-react](https://github.com/statelyai/xstate/tree/next/examples/store-counter-react): a module-level store read with `useSelector`.
|
|
40
|
+
- [local-store-counter-react](https://github.com/statelyai/xstate/tree/next/examples/local-store-counter-react): the same store created per component with `useStore`.
|
|
41
|
+
- [store-tic-tac-toe](https://github.com/statelyai/xstate/tree/next/examples/store-tic-tac-toe): a full game in one store, including derived win and draw outcomes.
|
|
42
42
|
|
|
43
43
|
## Backends and persistence
|
|
44
44
|
|
|
45
|
-
- [express-workflow](https://github.com/statelyai/xstate/tree/next/examples/express-workflow)
|
|
46
|
-
- [persisted-donut-maker](https://github.com/statelyai/xstate/tree/next/examples/persisted-donut-maker)
|
|
47
|
-
- [mongodb-persisted-state](https://github.com/statelyai/xstate/tree/next/examples/mongodb-persisted-state)
|
|
48
|
-
- [mongodb-credit-check-api](https://github.com/statelyai/xstate/tree/next/examples/mongodb-credit-check-api)
|
|
45
|
+
- [express-workflow](https://github.com/statelyai/xstate/tree/next/examples/express-workflow): an Express API that starts a workflow, restores it from a persisted snapshot on each request, sends the event and persists the result. The pattern behind [backend workflows](backend-workflows.md).
|
|
46
|
+
- [persisted-donut-maker](https://github.com/statelyai/xstate/tree/next/examples/persisted-donut-maker): a multi-step recipe machine whose snapshot is written to disk and restored on the next run.
|
|
47
|
+
- [mongodb-persisted-state](https://github.com/statelyai/xstate/tree/next/examples/mongodb-persisted-state): the same machine with MongoDB as the snapshot store, plus a queue that serializes writes.
|
|
48
|
+
- [mongodb-credit-check-api](https://github.com/statelyai/xstate/tree/next/examples/mongodb-credit-check-api): a credit check API where each external service is `createAsyncLogic(...)` with a Zod input schema.
|
|
49
49
|
|
|
50
50
|
## Serverless Workflow ports
|
|
51
51
|
|
|
52
52
|
Ports of the [Serverless Workflow specification](https://github.com/serverlessworkflow/specification/tree/main/examples) examples, each a single runnable `main.ts`. They are the densest collection of workflow shapes in the repository. Use them to see how a particular orchestration pattern looks as a statechart.
|
|
53
53
|
|
|
54
|
-
- [workflow-hello](https://github.com/statelyai/xstate/tree/next/examples/workflow-hello)
|
|
55
|
-
- [workflow-async-function](https://github.com/statelyai/xstate/tree/next/examples/workflow-async-function)
|
|
56
|
-
- [workflow-parallel](https://github.com/statelyai/xstate/tree/next/examples/workflow-parallel)
|
|
57
|
-
- [workflow-event-based](https://github.com/statelyai/xstate/tree/next/examples/workflow-event-based)
|
|
58
|
-
- [workflow-check-inbox](https://github.com/statelyai/xstate/tree/next/examples/workflow-check-inbox)
|
|
59
|
-
- [workflow-credit-check](https://github.com/statelyai/xstate/tree/next/examples/workflow-credit-check) and [workflow-applicant-request](https://github.com/statelyai/xstate/tree/next/examples/workflow-applicant-request)
|
|
60
|
-
- [workflow-book-lending](https://github.com/statelyai/xstate/tree/next/examples/workflow-book-lending)
|
|
61
|
-
- [workflow-provision-orders](https://github.com/statelyai/xstate/tree/next/examples/workflow-provision-orders)
|
|
62
|
-
- [workflow-purchase-order-deadline](https://github.com/statelyai/xstate/tree/next/examples/workflow-purchase-order-deadline)
|
|
63
|
-
- [workflow-car-vitals](https://github.com/statelyai/xstate/tree/next/examples/workflow-car-vitals)
|
|
64
|
-
- [workflow-accumulate-room-readings](https://github.com/statelyai/xstate/tree/next/examples/workflow-accumulate-room-readings)
|
|
65
|
-
- [workflow-monitor-patient](https://github.com/statelyai/xstate/tree/next/examples/workflow-monitor-patient)
|
|
66
|
-
- [workflow-media-scanner](https://github.com/statelyai/xstate/tree/next/examples/workflow-media-scanner)
|
|
54
|
+
- [workflow-hello](https://github.com/statelyai/xstate/tree/next/examples/workflow-hello): the minimum: one final state with `output`.
|
|
55
|
+
- [workflow-async-function](https://github.com/statelyai/xstate/tree/next/examples/workflow-async-function): one invoked async function with typed input taken from the workflow's own input.
|
|
56
|
+
- [workflow-parallel](https://github.com/statelyai/xstate/tree/next/examples/workflow-parallel): two branches running at once, joining when both are done.
|
|
57
|
+
- [workflow-event-based](https://github.com/statelyai/xstate/tree/next/examples/workflow-event-based): waiting for one of several events, with a named delay as the deadline.
|
|
58
|
+
- [workflow-check-inbox](https://github.com/statelyai/xstate/tree/next/examples/workflow-check-inbox): a root-level callback actor that sends a `reminder` event on an interval, driving a check-then-notify cycle back to `Idle`.
|
|
59
|
+
- [workflow-credit-check](https://github.com/statelyai/xstate/tree/next/examples/workflow-credit-check) and [workflow-applicant-request](https://github.com/statelyai/xstate/tree/next/examples/workflow-applicant-request): decision workflows that branch on the result of an invoked service.
|
|
60
|
+
- [workflow-book-lending](https://github.com/statelyai/xstate/tree/next/examples/workflow-book-lending): a long-running flow that waits on human events between service calls, with a delayed transition for the wait and a nested checkout region.
|
|
61
|
+
- [workflow-provision-orders](https://github.com/statelyai/xstate/tree/next/examples/workflow-provision-orders): order provisioning with a nested `Exception` state whose children handle each kind of missing field.
|
|
62
|
+
- [workflow-purchase-order-deadline](https://github.com/statelyai/xstate/tree/next/examples/workflow-purchase-order-deadline): a deadline on the whole order, with retries handled by an external retry policy library.
|
|
63
|
+
- [workflow-car-vitals](https://github.com/statelyai/xstate/tree/next/examples/workflow-car-vitals): several checks invoked together while the car is on, re-run on a delayed transition.
|
|
64
|
+
- [workflow-accumulate-room-readings](https://github.com/statelyai/xstate/tree/next/examples/workflow-accumulate-room-readings): readings collected into context until a delayed transition ends the window and reports them.
|
|
65
|
+
- [workflow-monitor-patient](https://github.com/statelyai/xstate/tree/next/examples/workflow-monitor-patient): a single monitoring state whose event handlers react to each kind of vital sign alert.
|
|
66
|
+
- [workflow-media-scanner](https://github.com/statelyai/xstate/tree/next/examples/workflow-media-scanner): a file scanning pipeline split across modules.
|
|
67
67
|
|
|
68
68
|
The remaining `workflow-*` directories cover the rest of the specification's examples: [workflow-async-subflow](https://github.com/statelyai/xstate/tree/next/examples/workflow-async-subflow), [workflow-car-auction-bids](https://github.com/statelyai/xstate/tree/next/examples/workflow-car-auction-bids), [workflow-event-based-service](https://github.com/statelyai/xstate/tree/next/examples/workflow-event-based-service), [workflow-event-greeting](https://github.com/statelyai/xstate/tree/next/examples/workflow-event-greeting), [workflow-filling-water](https://github.com/statelyai/xstate/tree/next/examples/workflow-filling-water), [workflow-finalize-college-app](https://github.com/statelyai/xstate/tree/next/examples/workflow-finalize-college-app), [workflow-greeting](https://github.com/statelyai/xstate/tree/next/examples/workflow-greeting), [workflow-math-problem](https://github.com/statelyai/xstate/tree/next/examples/workflow-math-problem), [workflow-monitor-job](https://github.com/statelyai/xstate/tree/next/examples/workflow-monitor-job), [workflow-new-patient-onboarding](https://github.com/statelyai/xstate/tree/next/examples/workflow-new-patient-onboarding), [workflow-reusing-functions](https://github.com/statelyai/xstate/tree/next/examples/workflow-reusing-functions) and [workflow-send-cloudevent](https://github.com/statelyai/xstate/tree/next/examples/workflow-send-cloudevent).
|
|
69
69
|
|
package/docs/fsm.md
CHANGED
|
@@ -153,7 +153,8 @@ from `xstate` adds the runtime to your bundle.
|
|
|
153
153
|
- A transition can be a state name, `{ target, context }`, or a function that
|
|
154
154
|
receives `{ context, event }` and returns `{ target, context }`. Return
|
|
155
155
|
`undefined` from a function for no transition.
|
|
156
|
-
-
|
|
156
|
+
- Only own entries in `states` and `on` define transitions. A state inherited from the `states` prototype has no outgoing transitions, like an undeclared state. Transition targets are not validated.
|
|
157
|
+
- Context updates are shallow patches. A patch adds its own enumerable string and symbol keys even when a new value is `undefined` or matches an inherited value. If the target is unchanged, a patch whose keys already exist with `Object.is`-equal values keeps the current snapshot.
|
|
157
158
|
- `types<T>()` adds types without runtime validation. Setup schema values are
|
|
158
159
|
ignored by the FSM runtime; state `schemas.input` is accepted for upgrade
|
|
159
160
|
compatibility but no input is consumed by this pure API.
|
package/docs/guards.md
CHANGED
|
@@ -38,7 +38,7 @@ Do not fetch data in a guard. Invoke an actor, store the result in context, then
|
|
|
38
38
|
|
|
39
39
|
## Named guards
|
|
40
40
|
|
|
41
|
-
Define reusable guards on `setup(...)` or the machine's `guards`. A named guard is a plain predicate function: it receives only the arguments you pass it and knows nothing about the machine. The caller decides what the guard sees
|
|
41
|
+
Define reusable guards on `setup(...)` or the machine's `guards`. A named guard is a plain predicate function: it receives only the arguments you pass it and knows nothing about the machine. The caller decides what the guard sees: pass values from `context` or the event explicitly.
|
|
42
42
|
|
|
43
43
|
```ts
|
|
44
44
|
const orderMachine = createMachine({
|
package/docs/index.md
CHANGED
package/docs/invoke.md
CHANGED
|
@@ -112,7 +112,7 @@ const machine = s.createMachine({
|
|
|
112
112
|
|
|
113
113
|
Keep the helper call inline: its enclosing state supplies narrowed context, ancestor context, state input and transition targets. In this example, `context.userId` is `string` and `event.output` is `{ name: string }`.
|
|
114
114
|
|
|
115
|
-
For an async `src`, `schemas.input` types the actor input, `schemas.output` checks the function's resolved value and types `onDone.event.output`, and `schemas.error` types `onError.event.error`. Omit the output schema to infer output from the async return value. The function receives `{ input, signal, self, system }` and the async enqueue object, including `emit` and `step`,
|
|
115
|
+
For an async `src`, `schemas.input` types the actor input, `schemas.output` checks the function's resolved value and types `onDone.event.output`, and `schemas.error` types `onError.event.error`. Omit the output schema to infer output from the async return value. The function receives `{ input, signal, self, system }` and the async enqueue object, including `emit` and `step`, as `createAsyncLogic` does. Its input comes from the invoke's static value or input mapper; parent context stays in the mapper and transition handlers.
|
|
116
116
|
|
|
117
117
|
You can also pass an existing actor logic value, including machine and callback logic:
|
|
118
118
|
|
|
@@ -124,6 +124,14 @@ invoke: s.createInvoke({
|
|
|
124
124
|
})
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
A bare inline invoke such as `{ src: loadUserLogic, onDone }` cannot correlate
|
|
128
|
+
that specific logic with its completion output. With no registered actor
|
|
129
|
+
sources, its `onDone.event.output` is `unknown`, so TypeScript requires
|
|
130
|
+
narrowing before it can be used. For a typed output, use
|
|
131
|
+
`s.createInvoke({ src: loadUserLogic, onDone })` as above, or register the logic
|
|
132
|
+
in `setup({ actors: { loadUserLogic } })` and invoke it with
|
|
133
|
+
`src: 'loadUserLogic'`. Prefer one of these typed forms when generating code.
|
|
134
|
+
|
|
127
135
|
For a logic value, its own schemas determine input, output, error and snapshot types. Declare schemas on that logic rather than repeating them on the invoke.
|
|
128
136
|
|
|
129
137
|
The helper requires `input` when the source's input type excludes `undefined`. Pass a static value or a mapper returning that input type. Logic that accepts `undefined` can omit `input`.
|
|
@@ -306,7 +314,31 @@ Invoke also is not the right tool when the child must outlive the state, or when
|
|
|
306
314
|
|
|
307
315
|
Provide named actor logic through `setup({ actors })` or `createMachine({ actors })`. XState correlates `src` with `input`, so a static or mapped input is checked against that logic's input type, and `onDone` sees the output type of that logic.
|
|
308
316
|
|
|
309
|
-
|
|
317
|
+
For a literal invoke `id` and a named or inline actor source, XState infers the
|
|
318
|
+
child reference in `snapshot.children`. The child may be absent when the invoke
|
|
319
|
+
state is inactive, so check for `undefined` before sending an event:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
import { createActor, setup, types } from 'xstate';
|
|
323
|
+
|
|
324
|
+
const paymentMachine = setup({
|
|
325
|
+
schemas: { events: { retry: types<{}>() } }
|
|
326
|
+
}).createMachine({ initial: 'idle', states: { idle: {} } });
|
|
327
|
+
|
|
328
|
+
const parent = setup({ actors: { paymentMachine } }).createMachine({
|
|
329
|
+
invoke: { id: 'payment', src: 'paymentMachine' }
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const payment = createActor(parent).getSnapshot().children.payment;
|
|
333
|
+
payment?.send({ type: 'retry' });
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Declare child ids with `schemas.children` when the child contract must also be
|
|
337
|
+
available inside machine callbacks. This also checks that each declared `id`
|
|
338
|
+
receives compatible logic. For dynamic IDs, keep the returned child reference
|
|
339
|
+
from `enq.spawn(...)` in context instead of indexing `snapshot.children` with
|
|
340
|
+
an arbitrary string. Spawned children have a separately managed lifetime; see
|
|
341
|
+
[Spawn](spawn.md):
|
|
310
342
|
|
|
311
343
|
```ts
|
|
312
344
|
setup({}).createMachine({
|
package/docs/persistence.md
CHANGED
|
@@ -25,6 +25,12 @@ const restored = restoreActor(
|
|
|
25
25
|
).start();
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
In Vue, keep a machine passed to `restoreActor` out of deep reactive state.
|
|
29
|
+
Store it in `shallowRef(machine)` or create it with `markRaw(machine)`. XState
|
|
30
|
+
also accepts a reactive Proxy around a machine, but a raw machine avoids
|
|
31
|
+
wrapping its state nodes and is the recommended way to hold actor logic in Vue.
|
|
32
|
+
See [Vue input and restored snapshots](https://stately.ai/docs/xstate/v6/vue/input-and-snapshots).
|
|
33
|
+
|
|
28
34
|
Pass `system` (a `createSystem()` result) to restore the actor into that system.
|
|
29
35
|
|
|
30
36
|
A restored snapshot is opaque: restoring runs no transitions and re-executes no actions. `always` transitions and choice states in the restored configuration are not re-evaluated until the next event, even if their guards would now pass. Development builds warn when the restored state has eventless transitions.
|
|
@@ -47,11 +53,11 @@ By default a persisted snapshot embeds each child's persisted state, producing a
|
|
|
47
53
|
const persisted = actor.getPersistedSnapshot({ embedChildren: false });
|
|
48
54
|
```
|
|
49
55
|
|
|
50
|
-
The option applies to the whole tree, not to a single placement boundary. Persisting by address requires each child to have a registered source key (a string `src`); `spawn(actors.worker)`, `enq.spawn(actors.worker)` and `enq.spawn('worker')` retain that key, while inline actor logic cannot be referenced by address. The string form makes the exact identity explicit when aliases share logic. Restoring an address-only child produces a location-transparent handle: sends to it route through the [system runtime](durable-execution.md), and its snapshot exposes lifecycle only, since a full snapshot is the last value an actor published and only co-located actors observe it. Install the system runtime before sending to a restored handle
|
|
56
|
+
The option applies to the whole tree, not to a single placement boundary. Persisting by address requires each child to have a registered source key (a string `src`); `spawn(actors.worker)`, `enq.spawn(actors.worker)` and `enq.spawn('worker')` retain that key, while inline actor logic cannot be referenced by address. The string form makes the exact identity explicit when aliases share logic. Restoring an address-only child produces a location-transparent handle: sends to it route through the [system runtime](durable-execution.md), and its snapshot exposes lifecycle only, since a full snapshot is the last value an actor published and only co-located actors observe it. Install the system runtime before sending to a restored handle. Without one, there is no route to the actor it references.
|
|
51
57
|
|
|
52
58
|
Persisted children changed shape in v6: each entry carries an `address` field and either an embedded `snapshot` or a `remote: true` marker. A remote entry may also carry an opaque `incarnation` token, round-tripped verbatim: XState never stamps one, but a host that does gets stale-completion protection on the referencing side and the token on journaled `sendTo` descriptors. Snapshots persisted by v5 or by v6 alphas use other shapes and are not read on restore; discard them or convert them before restoring. See the [migration guide](xstate-v5-to-v6.md#29-names-changed-during-the-v6-prereleases).
|
|
53
59
|
|
|
54
|
-
A timer persisted from a running actor carries its wall-clock start (`scheduledAt`), and restoring the snapshot schedules the remaining time toward the original deadline
|
|
60
|
+
A timer persisted from a running actor carries its wall-clock start (`scheduledAt`), and restoring the snapshot schedules the remaining time toward the original deadline. A timer past due fires immediately. Snapshots produced only by pure transitions carry no timestamp, so restoring one restarts each timer with its declared delay. A [durable execution](durable-execution.md) records timer starts when scheduling effects succeed; await `executeEffects()` before persisting its checkpoint to preserve deadlines. A custom adapter clock must use the same time origin when restoring.
|
|
55
61
|
|
|
56
62
|
For a host-driven durable loop, use `execution.restore(persistedSnapshot)` from
|
|
57
63
|
`xstate/durable`, then `execution.executeEffects(effects)` to resume embedded
|
|
@@ -59,13 +65,13 @@ children and pending timers. Persist `execution.nextTransitionIndex` with the
|
|
|
59
65
|
checkpoint and pass it to the next execution's adapter. See
|
|
60
66
|
[durable checkpoint restoration](durable-execution.md).
|
|
61
67
|
|
|
62
|
-
An actor's address is the `/`-joined path of actor ids from the root, such as `order/worker:0`. It is stable across persistence and restore, unlike `sessionId`, which identifies one incarnation. Generated child ids are recorded in each snapshot's `
|
|
68
|
+
An actor's address is the `/`-joined path of actor ids from the root, such as `order/worker:0`. It is stable across persistence and restore, unlike `sessionId`, which identifies one incarnation. Generated child ids are recorded in each snapshot's `nextActorIds`, so restored actors keep numbering where they left off.
|
|
63
69
|
|
|
64
70
|
## Compatibility
|
|
65
71
|
|
|
66
72
|
A persisted snapshot is a plain JSON-shaped object. Restoring one is lenient: XState reads the fields it knows and does not check a format marker.
|
|
67
73
|
|
|
68
|
-
These envelope fields are stable across 6.x releases: `status`, `value`, `context`, `output`, `error`, `historyValue`, `stateInputs`, `children`, `timers` and `machine`. The [persisted snapshot JSON Schema](https://github.com/statelyai/xstate/blob/next/packages/core/src/persistedSnapshot.schema.json) (draft 2020-12) describes them. Nested machine children carry their own envelope in `children[id].snapshot`. A child entry's `src` is the registered source key the child restores from. A child invoked with inline logic has no `src`; it restores through the invoke definition with its id among the restored active states, and restoring fails when no active state, or more than one, invokes that id.
|
|
74
|
+
These envelope fields are stable across 6.x releases: `status`, `value`, `context`, `output`, `error`, `historyValue`, `stateInputs`, `children`, `timers` and `machine`. The [persisted snapshot JSON Schema](https://github.com/statelyai/xstate/blob/next/packages/core/src/persistedSnapshot.schema.json) (draft 2020-12) describes them. Nested machine children carry their own envelope in `children[id].snapshot`. A child entry's `src` is the registered source key the child restores from. A child invoked with inline logic has no `src`; it restores through the invoke definition with its id among the restored active states, and restoring fails when no active state, or more than one, invokes that id. `nextTimerId` and `nextActorIds` are private. They round-trip verbatim; do not read or write them. No field name in the envelope starts with `$` or `_`, so it can be stored in document databases such as Convex and MongoDB that reserve those prefixes.
|
|
69
75
|
|
|
70
76
|
`timers` is a list of pending timers in scheduling order. A delayed `enq.raise(...)` or `enq.sendTo(...)` entry carries its `id`, or a `seq` number when scheduled without one. Built-in `after` and `timeout` timers have neither; their `event` (`xstate.after`, `xstate.timeout.state` or `xstate.timeout.actor`) identifies them.
|
|
71
77
|
|
|
@@ -95,15 +101,15 @@ In development builds, `getPersistedSnapshot()` warns once per call with the pat
|
|
|
95
101
|
|
|
96
102
|
`Date` values are not reported. `JSON.stringify` writes them as ISO strings, and `JSON.parse` does not turn them back into `Date` objects. Convert them on restore if your context needs `Date` instances.
|
|
97
103
|
|
|
98
|
-
Actor refs in `context` persist as `{
|
|
104
|
+
Actor refs in `context` persist as `{ '@actor': address }` and are not reported. A ref that names one of the snapshot's children restores to that child.
|
|
99
105
|
|
|
100
|
-
|
|
106
|
+
`@actor` is a reserved key in persisted context. On restore, any object in `context` whose keys are exactly `@actor` (and optionally `incarnation`) is read as a serialized actor ref: without an `incarnation`, it becomes the live child at that address, if there is one. Objects with other keys alongside `@actor` stay plain data. Do not store application data as an object with only an `@actor` key.
|
|
101
107
|
|
|
102
108
|
### Serialized actor refs
|
|
103
109
|
|
|
104
|
-
An actor ref serializes with `JSON.stringify` as `{
|
|
110
|
+
An actor ref serializes with `JSON.stringify` as `{ '@actor': address }` (the `SerializedActorRef` type, which also allows an optional string `incarnation`). This shape appears when a ref travels in an event payload, such as a `replyTo` field, and the event is persisted or crosses a process boundary. A serialized ref in `context` keeps its address across persistence and restore.
|
|
105
111
|
|
|
106
|
-
A serialized ref is a valid `enq.sendTo(...)` target. The event is delivered to the live actor at the ref's
|
|
112
|
+
A serialized ref is a valid `enq.sendTo(...)` target. The event is delivered to the live actor at the ref's `@actor` address, including an actor restored after the ref was serialized. If no live actor has that address, the event becomes a dead letter with reason `'missingTarget'`.
|
|
107
113
|
|
|
108
114
|
```ts
|
|
109
115
|
const server = createMachine({
|
|
@@ -154,8 +160,8 @@ const checkoutVersions = machineVersions([
|
|
|
154
160
|
children: z.object({}),
|
|
155
161
|
historyValue: z.object({}),
|
|
156
162
|
timers: z.tuple([]),
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
nextActorIds: z.record(z.string(), z.number()).optional(),
|
|
164
|
+
nextTimerId: z.number(),
|
|
159
165
|
stateInputs: z.undefined().optional(),
|
|
160
166
|
machine: z.object({
|
|
161
167
|
id: z.literal('checkout'),
|