6.0.0-alpha.41Pre-release
Added 13
- Actors now have deterministic, location-transparent identity with a logical address representing the path from the root
- Root actors are named after their logic's id, and children spawned without an explicit id get deterministic per-parent counters keyed by their actor source
- getEffectDescriptor(effect) returns a serializable view of any executable effect with actor references replaced by addresses
- A host runtime can be installed as system.runtime with the built-in local runtime as the default
- New deliverEvent, stopActor and terminateActor helpers expose local behaviors for custom runtimes to delegate to
- createDurable adapters carry their runtime operations directly and expose rootAddress and getActorRef(snapshot)
- getPersistedSnapshot(snapshot, { embedChildren: false }) persists children by logical address, leaving each child's state with the runtime that owns it
- Undeliverable events are reported through the new deadLetter runtime operation and a @xstate.deadletter inspection event
- A persisted remote child entry round-trips an optional opaque incarnation token to drop completions from different incarnations of the address
- createDurable exposes machineId and machineVersion so hosts can pin an execution's journal to the machine version that produced it
- Async-actor steps route through the new runStep runtime operation with a runStep helper export exposing the built-in behavior
- Serialized actor references now carry xstate$type: 'actorRef' instead of the v5 xstate$$type: 1 marker
- Timers persisted from a running actor carry their wall-clock start (startedAt) and restore by scheduling the remaining time toward the original deadline
Changed 6
- Addresses are stable across persistence and restore with sessionId identifying one incarnation of an address
- enq.spawn(actors.x) records the registered source key so spawned children persist by key
- Explicit child ids are now unique per parent and spawning or invoking with an id already held by a live sibling throws an error
- A transition to a history state that restores its own source now exits and reenters the source so its invoked actors restart instead of leaking
- During executeEffects in createDurable, root-addressed events no longer reach any runtime sendEvent and should be drained from the executeEffects result instead
- Runtime objects are wrapped before effects see them so identity comparisons and extra non-runtime properties are not preserved
From XState
Minor Changes
-
14cfdc3: Actors now have deterministic, location-transparent identity.
- Every actor has a logical
address: the/-joined path of actor ids from the root. Root actors are named after their logic'sid, and children spawned without an explicit id get deterministic per-parent counters keyed by their actor source (worker:0,worker:1). Addresses are stable across persistence and restore;sessionIdidentifies one incarnation of an address, and completions from a previous incarnation of a local child are dropped. enq.spawn(actors.x)records the registered source key so spawned children persist by key.getEffectDescriptor(effect)returns a serializable view of any executable effect, with actor references replaced by addresses and actor sources by source keys (payload fields pass through by reference).- A host runtime can be installed as
system.runtime; the built-in local runtime is the default. The newdeliverEvent,stopActorandterminateActorhelpers expose the local behaviors for custom runtimes to delegate to. createDurable(fromxstate/durable) adapters carry their runtime operations directly (sendEvent,scheduleTimer, …), and the execution installs them on every snapshot's actor system; it exposesrootAddressandgetActorRef(snapshot), tags every effect with a serializabledescriptor, andexecuteEffectsresolves only when every transitively initiated runtime operation has been accepted — returning the events addressed to the root actor for the durable loop. Breaking for existing adapters: duringexecuteEffects, root-addressed events no longer reach any runtimesendEvent(including per-effectruntime()implementations) — drain them from theexecuteEffectsresult instead. While the loop is parked inwaitForEvent, a root-addressed event reachessendEventlike any other target, and the host should enqueue it in its own mailbox. Runtime objects are also wrapped before effects see them, so identity comparisons and extra non-runtime properties on the returned object are not preserved.getPersistedSnapshot(snapshot, { embedChildren: false })persists children by logical address, leaving each child's state with the runtime that owns it; restoring an address-only child produces a location-transparent handle whose sends route through the system runtime.- Explicit child ids are unique per parent: spawning or invoking with an id already held by a live sibling throws (ids stopped earlier in the same transition stay reusable). Previously a duplicate id silently created a second running actor at the same address. A transition to a history state that restores its own source now exits and reenters the source, so its invoked actors restart instead of leaking.
- Undeliverable events are reported through the new
deadLetterruntime operation and a@xstate.deadletterinspection event. Delivery stays at-most-once — this is observability, not retry. - A persisted remote child entry round-trips an optional opaque
incarnationtoken. XState never stamps one, but when a host does, completions from a different incarnation of the address are dropped andsendToeffect descriptors journal the target's token. createDurableexposesmachineIdandmachineVersionso hosts can pin an execution's journal to the machine version that produced it.- Async-actor steps (
enq.step) route through the newrunStepruntime operation. The built-in behavior memoizes results in the actor's own snapshot as before; a durable host implementsrunStepto own the step journal, replaying memoized results without re-running the step. TherunStephelper export exposes the built-in behavior. - Serialized actor references (
Actor.toJSON, persisted context refs) carryxstate$type: 'actorRef'instead of the v5xstate$$type: 1marker. Migrate v5-persisted context refs withmachineVersionsif you restore them. - Timers persisted from a running actor carry their wall-clock start (
startedAt); restoring the snapshot schedules the remaining time toward the original deadline (clamped to the declared delay), so a timer past due fires immediately instead of restarting its full delay. Pure-transition snapshots carry no timestamp and restart the declared delay.
const durable = createDurable(machine, { sendEvent: (source, target, event) => host.send(source?.address, target.address, event), executeAction: (action, { id }, runtime) => host.runAction(id, () => action.exec(runtime)), waitForEvent: ({ id }) => host.waitForEvent(id) }); - Every actor has a logical