Repository navigation
xstate@6.0.0-alpha.70
Pre-releaseMajor Changes
-
ceeebb0:
ActorRefFrom<T>accepts actor logic only, andActorRefFromLogic<T>is removed. ReplaceActorRefFromLogic<T>withActorRefFrom<T>. Pass the logic type instead of a promise, an actor ref, or a function returning logic:// Before type Ref = ActorRefFromLogic<typeof machine>; type AsyncRef = ActorRefFrom<Promise<User>>; // After type Ref = ActorRefFrom<typeof machine>; type AsyncRef = ActorRefFrom<typeof loadUser>;
ActorRefFrom<typeof machine>refs return the fully typed machine snapshot fromgetSnapshot(), includingcontext,valueandmatches('#id'). -
ceeebb0: State keys cannot contain an unescaped
., which separates path segments. Creating a machine with such a key throwsState key "foo.bar" contains "." — nest the states or escape it as "foo\\.bar".To use a literal dot, write the key escaped. The escaped form is then the key everywhere: state values, implicit ids,matches(...), targets and persisted snapshots. It never collides with a nestedfoo→bar.// Before states: { 'foo.bar': {} }, on: { GO: { target: 'foo\\.bar' } } // After states: { 'foo\\.bar': {} }, on: { GO: { target: 'foo\\.bar' } } // snapshot.value === 'foo\\.bar'
Two implicit state ids can no longer coincide, and any duplicate state id now throws.
-
39d3b5c:
#targets,snapshot.matches('#…')and@xstate.stateInguards resolve by exact state id. The text after#is a state's whole id, either its explicitidor its implicit id (the path from the root, prefixed by the machineid). It is no longer split into an id followed by a relative path. Dots in explicit ids are matched as written, without escaping.// `wizard` has `id: 'flow'` in a machine with `id: 'shop'` // Before browsing: { on: { review: { target: '#flow.review' } } } // After: the target's full implicit id, or give it its own `id` browsing: { on: { review: { target: '#shop.wizard.review' } } }
An unknown id throws when the machine is created. When the old id-plus-path form resolves, the error names the target's id:
No state has id "flow.review". Use "#shop.wizard.review", or give that state an id.Typed
#targets andmatches('#…')accept only whole ids. -
ceeebb0: The root
xstateentry no longer exports type-level helpers:TODO,IsAny,IsNever,Equals,Cast,Compute,Prop,HomomorphicOmit,LowInfer,DoNotInfer,SingleOrArray,MaybeLazyandAnyFunction. Define equivalents in your own code if you used them.ConditionalRequired,IsNotNeverandNonReducibleUnknownremain exported because emitted declarations reference them, and are marked@experimental.ActorOptionsno longer has the undocumentedparentoption. Create child actors withinvoke,spawn(...)orenq.spawn(...). -
ceeebb0: Implicit state and invoke ids no longer use the
(machine)placeholder.- A state's implicit id is its path from the root joined by
., prefixed by the machineidwhen there is one:shop.checkout.paymentforid: 'shop', andcheckout.paymentwithout an id (previously(machine).checkout.payment). machine.idand the root state's id are''for a machine without anid.#cannot reference that root; give the machine anidto target it as#shop.- An unnamed invoke's id is
<stateId>:<index>:loading:0,loading:1,shop.checkout:0. A root invoke is:0, orshop:0with a machine id (previously0.(machine).loading). Anonymous spawned actors keepx:<n>, and generated spawn ids skip ids already held by an invoke. - An explicit state
idthat equals another state's id throws when the machine is created. - Error messages name
the machineinstead ofmachine '(machine)'.
const machine = createMachine({ initial: 'loading', states: { loading: { invoke: { src: fetchUser } } } }); createActor(machine).getSnapshot().children; // { 'loading:0': ... } machine.getStateNodeById('#loading').id; // 'loading'
Persisted snapshots that reference
(machine).…state ids or0.(machine).…child ids do not restore; migrate them withmachineVersions. - A state's implicit id is its path from the root joined by
-
ceeebb0: Children invoked with inline logic no longer get a synthetic source name (
xstate.invoke.<index>.<stateId>). Their persisted entries omitsrc, and restoring resolves them through the invoke definition with the same id among the restored active states. Restoring fails with a descriptive error when no active state, or more than one, invokes that id. Children invoked with a registered name (src: 'fetchUser') keep persisting that key, and remote children still require one.const machine = createMachine({ id: 'shop', initial: 'loading', states: { loading: { invoke: { id: 'fetch', src: fetchUser } } } }); createActor(machine).start().getPersistedSnapshot().children.fetch; // { address: 'shop/fetch', snapshot: {...} }, with no `src`
Snapshots persisted by earlier alphas that carry
src: 'xstate.invoke.…'no longer restore; remove thatsrcor migrate them withmachineVersions. -
ceeebb0: Bare string transition values throw when the machine is created, in every build. This matches the types and
machineFromJSON, which already rejected them. It coverson,after,always,onDone,onError,onTimeout, and invokeonDone/onError/onSnapshot/onTimeout.initialstays a string key, andxstate/fsmstill accepts string targets.// Throws: Transition for "GO" in state "idle" is the string "next". Use { target: 'next' }. on: { GO: 'next' } // Use on: { GO: { target: 'next' } }
xstate-codemod migrate(thestring-targetstransform) rewrites these. -
ceeebb0:
invokerequiresinputwhen the invoked actor logic requires input, matchingcreateActor(logic, { input })andenq.spawn(logic, { input }). This applies to logic values, registered source names andsrcfunctions, increateMachine,setup(...).createMachineandsetup(...).createStateConfig. Logic whose input type acceptsundefined(noschemas.input, or an optional input schema) can still omit it.const child = createMachine({ schemas: { input: z.object({ userId: z.string() }) } }); createMachine({ // Type error: This actor logic requires input. Add an `input` to this invoke. invoke: { src: child } }); createMachine({ invoke: { src: child, input: ({ context }) => ({ userId: context.userId }) } });
enq.spawn(logic)without required input now reportsExpected 2 argumentsinstead of a mismatch against the source-name overload. -
ceeebb0: A machine that declares
versionmust also declareid, because persisted snapshots record the machine as{ id, version }.createMachine,setup(...).createMachineandMachineJSONreport a missingidas a type error, and creating such a machine throwsMachine with version '2' must declare an id.Machines without aversionkeepidoptional.// Before createMachine({ version: '2', initial: 'idle', states: { idle: {} } }); // After createMachine({ id: 'order', version: '2', initial: 'idle', states: { idle: {} } });
Patch Changes
-
ceeebb0:
#transition targets type-check against implicit state ids as well as explicit ones:target: '#checkout.payment', or'#shop.checkout.payment'for a machine withid: 'shop'. Unknown ids are still type errors. At runtime, a#target can name a whole implicit id below a state with an explicit id, such as'#wizard.step'whenwizardhasid: 'flow'. -
ceeebb0:
enq.spawn(...)returns an inert actor ref while a transition is only evaluated, such as bysnapshot.can(event)or when resolving conflicting transitions in parallel states. The ref has theid(and, when the parent is known, theaddress) the real spawn would get, anundefinedsessionId, a stablegetSnapshot(), a no-opsend(), andtoJSON()returning{ $actor: address }. Previously it was a bare{ id }object. -
ceeebb0:
createActor(machine, { snapshot })reports a persisted snapshot that cannot represent a state as an'error'snapshot with anInvalidSnapshotError, instead of starting an unusable actor. This covers snapshots that are not objects, have novalueor an invalid one (not a state name or an object of state values, or several active children of a non-parallel state), and values naming states the machine does not have. Unknown fields are still ignored.import { createActor, InvalidSnapshotError } from 'xstate'; const actor = createActor(machine, { snapshot: stored }); const snapshot = actor.getSnapshot(); if (snapshot.status === 'error' && snapshot.error instanceof InvalidSnapshotError) { // fall back to a fresh actor }
-
73b2edf: Fix several v6 alpha transition, typing, and inspection issues:
- Preserve enqueued effects when transition functions wrap
enqcalls intry/catch, and avoid reevaluating ordinary effect-only transitions during selection. - Preserve declared schema context types in callbacks and snapshots, and reject patches containing only undeclared keys.
- Infer invoke input mapper arguments when the registered actor accepts
unknownoranyinput. - Report the sender of the event being processed in transition inspection events, even when more events are queued during processing.
- Report inspection observer exceptions without interrupting actor processing or other inspection observers.
- Include the source state, event, and original
causewhen graph transition evaluation fails. Supply representative payloads through the graph traversaleventsoption when transition functions require them.
- Preserve enqueued effects when transition functions wrap
-
05f1e0f: Ship version-matched XState v6 documentation with the xstate package. Start with README.md or docs/index.md after installation for v6 examples and migration guidance.
-
8773a22: Require payload samples for known event types with required fields in
getShortestPaths,getSimplePaths, andgetAdjacencyMap. Use aneventsmap to explore multiple payloads while retaining default payload-free events, invoke completions, errors, and delays:getShortestPaths(machine, { events: { 'item.add': [{ item: { qty: 1 } }, { item: { qty: 0 } }] } });
Each map entry also accepts a snapshot callback returning nonempty payload cases. Event arrays and callbacks returning events still replace defaults, but their types must cover every required payload event. Fixed-sequence replay with
getPathsFromEventsrequires only the supplied events. Coverage remains sample-based.Unconfigured wildcard descriptors retain their default samples alongside concrete event cases. Use an event array to restrict traversal to supplied concrete events.