-
#3835
431472082
Thanks @with-heart! - The newTagsFrom
helper type extracts the type oftags
from a state machine when typegen is enabled:const machine = createMachine({ // `tags` attached to machine via typegen tsTypes: {} as import('./machine.typegen').Typegen0, tags: ['a', 'b'], states: { idle: { tags: 'c' } } }); type Tags = TagsFrom<typeof machine>; // 'a' | 'b' | 'c'
If typegen is not enabled,
TagsFrom
returnsstring
:const machine = createMachine({ tags: ['a', 'b'], states: { idle: { tags: 'c' } } }); type Tags = TagsFrom<typeof machine>; // string
-
#3393
430986cdf
Thanks @davidkpiano! - Deprecatedsend()
action creator. Instead of that, you can usesendTo()
to send events to other actors andraise()
to send events to the "self" actor. -
#3802
8743ad0bd
Thanks @Andarist! - Fixed a class of inference problems for our builtin actions (assign
,sendTo
, etc). -
#3694
fd589055b
Thanks @Andarist! - All actions received a new generic:TExpressionEvent
. To type things more correctly and allow TS to infer things better we need to distinguish between all events accepted by a machine (TEvent
) and the event type that actions are "called" with (TExpressionEvent
).It's best to rely on type inference so you shouldn't have to specify this generic manually all over the place.
-
#3818
2d8d84fd8
Thanks @Andarist! - Fixed inference forassign
usingPropertyAssigner
, like here:actions: assign({ counter: 0, delta: (ctx, ev) => ev.delta });
- #3801
10d0ba76a
Thanks @Andarist! - Fixed an issue with not clearing registered interpreters when their machines reached final states.
- #3783
b68f0e8bf
Thanks @Andarist! - Fixed an issue withEmittedFrom
sometimes not being able to infer the snapshot types from machines.
- #3745
8cc70d27e
Thanks @viglucci! - Fix types to allow for string state name in service onDone/onError config
- #3713
96052976a
Thanks @Andarist! - Fixed an issue that prevented events sent from the exit actions of the invoking state to be delivered to the invoked actor (when leaving that state).
- #3607
f95180510
Thanks @davidkpiano! - ThecreateModel(...)
function is now marked as deprecated, as it will be removed in XState version 5. It is recommended to use Typegen instead.
-
#3677
a2ecf97ca
Thanks @Andarist! - Fixed an issue with targeted ancestors not being correctly exited when reented during external transitions. -
#3623
163c25562
Thanks @arromeo! - Fixed an issue with external transitions targeting ancestor states. In such a case,entry
actions were incorrectly called on the states between the source state and the target state for states that were not reentered within this transition. -
#3677
a2ecf97ca
Thanks @Andarist! - Fixed an issue with the active descendants of the targeted ancestor not being correctly reentered during external transitions. -
#3545
b9995f0
Thanks @with-heart! - Updatedpure
action types to allow actiontype
strings to be returned in the array.const machine = createMachine( { entry: ['doStuff'] }, { actions: { doStuff: pure(() => ['someAction']), someAction: () => console.log('executed by doStuff') } } );
Returning action
type
strings were already handled byxstate
and the types now correctly reflect that. -
#3666
5e0808eb4
Thanks @davidkpiano! - The warning for thepredictableActionArguments
setting has been improved to only warn if it is absent. You can disable the warning by settingpredictableActionArguments: false
. It's still recommended to set it totrue
though.
-
#3588
a4c8ead99
Thanks @davidkpiano! - The actionsraise
andsendTo
can now be imported directly fromxstate
:import { raise, sendTo } from 'xstate'; // ...
- #3599
333f803f9
Thanks @Andarist! - Fixed a regression that has causedmachine.getInitialState(value)
to not follow always transitions correctly.
-
#3571
6fdaae710
Thanks @davidkpiano! - Reading state directly fromis deprecated. UsesomeService.state
someService.getSnapshot()
instead. -
#3555
4c13b3faf
Thanks @davidkpiano! - ThesendTo(actorName, event)
action creator now accepts a stringactorName
.
-
#3559
ddbc9bc5c
Thanks @Andarist! - Fixed minor compatibility issues with TypeScript 4.8 in the codebase. This fixes the typechecking with TypeScript 4.8 in projects that don't useskipLibCheck: true
. -
#3563
e3c7a9caf
Thanks @Andarist! - Fixed an issue with not executing actions in response to received batched events when usingpredictableActionArguments
. -
#3520
95a6a06d0
Thanks @Andarist! - Fixed a runtime crash when sending multiple events as an array to a service. It is not recommended to use this feature though as it will be removed in the next major version.
- #3549
768c4e938
Thanks @Andarist! - Fixed an issue with not being able to send events to initially started child actors when usingpredictableActionArguments
.
-
#3540
121fad172
Thanks @Andarist! - Fixed an issue that causedinvoke
d actors to be created before resolvingassign
actions fromentry
of the same state when usingpredictableActionArguments
flag. -
#3541
6c081ab87
Thanks @Andarist! - Fixed an issue with not being able to read the updated snapshot of a child when receiving and processing events from it and when usingpredictableActionArguments
flag.
- #3523
129bcf927
Thanks @Andarist! - Fixed a regression that caused child actors not being correctly stopped when their parent reached a final state.
- #3514
b451f5789
Thanks @Andarist! - Fixed an issue with.nextState(event)
calls accidentally executing actions in machines withpredictableActionArguments
.
-
#3289
c0a147e25
Thanks @Andarist! - A newpredictableActionArguments
feature flag has been added that allows you to opt into some fixed behaviors that will be the default in v5. With this flag:- XState will always call an action with the event directly responsible for the related transition,
- you also automatically opt-into
preserveActionOrder
.
Please be aware that you might not able to use
state
from themeta
argument when using this flag. -
#3126
37b751cb3
Thanks @Andarist! - Allexit
actions in the machine will now be correctly resolved and executed when a machine gets stopped or reaches its top-level final state. Previously, the actions were not correctly resolved and that was leading to runtime errors.To implement this fix in a reliable way, a new internal event has been introduced:
{ type: 'xstate.stop' }
and when the machine stops its execution, all exit handlers of the current state (i.e. the active state nodes) will be called with that event. You should always assume that an exit handler might be called with that event.
-
#3178
6badd2ba3
Thanks @davidkpiano! - Added a dev-only error whenforwardTo
accidentally ends up trying to forward an event to an undefined actor. Such a situation indicates a logical error and risks an infinite loop. -
#3453
368ed9b1c
Thanks @pixtron! - Call thecomplete
callback of the subscribedobserver
when an interpreter gets stopped. -
#3422
e35493f59
Thanks @Andarist! - Fixed an issue with parallel regions not always being correctly reentered on external transitions of the containing parallel state targeting another region within that parallel state. -
#3447
e93754d7a
Thanks @davidkpiano! - The types forstate.nextEvents
are now properly typed to the actual event types of the machine. Original PR: #1115 (Thanks @alexreardon!) -
#3424
88d540eb8
Thanks @Andarist! - Fixed an issue with targeted ancestors not being correctly reentered during external transitions.
- #3292
16514e466
Thanks @Andarist! - Fixed an issue in theEmittedFrom
type helper that could prevent it from inferring the desired type from some services.
- #3234
ce376b388
Thanks @Andarist! - Added aStateValueFrom
helper that can be used to extract valid state values from a machine. This might specifically be useful with typegen because typegenlessstate.matches
acceptsany
anyway.
-
#3215
44c66e74f
Thanks @tom-sherman! - Removing the timeout that's built in towaitFor
is now supported by explicitly passing anInfinity
value.Example usage:
import { waitFor } from 'xstate/lib/waitFor'; // This will const loggedInState = await waitFor( loginService, (state) => state.hasTag('loggedIn'), { timeout: Infinity } );
This fixes a bug that causes
waitFor
to reject with an error immediately due to the behaviour ofsetTimeout
. -
#3230
780458c92
Thanks @Andarist! - Fixed an issue with typegen types not being able to provide events that had a union of strings as theirtype
(such as{ type: 'INC' | 'DEC'; value: number; }
). -
#3252
a94dfd467
Thanks @Andarist! - Fixed an issue withEventFrom
not being able to extract events that had a union of strings as theirtype
(such as{ type: 'INC' | 'DEC'; value: number; }
). -
#3090
c4f73ca13
Thanks @Andarist! - Fixed an issue with action objects not receiving correct event types when used in the second argument to thecreateMachine
. -
#3238
3df6335ef
Thanks @davidkpiano! - The typings forsendTo(...)
have been fixed. -
#3228
fe5f0e6c9
Thanks @Andarist! - Fixed an issue with inline functions in the config object used as transition actions not having their argument types inferred. -
#3252
a94dfd467
Thanks @Andarist! - Fixed an issue with defaultTEvent
({ type: string }
) not being correctly provided to inline transition actions.
-
#3190
fbf5ca0ad
Thanks @davidkpiano! - ThewaitFor(...)
helper function, which asynchronously waits for an actor's emitted value to satisfy apredicate
before atimeout
, is now available.Example usage:
import { waitFor } from 'xstate/lib/waitFor'; // ... const loginService = interpret(loginMachine).start(); const loggedInState = await waitFor(loginService, (state) => state.hasTag('loggedIn') ); loggedInState.hasTag('loggedIn'); // true
-
#3200
56c0a36
Thanks @Andarist! - Subscribing to a stopped interpreter will now always immediately emit its state and call a completion callback.
-
#3166
be4c5c74d
Thanks @Andarist! - Fixed an issue withstate.tags
not having correct values when resolving micro transitions (taken in response to raised events). This was creating issues when checking tags in guards. -
#3171
14f8b4785
Thanks @Andarist! - Fixed an issue withonDone
on parallel states not being "called" correctly when a parallel state had a history state defined directly on it. -
#2939
360e85462
Thanks @Andarist! - Fixed issues with not disposing some cached internal values when stopping interpreters, which could have led to issues when starting such an interpreter again. -
#3153
b36ef9dda
Thanks @Andarist! - Made type displays (like in the IDE tooltips etc) more readable by using a type interface for the internalResolveTypegenMeta
type.
-
#3131
d9a0bcfc9
Thanks @Andarist! - Fixed an issue with event type being inferred from too many places withincreateMachine
call and possibly ending up asany
/AnyEventObject
for the entire machine. -
#3133
4feef9d47
Thanks @fw6! - Fixed compatibility with esoteric Mini Program environment whereglobal
object was available butglobal.console
wasn't. -
#3140
502ffe91a
Thanks @Andarist! - Fixed an issue with interpreters started using a persisted state not being "resolved" in full. This could cause some things, such asafter
transitions, not being executed correctly after starting an interpreter like this. -
#3147
155539c85
Thanks @Andarist! - Fixed a TS inference issue causing some functions to infer the constraint type for the event type even though aStateMachine
passed to the function was parametrized with a concrete type for the event. More information can be found here. -
#3146
4cf89b5f9
Thanks @Andarist! - Fixed compatibility ofInterpreter
with older versions of TypeScript. This ensures that our interpreters can correctly be consumed by functions expectingActorRef
interface (like for exampleuseSelector
). -
#3139
7b45fda9e
Thanks @Andarist! -InterpreterFrom
andActorRefFrom
types used on machines with typegen data should now correctly return types with final/resolved typegen data. The "final" type here means a type that already encodes the information that all required implementations have been provided. Before this change this wouldn't typecheck correctly:const machine = createMachine({ // this encodes that we still expect `myAction` to be provided tsTypes: {} as Typegen0 }); const service: InterpreterFrom<typeof machine> = machine.withConfig({ actions: { myAction: () => {} } });
-
#3097
c881c8ca9
Thanks @davidkpiano! - State that is persisted and restored frommachine.resolveState(state)
will now have the correctstate.machine
value, so thatstate.can(...)
and other methods will work as expected. See #3096 for more details.
- #3118
28e353081
Thanks @Andarist! - Fixed a bundling issue that prevented thekeys()
export to be preserved in the previous release.
-
#3104
3706c62f4
Thanks @Andarist! - FixedContextFrom
helper type to work on typegened machines. -
#3113
144131bed
Thanks @davidkpiano! - Thekeys()
utility function export, which was removed in #3089, is now added back, as older versions of XState libraries may depend on it still. See #3106 for more details. -
#3104
3706c62f4
Thanks @Andarist! - FixedEventFrom
helper type to work on machines.
-
#3088
9f02271a3
Thanks @Andarist! - Added some internal@ts-ignore
comments to fix consuming projects that do not useskipLibCheck
. -
#3082
8d3f2cfea
Thanks @Andarist! - Fixed an issue with context type being inferred from too many places withincreateMachine
call and possibly ending up asany
for the entire machine. -
#3027
97ad964bd
Thanks @hedgepigdaniel! - Fixed an issue with not being able to callcreateMachine
in a generic context when the type for the context was generic and not concrete. -
#3084
50c271dc1
Thanks @Andarist! - Fixed an issue with context type defined usingschema.context
being sometimes widened based onconfig.context
. If both are given theschema.context
should always take precedence and should represent the complete type of the context. -
#3089
862697e29
Thanks @Andarist! - Fixed compatibility with Skypack by exporting some shared utilities from root entry of XState and consuming them directly in other packages (this avoids accessing those things using deep imports and thus it avoids creating those compatibility problems). -
#3087
ae9579497
Thanks @Andarist! - Fixed an issue withActorRefFrom
not resolving the typegen metadata from machine types given to it. This could sometimes result in types assignability problems, especially when using machine factories andspawn
.
-
#3063
c826559b4
Thanks @Andarist! - Fixed a type compatibility with Svelte's readables. It should be possible again to use XState interpreters directly as readables at the type-level. -
#3051
04091f29c
Thanks @Andarist! - Fixed type compatibility with functions accepting machines that were created before typegen was a thing in XState. This should make it possible to use the latest version of XState with@xstate/vue
,@xstate/react@^1
and some community packages.Note that this change doesn't make those functions to accept machines that have typegen information on them. For that the signatures of those functions would have to be adjusted.
-
#3077
2c76ecac5
Thanks @Andarist! - Fixed an issue with nestedstate.matches
calls when the typegen was involved. Thestate
ended up beingnever
and thus not usable.
-
#3040
18dc2b3e2
Thanks @davidkpiano! - TheAnyState
andAnyStateMachine
types are now available, which can be used to express any state and state machine, respectively:import type { AnyState, AnyStateMachine } from 'xstate'; // A function that takes in any state machine function visualizeMachine(machine: AnyStateMachine) { // (exercise left to reader) } function logState(state: AnyState) { // ... }
-
#3042
e53396f08
Thanks @suerta-git! - Added theAnyStateConfig
type, which represents anyStateConfig<...>
:import type { AnyStateConfig } from 'xstate'; import { State } from 'xstate'; // Retrieving the state config from localStorage const stateConfig: AnyStateConfig = JSON.parse( localStorage.getItem('app-state') ); // Use State.create() to restore state from config object with correct type const previousState = State.create(stateConfig);
-
#2965
8b8f719c3
Thanks @satyasinha! - All actions are now available in theactions
variable when importing:import { actions } from 'xstate'
-
#2892
02de3d44f
Thanks @davidkpiano! - Persisted state can now be easily restored to a state compatible with the machine without converting it to aState
instance first:// Persisting a state someService.subscribe((state) => { localStorage.setItem('some-state', JSON.stringify(state)); }); // Restoring a state const stateJson = localStorage.getItem('some-state'); // No need to convert `stateJson` object to a state! const someService = interpret(someMachine).start(stateJson);
-
#3012
ab431dcb8
Thanks @Andarist! - Fixed an issue with a reference to@types/node
being inserted into XState's compiled output. This could cause unexpected issues in projects expecting APIs likesetTimeout
to be typed with browser compatibility in mind. -
#3023
642e9f5b8
Thanks @Andarist! - Fixed an issue with states created usingmachine.getInitialState
not being "resolved" in full. This could cause some things, such asafter
transitions, not being executed correctly after starting an interpreter using such state. -
#2982
a39145580
Thanks @Andarist! - Marked all phantom properties on theStateMachine
type as deprecated. This deprioritized them in IDEs so they don't popup as first suggestions during property access. -
#2992
22737adf2
Thanks @Andarist, @mattpocock! - Fixed an issue withstate.context
becomingany
afterstate.matches
when typegen is used. -
#2981
edf60d67b
Thanks @Andarist! - Moved an internal@ts-ignore
to a JSDoc-style comment to fix consuming projects that do not useskipLibCheck
. Regular inline and block comments are not preserved in the TypeScript's emit.
-
#2674
1cd26811c
Thanks @Andarist! - Usingconfig.schema
becomes the preferred way of "declaring" TypeScript generics with this release:createMachine({ schema: { context: {} as { count: number }, events: {} as { type: 'INC' } | { type: 'DEC' } } })
This allows us to leverage the inference algorithm better and unlocks some exciting possibilities for using XState in a more type-strict manner.
-
#2674
1cd26811c
Thanks @Andarist, @mattpocock! - Added the ability to tighten TS declarations of machine with generated metadata. This opens several exciting doors to being able to use typegen seamlessly with XState to provide an amazing typing experience.With the VS Code extension, you can specify a new attribute called
tsTypes: {}
in your machine definition:const machine = createMachine({ tsTypes: {} });
The extension will automatically add a type assertion to this property, which allows for type-safe access to a lot of XState's API's.
⚠️ This feature is in beta. Actions/services/guards/delays might currently get incorrectly annotated if they are called "in response" to always transitions or raised events. We are working on fixing this, both in XState and in the typegen.
-
#2962
32520650b
Thanks @mattpocock! - Addedt()
, which can be used to provide types forschema
attributes in machine configs:import { t, createMachine } from 'xstate'; const machine = createMachine({ schema: { context: t<{ value: number }>(), events: t<{ type: 'EVENT_1' } | { type: 'EVENT_2' }>() } });
-
#2957
8550ddda7
Thanks @davidkpiano! - The repository links have been updated fromgithub.com/davidkpiano
togithub.com/statelyai
.
-
#2943
e9f3f07a1
Thanks @Andarist! - Fixed an infinite loop when initially spawned actor (in an initial context) responded synchronously to its parent. -
#2953
90fa97008
Thanks @Andarist! - Bring back the global type declaration for theSymbol.observable
to fix consuming projects that do not useskipLibCheck
. -
#2903
b6dde9075
Thanks @Andarist! - Fixed an issue with exit actions being called in random order when stopping a machine. They should always be called in the reversed document order (the ones defined on children should be called before the ones defined on ancestors and the ones defined on states appearing later in the code should be called before the ones defined on their sibling states).
- #2835
029f7b75a
Thanks @woutermont! - Added interop observable symbols toActorRef
so that actor refs are compatible with libraries like RxJS.
-
#2864
4252ee212
Thanks @davidkpiano! - Generated IDs for invocations that do not provide anid
are now based on the state ID to avoid collisions:createMachine({ id: 'test', initial: 'p', states: { p: { type: 'parallel', states: { // Before this change, both invoke IDs would be 'someSource', // which is incorrect. a: { invoke: { src: 'someSource' // generated invoke ID: 'test.p.a:invocation[0]' } }, b: { invoke: { src: 'someSource' // generated invoke ID: 'test.p.b:invocation[0]' } } } } } });
-
#2925
239b4666a
Thanks @devanfarrell! - ThesendTo(actorRef, event)
action creator introduced in4.27.0
, which was not accessible from the package exports, can now be used just like other actions:import { actions } from 'xstate'; const { sendTo } = actions;
-
#2800
759a90155
Thanks @davidkpiano! - ThesendTo(actorRef, event)
action creator has been introduced. It allows you to specify the recipient actor ref of an event first, so that the event can be strongly typed against the events allowed to be received by the actor ref:// ... entry: sendTo( (ctx) => ctx.someActorRef, { type: 'EVENT_FOR_ACTOR' } ), // ...
-
#2804
f3caecf5a
Thanks @davidkpiano! - Thestate.can(...)
method no longer unnecessarily executesassign()
actions and instead determines if a given event will change the state by reading transition data before evaluating actions. -
#2856
49c2e9094
Thanks @Andarist! - Fixed an issue with stopped children sometimes starting their own child actors. This could happen when the child was stopped synchronously (for example by its parent) when transitioning to an invoking state. -
#2895
df5ffce14
Thanks @Andarist! - Fixed an issue with some exit handlers being executed more than once when stopping a machine.
-
#2819
0d51d33cd
Thanks @simonihmig! - SupportglobalThis
ingetGlobal()
for better compatibility -
#2828
c0ef3e8
Thanks @davidkpiano! - XState is now compatible with TypeScript version 4.5.
-
#2672
8e1d05d
Thanks @davidkpiano! - Thedescription
property is a new top-level property for state nodes and transitions, that lets you provide text descriptions:const machine = createMachine({ // ... states: { active: { // ... description: 'The task is in progress', on: { DEACTIVATE: { // ... description: 'Deactivates the task' } } } } });
Future Stately tooling will use the
description
to render automatically generated documentation, type hints, and enhancements to visual tools. -
#2743
e268bf34a
Thanks @janovekj! - Add optional type parameter to narrow type returned byEventFrom
. You can use it like this:type UpdateNameEvent = EventFrom<typeof userModel>;
-
#2738
942fd90e0
Thanks @michelsciortino! - Thetags
property was missing from state's definitions. This is used when converting a state to a JSON string. Since this is how we serialize states within@xstate/inspect
this has caused inspected machines to miss thetags
information. -
#2740
707cb981f
Thanks @Andarist! - Fixed an issue with tags being missed on a service state after starting that service using a state value, like this:const service = interpret(machine).start('active'); service.state.hasTag('foo'); // this should now return a correct result
-
#2691
a72806035
Thanks @davidkpiano! - Meta data can now be specified forinvoke
configs in theinvoke.meta
property:const machine = createMachine({ // ... invoke: { src: (ctx, e) => findUser(ctx.userId), meta: { summary: 'Finds user', updatedAt: '2021-09-...', version: '4.12.2' // other descriptive meta properties } } });
-
#2657
72155c1b7
Thanks @mattpocock! - Removed the ability to pass a model as a generic tocreateMachine
, in favour ofmodel.createMachine
. This lets us cut an overload from the definition ofcreateMachine
, meaning errors become more targeted and less cryptic.This means that this approach is no longer supported:
const model = createModel({}); const machine = createMachine<typeof model>();
If you're using this approach, you should use
model.createMachine
instead:const model = createModel({}); const machine = model.createMachine();
-
#2659
7bfeb930d
Thanks @Andarist! - Fixed a regression in the inline actions type inference in models without explicit action creators.const model = createModel( { foo: 100 }, { events: { BAR: () => ({}) } } ); model.createMachine({ // `ctx` was of type `any` entry: (ctx) => {}, exit: assign({ // `ctx` was of type `unknown` foo: (ctx) => 42 }) });
- #2649
ad611007a
Thanks @Andarist! - Fixed an issue with functions used as inline actions not always receiving the correct arguments when used withpreserveActionOrder
.
-
#2546
a4cfce18c
Thanks @davidkpiano! - You can now know if an event will cause a state change by using the newstate.can(event)
method, which will returntrue
if an interpreted machine will "change" the state when sent theevent
, orfalse
otherwise:const machine = createMachine({ initial: 'inactive', states: { inactive: { on: { TOGGLE: 'active' } }, active: { on: { DO_SOMETHING: { actions: ['something'] } } } } }); const state = machine.initialState; state.can('TOGGLE'); // true state.can('DO_SOMETHING'); // false // Also takes in full event objects: state.can({ type: 'DO_SOMETHING', data: 42 }); // false
A state is considered "changed" if any of the following are true:
- its
state.value
changes - there are new
state.actions
to be executed - its
state.context
changes
See
state.changed
(documentation) for more details. - its
-
#2632
f8cf5dfe0
Thanks @davidkpiano! - A regression was fixed where actions were being typed asnever
if events were specified increateModel(...)
but not actions:const model = createModel( {}, { events: {} } ); model.createMachine({ // These actions will cause TS to not compile entry: 'someAction', exit: { type: 'someObjectAction' } });
-
#2606
01e5d7984
Thanks @davidkpiano! - The following utility types were previously returningnever
in some unexpected cases, and are now working as expected:ContextFrom<T>
EventFrom<T>
EmittedFrom<T>
- #2587
5aaa8445c
Thanks @Andarist! - Allow for guards to be always resolved from the implementations object. This allows a guard implementation to be updated in the running service by@xstate/react
.
-
6c3f15c9
#2551 Thanks @mattpocock! - Widened the *From utility types to allow extracting from factory functions.This allows for:
const makeMachine = () => createMachine({}); type Interpreter = InterpreterFrom<typeof makeMachine>; type Actor = ActorRefFrom<typeof makeMachine>; type Context = ContextFrom<typeof makeMachine>; type Event = EventsFrom<typeof makeMachine>;
This also works for models, behaviours, and other actor types.
The previous method for doing this was a good bit more verbose:
const makeMachine = () => createMachine({}); type Interpreter = InterpreterFrom<ReturnType<typeof machine>>;
-
413a4578
#2491 Thanks @davidkpiano! - The custom.toString()
method on action objects is now removed which improves performance in larger applications (see #2488 for more context). -
5e1223cd
#2422 Thanks @davidkpiano! - Thecontext
property has been removed fromStateNodeConfig
, as it has never been allowed, nor has it ever done anything. The previous typing was unsafe and allowedcontext
to be specified on nested state nodes:createMachine({ context: { /* ... */ }, // ✅ This is allowed initial: 'inner', states: { inner: { context: { /* ... */ } // ❌ This will no longer compile } } });
-
5b70c2ff
#2508 Thanks @davidkpiano! - A race condition occurred when a child service is immediately stopped and the parent service tried to remove it from its undefined state (during its own initialization). This has been fixed, and the race condition no longer occurs. See this issue for details. -
5a9500d1
#2522 Thanks @farskid, @Andarist! - Adjusted TS type definitions of thewithContext
andwithConfig
methods so that they accept "lazy context" now.Example:
const copy = machine.withContext(() => ({ ref: spawn(() => {}) }));
-
84f9fcae
#2540 Thanks @Andarist! - Fixed an issue withstate.hasTag('someTag')
crashing when thestate
was rehydrated. -
c17dd376
#2496 Thanks @VanTanev! - Add utility typeEmittedFrom<T>
that extractsEmitted
type from any type which can emit data
-
141c91cf
#2436 Thanks @Andarist! - Fixed an issue where, when usingmodel.createMachine
, state's context was incorrectly inferred asany
after refinement with.matches(...)
, e.g.// `state.context` became `any` erroneously if (state.matches('inactive')) { console.log(state.context.count); }
-
7dc7ceb8
#2379 Thanks @davidkpiano! - There is a new.preserveActionOrder
(default:false
) setting in the machine configuration that preserves the order of actions when set totrue
. Normally, actions are executed in order except forassign(...)
actions, which are prioritized and executed first. When.preserveActionOrder
is set totrue
,assign(...)
actions will not be prioritized, and will instead run in order. As a result, actions will capture the intermediatecontext
values instead of the resultingcontext
value from allassign(...)
actions.// With `.preserveActionOrder: true` const machine = createMachine({ context: { count: 0 }, entry: [ (ctx) => console.log(ctx.count), // 0 assign({ count: (ctx) => ctx.count + 1 }), (ctx) => console.log(ctx.count), // 1 assign({ count: (ctx) => ctx.count + 1 }), (ctx) => console.log(ctx.count) // 2 ], preserveActionOrder: true }); // With `.preserveActionOrder: false` (default) const machine = createMachine({ context: { count: 0 }, entry: [ (ctx) => console.log(ctx.count), // 2 assign({ count: (ctx) => ctx.count + 1 }), (ctx) => console.log(ctx.count), // 2 assign({ count: (ctx) => ctx.count + 1 }), (ctx) => console.log(ctx.count) // 2 ] // preserveActionOrder: false });
-
4e305372
#2361 Thanks @woutermont! - Add type forSymbol.observable
to theInterpreter
to improve the compatibility with RxJS. -
1def6cf6
#2374 Thanks @davidkpiano! - Existing actors can now be identified inspawn(...)
calls by providing anid
. This allows them to be referenced by string:const machine = createMachine({ context: () => ({ someRef: spawn(someExistingRef, 'something') }), on: { SOME_EVENT: { actions: send('AN_EVENT', { to: 'something' }) } } });
-
da6861e3
#2391 Thanks @davidkpiano! - There are two new helper types for extractingcontext
andevent
types:ContextFrom<T>
which extracts thecontext
from any type that uses contextEventFrom<T>
which extracts theevent
type (which extendsEventObject
) from any type which uses events
-
1b32aa0d
#2356 Thanks @davidkpiano! - The model created fromcreateModel(...)
now provides a.createMachine(...)
method that does not require passing any generic type parameters:const model = createModel(/* ... */); -const machine = createMachine<typeof model>(/* ... */); +const machine = model.createMachine(/* ... */);
-
432b60f7
#2280 Thanks @davidkpiano! - Actors can now be invoked/spawned from reducers using thefromReducer(...)
behavior creator:import { fromReducer } from 'xstate/lib/behaviors'; type CountEvent = { type: 'INC' } | { type: 'DEC' }; const countReducer = (count: number, event: CountEvent): number => { if (event.type === 'INC') { return count + 1; } else if (event.type === 'DEC') { return count - 1; } return count; }; const countMachine = createMachine({ invoke: { id: 'count', src: () => fromReducer(countReducer, 0) }, on: { INC: { actions: forwardTo('count') }, DEC: { actions: forwardTo('count') } } });
-
f9bcea2c
#2366 Thanks @davidkpiano! - Actors can now be spawned directly in the initialmachine.context
using lazy initialization, avoiding the need for intermediate states and unsafe typings for immediately spawned actors:const machine = createMachine<{ ref: ActorRef<SomeEvent> }>({ context: () => ({ ref: spawn(anotherMachine, 'some-id') // spawn immediately! }) // ... });
1ef29e83
#2343 Thanks @davidkpiano! - Eventless ("always") transitions will no longer be ignored if an event is sent to a machine in a state that does not have any enabled transitions for that event.
-
99bc5fb9
#2275 Thanks @davidkpiano! - TheSpawnedActorRef
TypeScript interface has been deprecated in favor of a unifiedActorRef
interface, which contains the following:interface ActorRef<TEvent extends EventObject, TEmitted = any> extends Subscribable<TEmitted> { send: (event: TEvent) => void; id: string; subscribe(observer: Observer<T>): Subscription; subscribe( next: (value: T) => void, error?: (error: any) => void, complete?: () => void ): Subscription; getSnapshot: () => TEmitted | undefined; }
For simpler actor-ref-like objects, the
BaseActorRef<TEvent>
interface has been introduced.interface BaseActorRef<TEvent extends EventObject> { send: (event: TEvent) => void; }
-
38e6a5e9
#2334 Thanks @davidkpiano! - When using a model type increateMachine<typeof someModel>(...)
, TypeScript will no longer compile machines that are missing thecontext
property in the machine configuration:const machine = createMachine<typeof someModel>({ // missing context - will give a TS error! // context: someModel.initialContext, initial: 'somewhere', states: { somewhere: {} } });
-
5f790ba5
#2320 Thanks @davidkpiano! - The typing forInvokeCallback
have been improved for better event constraints when using thesendBack
parameter of invoked callbacks:invoke: () => (sendBack, receive) => { // Will now be constrained to events that the parent machine can receive sendBack({ type: 'SOME_EVENT' }); };
-
2de3ec3e
#2272 Thanks @davidkpiano! - Thestate.meta
value is now calculated directly fromstate.configuration
. This is most useful when starting a service from a persisted state:const machine = createMachine({ id: 'test', initial: 'first', states: { first: { meta: { name: 'first state' } }, second: { meta: { name: 'second state' } } } }); const service = interpret(machine); service.start('second'); // `meta` will be computed // the state will have // meta: { // 'test.second': { // name: 'second state' // } // } });
-
28059b9f
#2197 Thanks @davidkpiano! - All spawned and invoked actors now have a.getSnapshot()
method, which allows you to retrieve the latest value emitted from that actor. That value may beundefined
if no value has been emitted yet.const machine = createMachine({ context: { promiseRef: null }, initial: 'pending', states: { pending: { entry: assign({ promiseRef: () => spawn(fetch(/* ... */), 'some-promise') }) } } }); const service = interpret(machine) .onTransition((state) => { // Read promise value synchronously const resolvedValue = state.context.promiseRef?.getSnapshot(); // => undefined (if promise not resolved yet) // => { ... } (resolved data) }) .start(); // ...
4ef03465
#2240 Thanks @VanTanev! - Preserve StateMachine type when .withConfig() and .withContext() modifiers are used on a machine.
-
18789aa9
#2107 Thanks @woutermont! - This update restricts invokedSubscribable
s toEventObject
s, so that type inference can be done on whichSubscribable
s are allowed to be invoked. ExistingMachineConfig
s that invokeSubscribable<any>
s that are notSubscribable<EventObject>
s should be updated accordingly. -
38dcec1d
#2149 Thanks @davidkpiano! - Invocations and entry actions for combinatorial machines (machines with only a single root state) now behave predictably and will not re-execute upon targetless transitions.
64ab1150
#2173 Thanks @Andarist! - Fixed an issue with tags not being set correctly after sending an event to a machine that didn't result in selecting any transitions.
-
4f2f626d
#2143 Thanks @davidkpiano! - Tags can now be added to state node configs under the.tags
property:const machine = createMachine({ initial: 'green', states: { green: { tags: 'go' // single tag }, yellow: { tags: 'go' }, red: { tags: ['stop', 'other'] // multiple tags } } });
You can query whether a state has a tag via
state.hasTag(tag)
:const canGo = state.hasTag('go'); // => `true` if in 'green' or 'red' state
a61d01ce
#2125 Thanks @VanTanev! - In callback invokes, the types ofcallback
andonReceive
are properly scoped to the machine TEvent.
-
d0939ec6
#2046 Thanks @SimeonC! - Allow machines to communicate with the inspector even in production builds. -
e37fffef
#2079 Thanks @davidkpiano! - There is now support for "combinatorial machines" (state machines that only have one state):const testMachine = createMachine({ context: { value: 42 }, on: { INC: { actions: assign({ value: (ctx) => ctx.value + 1 }) } } });
These machines omit the
initial
andstate
properties, as the entire machine is treated as a single state.
6a9247d4
#2102 Thanks @VanTanev! - Provide a convenience type for getting theInterpreter
type based on theStateMachine
type by transferring all generic parameters onto it. It can be used like this:InterpreterFrom<typeof machine>
33302814
#2041 Thanks @Andarist! - Fixed an issue with creatorless models not being correctly matched bycreateMachine
's overload responsible for using model-induced types.
-
7763db8d
#1977 Thanks @davidkpiano! - Theschema
property has been introduced to the machine config passed intocreateMachine(machineConfig)
, which allows you to provide metadata for the following:- Context
- Events
- Actions
- Guards
- Services
This metadata can be accessed as-is from
machine.schema
:const machine = createMachine({ schema: { // Example in JSON Schema (anything can be used) context: { type: 'object', properties: { foo: { type: 'string' }, bar: { type: 'number' }, baz: { type: 'object', properties: { one: { type: 'string' } } } } }, events: { FOO: { type: 'object' }, BAR: { type: 'object' } } } // ... });
Additionally, the new
createSchema()
identity function allows any schema "metadata" to be represented by a specific type, which makes type inference easier without having to specify generic types:import { createSchema, createMachine } from 'xstate'; // Both `context` and `events` are inferred in the rest of the machine! const machine = createMachine({ schema: { context: createSchema<{ count: number }>(), // No arguments necessary events: createSchema<{ type: 'FOO' } | { type: 'BAR' }>() } // ... });
-
5febfe83
#1955 Thanks @davidkpiano! - Event creators can now be modeled inside of the 2nd argument ofcreateModel()
, and types for bothcontext
andevents
will be inferred properly increateMachine()
when given thetypeof model
as the first generic parameter.import { createModel } from 'xstate/lib/model'; const userModel = createModel( // initial context { name: 'David', age: 30 }, // creators (just events for now) { events: { updateName: (value: string) => ({ value }), updateAge: (value: number) => ({ value }), anotherEvent: () => ({}) // no payload } } ); const machine = createMachine<typeof userModel>({ context: userModel.initialContext, initial: 'active', states: { active: { on: { updateName: { /* ... */ }, updateAge: { /* ... */ } } } } }); const nextState = machine.transition( undefined, userModel.events.updateName('David') );
4194ffe8
#1710 Thanks @davidkpiano! - Stopping an already stopped interpreter will no longer crash. See #1697 for details.
af6b7c70
#1865 Thanks @Andarist! - Improved.matches(value)
inference for typestates containing union types as values.
-
d2e328f8
#1439 Thanks @davidkpiano! - An opt-increateModel()
helper has been introduced to make it easier to work with typedcontext
and events.createModel(initialContext)
creates amodel
objectmodel.initialContext
returns theinitialContext
model.assign(assigner, event?)
creates anassign
action that is properly scoped to theevent
in TypeScript
See #1439 for more details.
import { createMachine } from 'xstate'; import { createModel } from 'xstate/lib/model'; // opt-in, not part of main build interface UserContext { name: string; age: number; } type UserEvents = | { type: 'updateName'; value: string } | { type: 'updateAge'; value: number } const userModel = createModel<UserContext, UserEvents>({ name: 'David', age: 30 }); const assignName = userModel.assign({ name: (_, e) => e.value // correctly typed to `string` }, 'updateName'); // restrict to 'updateName' event const machine = createMachine<UserContext, UserEvents>({ context: userModel.context, initial: 'active', states: { active: { on: { updateName: { actions: assignName } } } } });
0cb8df9b
#1816 Thanks @Andarist! -machine.resolveState(state)
calls should resolve to the correct value of.done
property now.
63ba888e
#1770 Thanks @davidkpiano! - Instead of referencingwindow
directly, XState now internally calls agetGlobal()
function that will resolve to the properglobalThis
value in all environments. This affects the dev tools code only.
497c543d
#1766 Thanks @Andarist! - Fixed an issue with events received from callback actors not having the appropriate_event.origin
set.
8a8cfa32
#1704 Thanks @blimmer! - The defaultclock
methods (setTimeout
andclearTimeout
) are now invoked properly with the global context preserved for those invocations which matter for some JS environments. More details can be found in the corresponding issue: #1703.
-
6596d0ba
#1622 Thanks @davidkpiano! - Spawned/invoked actors and interpreters are now typed as extendingActorRef
(e.g.,SpawnedActorRef
) rather thanActor
orInterpreter
. This unification of types should make it more straightforward to provide actor types:import { - Actor + ActorRef } from 'xstate'; // ... interface SomeContext { - server?: Actor; + server?: ActorRef<ServerEvent>; }
It's also easier to specify the type of a spawned/invoked machine with
ActorRefFrom
:import { createMachine, - Actor + ActorRefFrom } from 'xstate'; const serverMachine = createMachine<ServerContext, ServerEvent>({ // ... }); interface SomeContext { - server?: Actor; // difficult to type + server?: ActorRefFrom<typeof serverMachine>; }
75a91b07
#1692 Thanks @Andarist! - Fixed an issue with history state entering a wrong state if the most recent visit in its parent has been caused by a transient transition.
02c76350
#1656 Thanks @Andarist! - Exit actions will now be properly called when a service gets canceled by calling itsstop
method.
-
119db8fb
#1577 Thanks @davidkpiano! - Expressions can now be used in thestop()
action creator:// ... actions: stop((context) => context.someActor);
-
8c78e120
#1570 Thanks @davidkpiano! - The return type ofspawn(machine)
will now beActor<State<TContext, TEvent>, TEvent>
, which is a supertype ofInterpreter<...>
. -
602687c2
#1566 Thanks @davidkpiano! - Exit actions will now be properly called when an invoked machine reaches its final state. See #1109 for more details. -
6e44d02a
#1553 Thanks @davidkpiano! - Thestate.children
property now properly shows all spawned and invoked actors. See #795 for more details. -
72b0880e
#1504 Thanks @Andarist! - Addedstatus
property on theInterpreter
- this can be used to differentiate not started, running and stopped interpreters. This property is best compared to values on the newInterpreterStatus
export.
f51614df
#1409 Thanks @jirutka! - Fix typeExtractStateValue
so that it generates a type actually describing aState.value
-
b1684ead
#1402 Thanks @Andarist! - Improved TypeScript type-checking performance a little bit by using distributive conditional type withinTransitionsConfigArray
declarations instead of a mapped type. Kudos to @amcasey, some discussion around this can be found here -
ad3026d4
#1407 Thanks @tomenden! - Fixed an issue with not being able to run XState in Web Workers due to assuming thatwindow
orglobal
object is available in the executing environment, but none of those are actually available in the Web Workers context. -
4e949ec8
#1401 Thanks @Andarist! - Fixed an issue with spawned actors being spawned multiple times when they got spawned in an initial state of a child machine that is invoked in the initial state of a parent machine.Illustrating example for curious readers.
const child = createMachine({ initial: 'bar', context: {}, states: { bar: { entry: assign({ promise: () => { return spawn(() => Promise.resolve('answer')); } }) } } }); const parent = createMachine({ initial: 'foo', states: { foo: { invoke: { src: child, onDone: 'end' } }, end: { type: 'final' } } }); interpret(parent).start();
-
b72e29dd
#1354 Thanks @davidkpiano! - TheAction
type was simplified, and as a result, you should see better TypeScript performance. -
4dbabfe7
#1320 Thanks @davidkpiano! - Theinvoke.src
property now accepts an object that describes the invoke source with itstype
and other related metadata. This can be read from theservices
option in themeta.src
argument:const machine = createMachine( { initial: 'searching', states: { searching: { invoke: { src: { type: 'search', endpoint: 'example.com' } // ... } // ... } } }, { services: { search: (context, event, { src }) => { console.log(src); // => { endpoint: 'example.com' } } } } );
Specifying a string for
invoke.src
will continue to work the same; e.g., ifsrc: 'search'
was specified, this would be the same assrc: { type: 'search' }
. -
8662e543
#1317 Thanks @Andarist! - AllTTypestate
type parameters default to{ value: any; context: TContext }
now and the parametrized type is passed correctly between various types which results in more accurate types involving typestates.
-
3ab3f25e
#1285 Thanks @Andarist! - Fixed an issue with initial state of invoked machines being read without custom data passed to them which could lead to a crash when evaluating transient transitions for the initial state. -
a7da1451
#1290 Thanks @davidkpiano! - The "Attempted to spawn an Actor [...] outside of a service. This will have no effect." warnings are now silenced for "lazily spawned" actors, which are actors that aren't immediately active until the function that creates them are called:// ⚠️ "active" actor - will warn spawn(somePromise); // 🕐 "lazy" actor - won't warn spawn(() => somePromise); // 🕐 machines are also "lazy" - won't warn spawn(someMachine);
It is recommended that all
spawn(...)
-ed actors are lazy, to avoid accidentally initializing them e.g., when readingmachine.initialState
or calculating otherwise pure transitions. In V5, this will be enforced. -
c1f3d260
#1317 Thanks @Andarist! - Fixed a type returned by araise
action - it's nowRaiseAction<TEvent> | SendAction<TContext, AnyEventObject, TEvent>
instead ofRaiseAction<TEvent> | SendAction<TContext, TEvent, TEvent>
. This makes it comaptible in a broader range of scenarios. -
8270d5a7
#1372 Thanks @christianchown! - Narrowed theServiceConfig
type definition to use a specific event type to prevent compilation errors on strictly-typedMachineOptions
. -
01e3e2dc
#1320 Thanks @davidkpiano! - The JSON definition forstateNode.invoke
objects will no longer include theonDone
andonError
transitions, since those transitions are already merged into thetransitions
array. This solves the issue of reviving a serialized machine from JSON, where before, theonDone
andonError
transitions for invocations were wrongly duplicated.
-
36ed8d0a
#1262 Thanks @Andarist! - Improved type inference forInvokeConfig['data']
. This has required renamingdata
property onStateNode
instances todoneData
. This property was never meant to be a part of the public API, so we don't consider this to be a breaking change. -
2c75ab82
#1219 Thanks @davidkpiano! - The resolved value of theinvoke.data
property is now available in the "invoke meta" object, which is passed as the 3rd argument to the service creator inoptions.services
. This will work for all types of invoked services now, including promises, observables, and callbacks.const machine = createMachine({ initial: 'pending', context: { id: 42 }, states: { pending: { invoke: { src: 'fetchUser', data: { userId: (context) => context.id }, onDone: 'success' } }, success: { type: 'final' } } }, { services: { fetchUser: (ctx, _, { data }) => { return fetch(`some/api/user/${data.userId}`) .then(response => response.json()); } } }
-
a6c78ae9
#1249 Thanks @davidkpiano! - New property introduced for eventless (transient) transitions:always
, which indicates a transition that is always taken when in that state. Empty string transition configs for transient transitions are deprecated in favor ofalways
:// ... states: { playing: { + always: [ + { target: 'win', cond: 'didPlayerWin' }, + { target: 'lose', cond: 'didPlayerLose' }, + ], on: { // ⚠️ Deprecation warning - '': [ - { target: 'win', cond: 'didPlayerWin' }, - { target: 'lose', cond: 'didPlayerLose' }, - ] } } } // ...
The old empty string syntax (
'': ...
) will continue to work until V5.
36ed8d0a
#1262 Thanks @Andarist! -StateMachine<any, any, any>
is no longer a part of theInvokeConfig
type, but rather it creates a union withInvokeConfig
in places where it is needed. This change shouldn't affect consumers' code.
-
0133954
#1178 Thanks @davidkpiano! - The types for thesend()
andsendParent()
action creators have been changed to fix the issue of only being able to send events that the machine can receive. In reality, a machine can and should send events to other actors that it might not be able to receive itself. See #711 for more information. -
a1f1239
#1189 Thanks @davidkpiano! - Previously,state.matches(...)
was problematic because it was castingstate
tonever
if it didn't match the state value. This is now fixed by making theTypestate
resolution more granular. -
dbc6a16
#1183 Thanks @davidkpiano! - Actions from a restored state provided as a custom initial state tointerpret(machine).start(initialState)
are now executed properly. See #1174 for more information.
-
a10d604
#1176 Thanks @itfarrier! - Fix passing state schema into State generic -
326db72
#1185 Thanks @Andarist! - Fixed an issue with invoked service not being correctly started if other service got stopped in a subsequent microstep (in response to raised or null event). -
c3a496e
#1160 Thanks @davidkpiano! - Delayed transitions defined usingafter
were previously causing a circular dependency when the machine was converted using.toJSON()
. This has now been fixed. -
e16e48e
#1153 Thanks @Andarist! - Fixed an issue withchoose
andpure
not being able to use actions defined in options. -
d496ecb
#1165 Thanks @davidkpiano! - XState will now warn if you define an.onDone
transition on the root node. Root nodes which are "done" represent the machine being in its final state, and can no longer accept any events. This has been reported as confusing in #1111.
-
8a97785
#1137 Thanks @davidkpiano! - Added docs for thechoose()
andpure()
action creators, as well as exporting thepure()
action creator in theactions
object. -
e65dee9
#1131 Thanks @wKovacs64! - Include the newchoose
action in theactions
export from thexstate
core package. This was missed in v4.9.0.
-
f3ff150
#1103 Thanks @davidkpiano! - Simplify theTransitionConfigArray
andTransitionConfigMap
types in order to fix excessively deep type instantiation TypeScript reports. This addresses #1015. -
6c47b66
#1076 Thanks @Andarist! - Added support for conditional actions. It's possible now to have actions executed based on conditions using following:entry: [ choose([ { cond: (ctx) => ctx > 100, actions: raise('TOGGLE') }, { cond: 'hasMagicBottle', actions: [assign((ctx) => ({ counter: ctx.counter + 1 }))] }, { actions: ['fallbackAction'] } ]) ];
It works very similar to the if-else syntax where only the first matched condition is causing associated actions to be executed and the last ones can be unconditional (serving as a general fallback, just like else branch).
-
1a129f0
#1073 Thanks @Andarist! - Cleanup internal structures upon receiving termination events from spawned actors. -
e88aa18
#1085 Thanks @Andarist! - Fixed an issue with data expressions of root's final nodes being called twice. -
88b17b2
#1090 Thanks @rjdestigter! - This change carries forward the typestate type information encoded in the arguments of the following functions and assures that the return type also has the same typestate type information:- Cloned state machine returned by
.withConfig
. .state
getter defined for services.start
method of services.
- Cloned state machine returned by
-
d5f622f
#1069 Thanks @davidkpiano! - Loosened event type forSendAction<TContext, AnyEventObject>
-
55aa589
#960 Thanks @davidkpiano! - The machine can now be safely JSON-serialized, usingJSON.stringify(machine)
. The shape of this serialization is defined inmachine.schema.json
and reflected inmachine.definition
.Note that
onEntry
andonExit
have been deprecated in the definition in favor ofentry
andexit
.
520580b
#967 Thanks @andrewgordstewart! - Add context & event types to InvokeConfig
-
c8db035
#936 Thanks @davidkpiano! - Theescalate()
action can now take in an expression, which will be evaluated against thecontext
,event
, andmeta
to return the error data. -
2a3fea1
#952 Thanks @davidkpiano! - The typings for the raise() action have been fixed to allow any event to be raised. This typed behavior will be refined in version 5, to limit raised events to those that the machine accepts. -
f86d419
#957 Thanks @Andarist! - Fixed memory leak - each created service has been registered in internal map but it was never removed from it. Registration has been moved to a point where Interpreter is being started and it's deregistered when it is being stopped.
- dae8818: Typestates are now propagated to interpreted services.
- 6b3d767: Fixed issue with delayed transitions scheduling a delayed event for each transition defined for a single delay.
- 9b043cd: The initial state is now cached inside of the service instance instead of the machine, which was the previous (faulty) strategy. This will prevent entry actions on initial states from being called more than once, which is important for ensuring that actors are not spawned more than once.
- 2b134eee: Fixed issue with events being forwarded to children after being processed by the current machine. Events are now always forwarded first.
- 2b134eee: Fixed issue with not being able to spawn an actor when processing an event batch.