Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[core] Transition function #4954

Merged
merged 98 commits into from
Nov 12, 2024
Merged
Show file tree
Hide file tree
Changes from 84 commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
9f6f015
Use defaultActionExecutor (temp)
davidkpiano Jun 4, 2024
abfe358
Convert entry/exit events
davidkpiano Jun 4, 2024
6e181ea
UnknownAction -> UnknownActionObject
davidkpiano Jun 4, 2024
f1100ab
Use defaultActionExecutor
davidkpiano Jun 5, 2024
8e25fa1
Cleanup
davidkpiano Jun 5, 2024
b2b974a
WIP
davidkpiano Jun 5, 2024
6dd475c
Merge branch 'main' into davidkpiano/transition
davidkpiano Jun 12, 2024
6533540
Merge branch 'main' into davidkpiano/transition
davidkpiano Jun 18, 2024
7641ec5
Fixing tests
davidkpiano Jun 19, 2024
9942382
Add toJSON to built-in actions
davidkpiano Jun 19, 2024
f566149
Merge branch 'main' into davidkpiano/transition
davidkpiano Jun 23, 2024
efaac40
Add docs
davidkpiano Jun 25, 2024
8b936ab
Update packages/core/src/actions/spawnChild.ts
davidkpiano Jul 3, 2024
ae70cc6
Update packages/core/src/stateUtils.ts
davidkpiano Jul 3, 2024
f3c360f
Unify convertAction
davidkpiano Jul 4, 2024
32b278c
Update packages/core/src/stateUtils.ts
davidkpiano Jul 4, 2024
b1d346e
Remove TODO
davidkpiano Jul 4, 2024
4f61ac7
Introduce `executeAction`, remove `action.execute()`
davidkpiano Jul 4, 2024
bc431d1
Enqueue actions test
davidkpiano Jul 4, 2024
96ba826
Fix type error
davidkpiano Jul 4, 2024
abf3a2c
Expose `executeAction(…)`
davidkpiano Jul 8, 2024
d97cb8f
Provide actor to action
davidkpiano Jul 8, 2024
5f6a00b
Fix type issue
davidkpiano Jul 8, 2024
c0597ee
Merge branch 'main' into davidkpiano/transition
davidkpiano Jul 11, 2024
59daf7c
Merge branch 'main' into davidkpiano/transition
davidkpiano Jul 11, 2024
1f13746
Merge branch 'main' into davidkpiano/transition
davidkpiano Jul 13, 2024
a73a0db
Changeset
davidkpiano Jul 13, 2024
4c8d491
Deprecate getNextSnapshot and getInitialSnapshot
davidkpiano Jul 14, 2024
cbb0a7f
Update packages/core/src/stateUtils.ts
davidkpiano Jul 18, 2024
18f0015
Restore getNextSnapshot.test.ts file
davidkpiano Jul 18, 2024
e2e821c
function -> exec
davidkpiano Jul 18, 2024
ad422a8
Merge branch 'main' into davidkpiano/transition
davidkpiano Jul 24, 2024
737f67f
Merge branch 'main' into davidkpiano/transition
davidkpiano Jul 30, 2024
f7991ad
Merge branch 'main' into davidkpiano/transition
davidkpiano Aug 3, 2024
97713c5
Merge branch 'main' into davidkpiano/transition
davidkpiano Aug 7, 2024
0c29c2c
Delayed raise action test
davidkpiano Aug 7, 2024
6e18296
Getting close
davidkpiano Aug 8, 2024
7146bdc
Update scheduler to handle delayed sendTo actions without an initiall…
davidkpiano Aug 9, 2024
e776f2c
Merge branch 'main' into davidkpiano/transition
davidkpiano Aug 10, 2024
3d76b58
Fix types
davidkpiano Aug 10, 2024
7e05a9a
Merge branch 'main' into davidkpiano/transition
davidkpiano Aug 17, 2024
857c96c
WIP
davidkpiano Aug 17, 2024
59eedc2
Remove test code
davidkpiano Aug 28, 2024
574c4c9
Merge branch 'main' into davidkpiano/transition
davidkpiano Sep 1, 2024
4cdd545
Default actor
davidkpiano Sep 1, 2024
d2e583f
Serialization in test
davidkpiano Sep 1, 2024
fea607b
Revert invoke.test.ts
davidkpiano Sep 5, 2024
7a33175
Cancel action execution
davidkpiano Sep 5, 2024
0c80c66
WIP
davidkpiano Sep 5, 2024
f596786
Merge branch 'main' into davidkpiano/transition
davidkpiano Sep 14, 2024
062676c
Update launch.json and jest.config.js
davidkpiano Sep 14, 2024
2d480e1
Proof of concept for invoked actions
davidkpiano Sep 14, 2024
2e450c7
Merge branch 'main' into davidkpiano/transition
davidkpiano Sep 23, 2024
a75ceb9
Include resolved input & systemId in spawnChild action
davidkpiano Sep 23, 2024
200fc12
Refactor action types to use ExecutableActionObject and add startedAt…
davidkpiano Sep 24, 2024
edb0615
Merge branch 'main' into davidkpiano/transition
davidkpiano Sep 26, 2024
96f4ac4
Add ExecutableActionsFrom
davidkpiano Sep 26, 2024
9c077d6
Clean up types
davidkpiano Sep 28, 2024
5bd5e09
Merge branch 'main' into davidkpiano/transition
davidkpiano Oct 9, 2024
cf9d549
Lint
davidkpiano Oct 9, 2024
68654be
Lint for real
davidkpiano Oct 9, 2024
7622a79
Lint lint
davidkpiano Oct 9, 2024
4f61f39
Back to any
davidkpiano Oct 9, 2024
a365418
Update packages/core/test/transition.test.ts
davidkpiano Oct 9, 2024
f6de768
use sleep
Andarist Oct 11, 2024
1977a9d
remove outdated comment
Andarist Oct 11, 2024
d202b93
add `ExecutableSendToAction` to `SpecialExecutableAction`
Andarist Oct 11, 2024
0c344da
remove `startedAt`
Andarist Oct 11, 2024
ece423d
add failing raise test case
Andarist Oct 12, 2024
9576616
tweak test title
Andarist Oct 12, 2024
85b1f67
add extra cancel tests
Andarist Oct 12, 2024
9d3f2a0
add failing test for invalid event delivery
Andarist Oct 12, 2024
4516833
Revert test (happens in main)
davidkpiano Oct 13, 2024
ab4bad0
Remove invalid test: The <cancel> element is used to cancel a delayed…
davidkpiano Oct 20, 2024
73c6b22
Fixed tests
Andarist Oct 21, 2024
07707e7
remove unused import
Andarist Oct 21, 2024
898fa99
add warn assertions
Andarist Oct 21, 2024
71324ad
Revert "Remove invalid test: The <cancel> element is used to cancel a…
Andarist Oct 23, 2024
ca2977f
make it green, make it green
Andarist Oct 25, 2024
c50d849
add action resolution capabilities to machine.executeAction
Andarist Oct 26, 2024
a7e83ce
share `resolvedInfo` between branches
Andarist Oct 26, 2024
3474c5c
bring back `ExecutableActionObject['exec']`
Andarist Oct 26, 2024
94d44f0
add a failing boilerplate for `cancel` execution
Andarist Oct 30, 2024
6242d5d
Fix cancel action
davidkpiano Nov 2, 2024
2a573fb
Add SpecialActionResolution type
davidkpiano Nov 2, 2024
91128d8
Add test for sendTo action
davidkpiano Nov 3, 2024
002670f
actorId -> targetId
davidkpiano Nov 4, 2024
a2d8fb0
Rename
davidkpiano Nov 4, 2024
b47f77f
Add tests for emit and log
davidkpiano Nov 4, 2024
c04c12c
Remove switch statement in executeAction
davidkpiano Nov 12, 2024
9772108
Merge branch 'main' into davidkpiano/transition
davidkpiano Nov 12, 2024
e87d847
Undo
davidkpiano Nov 12, 2024
a58a3f2
Remove executeAction for now
davidkpiano Nov 12, 2024
b0a7992
bring back one `toSerializableAction` call
Andarist Nov 12, 2024
c894ad4
fix one type issue
Andarist Nov 12, 2024
03dac75
tweak test titles
Andarist Nov 12, 2024
fa47906
remove redundant test
Andarist Nov 12, 2024
9ee7564
dont export `getAction`
Andarist Nov 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .changeset/lemon-needles-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'xstate': minor
---

Added a new `transition` function that takes an actor logic, a snapshot, and an event, and returns a tuple containing the next snapshot and the actions to execute. This function is a pure function and does not execute the actions itself. It can be used like this:

```ts
import { transition } from 'xstate';

const [nextState, actions] = transition(actorLogic, currentState, event);
// Execute actions as needed
```

Added a new `initialTransition` function that takes an actor logic and an optional input, and returns a tuple containing the initial snapshot and the actions to execute from the initial transition. This function is also a pure function and does not execute the actions itself. It can be used like this:

```ts
import { initialTransition } from 'xstate';

const [initialState, actions] = initialTransition(actorLogic, input);
// Execute actions as needed
```

These new functions provide a way to separate the calculation of the next snapshot and actions from the execution of those actions, allowing for more control and flexibility in the transition process.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
"program": "${workspaceFolder}/node_modules/jest/bin/jest.js"
}
},
{
Expand All @@ -30,7 +30,7 @@
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"windows": {
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
"program": "${workspaceFolder}/node_modules/jest/bin/jest.js"
}
}
]
Expand Down
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,16 @@ Read [📽 the slides](http://slides.com/davidkhourshid/finite-state-machines) (

## Packages

| Package | Description |
| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| 🤖 `xstate` | Core finite state machine and statecharts library + interpreter |
| [📉 `@xstate/graph`](https://github.com/statelyai/xstate/tree/main/packages/xstate-graph) | Graph traversal and model-based testing utilities using XState |
| [⚛️ `@xstate/react`](https://github.com/statelyai/xstate/tree/main/packages/xstate-react) | React hooks and utilities for using XState in React applications |
| [💚 `@xstate/vue`](https://github.com/statelyai/xstate/tree/main/packages/xstate-vue) | Vue composition functions and utilities for using XState in Vue applications |
| [🎷 `@xstate/svelte`](https://github.com/statelyai/xstate/tree/main/packages/xstate-svelte) | Svelte utilities for using XState in Svelte applications |
| [🥏 `@xstate/solid`](https://github.com/statelyai/xstate/tree/main/packages/xstate-solid) | Solid hooks and utilities for using XState in Solid applications |
| [🔍 `@statelyai/inspect`](https://github.com/statelyai/inspect) | Inspection utilities for XState |
| [🏪 `@xstate/store`](https://github.com/statelyai/xstate/tree/main/packages/xstate-store) | Small library for simple state management |
| Package | Description |
| ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| 🤖 `xstate` | Core finite state machine and statecharts library + interpreter |
| [📉 `@xstate/graph`](https://github.com/statelyai/xstate/tree/main/packages/xstate-graph) | Graph traversal and model-based testing utilities using XState |
| [⚛️ `@xstate/react`](https://github.com/statelyai/xstate/tree/main/packages/xstate-react) | React hooks and utilities for using XState in React applications |
| [💚 `@xstate/vue`](https://github.com/statelyai/xstate/tree/main/packages/xstate-vue) | Vue composition functions and utilities for using XState in Vue applications |
| [🎷 `@xstate/svelte`](https://github.com/statelyai/xstate/tree/main/packages/xstate-svelte) | Svelte utilities for using XState in Svelte applications |
| [🥏 `@xstate/solid`](https://github.com/statelyai/xstate/tree/main/packages/xstate-solid) | Solid hooks and utilities for using XState in Solid applications |
| [🔍 `@statelyai/inspect`](https://github.com/statelyai/inspect) | Inspection utilities for XState |
| [🏪 `@xstate/store`](https://github.com/statelyai/xstate/tree/main/packages/xstate-store) | Small library for simple state management |

## Finite State Machines

Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const { constants } = require('jest-config');
/** @type {import('@jest/types').Config.InitialOptions} */
module.exports = {
prettierPath: null,
setupFilesAfterEnv: ['@xstate-repo/jest-utils/setup'],
setupFilesAfterEnv: ['<rootDir>/scripts/jest-utils/setup'],
transform: {
[constants.DEFAULT_JS_PATTERN]: 'babel-jest',
'^.+\\.vue$': '@vue/vue3-jest',
Expand Down
69 changes: 65 additions & 4 deletions packages/core/src/StateMachine.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import isDevelopment from '#is-development';
import { assign } from './actions.ts';
import { executeCancel } from './actions/cancel.ts';
import { executeRaise } from './actions/raise.ts';
import { executeSendTo } from './actions/send.ts';
import { createEmptyActor } from './actors/index.ts';
import { $$ACTOR_TYPE, createActor } from './createActor.ts';
import { createInitEvent } from './eventUtils.ts';
import {
Expand All @@ -18,6 +22,7 @@ import {
macrostep,
microstep,
resolveActionsAndContext,
resolveReferencedAction,
resolveStateValue,
transitionNode
} from './stateUtils.ts';
Expand Down Expand Up @@ -48,7 +53,8 @@ import type {
TransitionDefinition,
ResolvedStateMachineTypes,
StateSchema,
SnapshotStatus
SnapshotStatus,
ExecutableActionObject
} from './types.ts';
import { resolveReferencedActor, toStatePath } from './utils.ts';

Expand Down Expand Up @@ -293,7 +299,8 @@ export class StateMachine<
TMeta,
TConfig
> {
return macrostep(snapshot, event, actorScope).snapshot as typeof snapshot;
return macrostep(snapshot, event, actorScope, [])
.snapshot as typeof snapshot;
}

/**
Expand Down Expand Up @@ -328,7 +335,7 @@ export class StateMachine<
TConfig
>
> {
return macrostep(snapshot, event, actorScope).microstates;
return macrostep(snapshot, event, actorScope, []).microstates;
}

public getTransitionData(
Expand Down Expand Up @@ -386,7 +393,8 @@ export class StateMachine<
initEvent,
actorScope,
[assign(assignment)],
internalQueue
internalQueue,
undefined
) as SnapshotFrom<this>;
}

Expand Down Expand Up @@ -629,4 +637,57 @@ export class StateMachine<

return restoredSnapshot;
}

/**
* Runs an executable action. Executable actions are returned from the
* `transition(…)` function.
*
* @example
*
* ```ts
* const [state, actions] = transition(someMachine, someState, someEvent);
*
* for (const action of actions) {
* // Executes the action
* someMachine.executeAction(action);
* }
* ```
*/
public executeAction(
action: ExecutableActionObject,
actor: AnyActorRef = createEmptyActor()
) {
const actorScope = (actor as any)._actorScope as AnyActorScope;
const defer = actorScope.defer;
actorScope.defer = (fn) => fn();
try {
switch (action.type) {
case 'xstate.cancel':
executeCancel(actorScope, action.params as any);
return;
case 'xstate.raise':
if (typeof (action as any).params.delay !== 'number') {
return;
}
executeRaise(actorScope, action.params as any);
return;
case 'xstate.sendTo':
executeSendTo(actorScope, action.params as any);
return;
}
davidkpiano marked this conversation as resolved.
Show resolved Hide resolved
const resolvedInfo = {
...action.info,
self: actor,
system: actor.system
};
if (action.exec) {
action.exec?.(resolvedInfo, action.params);
} else {
const resolvedAction = resolveReferencedAction(this, action.type)!;
resolvedAction(resolvedInfo, action.params);
}
} finally {
actorScope.defer = defer;
}
}
}
12 changes: 7 additions & 5 deletions packages/core/src/StateNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NULL_EVENT, STATE_DELIMITER } from './constants.ts';
import { evaluateGuard } from './guards.ts';
import { memo } from './memo.ts';
import {
BuiltinAction,
formatInitialTransition,
formatTransition,
formatTransitions,
Expand Down Expand Up @@ -47,7 +48,7 @@ const toSerializableAction = (action: UnknownAction) => {
}
if (typeof action === 'function') {
if ('resolve' in action) {
return { type: (action as any).type };
return { type: (action as BuiltinAction).type };
}
return {
type: action.name
Expand Down Expand Up @@ -261,7 +262,7 @@ export class StateNode<
on: this.on,
transitions: [...this.transitions.values()].flat().map((t) => ({
...t,
actions: t.actions.map(toSerializableAction)
actions: t.actions
})),
entry: this.entry.map(toSerializableAction),
exit: this.exit.map(toSerializableAction),
Expand Down Expand Up @@ -296,21 +297,22 @@ export class StateNode<
toArray(this.config.invoke).map((invokeConfig, i) => {
const { src, systemId } = invokeConfig;
const resolvedId = invokeConfig.id ?? createInvokeId(this.id, i);
const resolvedSrc =
const sourceName =
typeof src === 'string'
? src
: `xstate.invoke.${createInvokeId(this.id, i)}`;

return {
...invokeConfig,
src: resolvedSrc,
src: sourceName,
id: resolvedId,
systemId: systemId,
toJSON() {
const { onDone, onError, ...invokeDefValues } = invokeConfig;
return {
...invokeDefValues,
type: 'xstate.invoke',
src: resolvedSrc,
src: sourceName,
id: resolvedId
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/actions/assign.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import isDevelopment from '#is-development';
import { cloneMachineSnapshot } from '../State.ts';
import { executingCustomAction } from '../createActor.ts';
import { Spawner, createSpawner } from '../spawn.ts';
import { executingCustomAction } from '../stateUtils.ts';
import type {
ActionArgs,
AnyActorScope,
Expand Down
14 changes: 9 additions & 5 deletions packages/core/src/actions/cancel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
EventObject,
MachineContext,
ActionArgs,
ParameterizedObject
ParameterizedObject,
SpecialActionResolution
} from '../types.ts';

type ResolvableSendId<
Expand All @@ -26,15 +27,18 @@ function resolveCancel(
actionArgs: ActionArgs<any, any, any>,
actionParams: ParameterizedObject['params'] | undefined,
{ sendId }: { sendId: ResolvableSendId<any, any, any, any> }
) {
): SpecialActionResolution {
const resolvedSendId =
typeof sendId === 'function' ? sendId(actionArgs, actionParams) : sendId;
return [snapshot, resolvedSendId];
return [snapshot, { sendId: resolvedSendId }];
}

function executeCancel(actorScope: AnyActorScope, resolvedSendId: string) {
export function executeCancel(
actorScope: AnyActorScope,
params: { sendId: string }
) {
actorScope.defer(() => {
actorScope.system.scheduler.cancel(actorScope.self, resolvedSendId);
actorScope.system.scheduler.cancel(actorScope.self, params.sendId);
});
}

Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/actions/emit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isDevelopment from '#is-development';
import { executingCustomAction } from '../stateUtils.ts';
import { executingCustomAction } from '../createActor.ts';
import {
ActionArgs,
ActionFunction,
Expand Down
23 changes: 20 additions & 3 deletions packages/core/src/actions/raise.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import isDevelopment from '#is-development';
import { executingCustomAction } from '../stateUtils.ts';
import { executingCustomAction } from '../createActor.ts';
import {
ActionArgs,
ActionFunction,
Expand All @@ -9,6 +9,7 @@ import {
DelayExpr,
DoNotInfer,
EventObject,
ExecutableActionObject,
MachineContext,
ParameterizedObject,
RaiseActionOptions,
Expand Down Expand Up @@ -75,10 +76,17 @@ function resolveRaise(
if (typeof resolvedDelay !== 'number') {
internalQueue.push(resolvedEvent);
}
return [snapshot, { event: resolvedEvent, id, delay: resolvedDelay }];
return [
snapshot,
{
event: resolvedEvent,
id,
delay: resolvedDelay
}
];
}

function executeRaise(
export function executeRaise(
actorScope: AnyActorScope,
params: {
event: EventObject;
Expand Down Expand Up @@ -168,3 +176,12 @@ export function raise<

return raise;
}

export interface ExecutableRaiseAction extends ExecutableActionObject {
type: 'xstate.raise';
params: {
event: EventObject;
id: string | undefined;
delay: number | undefined;
};
}
Loading