Skip to content

Commit

Permalink
Introduce setup() API (#4353)
Browse files Browse the repository at this point in the history
* Tests

* Rename

* Convert some types

* Add setup function to exports

* Rework setup inference and specifically guard inferences to aid higher order guards (#4474)

* Rework setup inference and specifically guard inferences to aid higher order guards

* move tests

* add extra runtime tests for guards execution

* fixed `not` types

* shift one extra type-level test

* Remove `TParams` from `choose` and `pure`

* Remove generics provided by `setup` from `setup().createMachine`

* Resolve `TypegenDisabled` type in `setup`

* Removed unused type parameters from `InternalMachineImplementations`

* Add changeset

---------

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
  • Loading branch information
davidkpiano and Andarist committed Nov 21, 2023
1 parent 6777c32 commit a3a11c8
Show file tree
Hide file tree
Showing 19 changed files with 1,302 additions and 185 deletions.
46 changes: 46 additions & 0 deletions .changeset/tender-wolves-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
'xstate': minor
---

You can now use the `setup({ ... }).createMachine({ ... })` function to setup implementations for `actors`, `actions`, `guards`, and `delays` that will be used in the created machine:

```ts
import { setup, createMachine } from 'xstate';

const fetchUser = fromPromise(async ({ input }) => {
const response = await fetch(`/user/${input.id}`);
const user = await response.json();
return user;
});

const machine = setup({
actors: {
fetchUser
},
actions: {
clearUser: assign({ user: undefined })
},
guards: {
isUserAdmin: (_, params) => params.user.role === 'admin'
}
}).createMachine({
// ...
invoke: {
// Strongly typed!
src: 'fetchUser',
input: ({ context }) => ({ id: context.userId }),
onDone: {
guard: {
type: 'isUserAdmin',
params: ({ context }) => ({ user: context.user })
},
target: 'success',
actions: assign({ user: ({ event }) => event.output })
},
onError: {
target: 'failure',
actions: 'clearUser'
}
}
});
```
4 changes: 0 additions & 4 deletions packages/core/src/Machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ export function createMachine<
>,
implementations?: InternalMachineImplementations<
TContext,
TEvent,
TActor,
TAction,
TDelay,
ResolveTypegenMeta<
TTypesMeta,
TEvent,
Expand Down
4 changes: 0 additions & 4 deletions packages/core/src/StateMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,6 @@ export class StateMachine<
public provide(
implementations: InternalMachineImplementations<
TContext,
TEvent,
TActor,
TAction,
TDelay,
TResolvedTypesMeta,
true
>
Expand Down
7 changes: 2 additions & 5 deletions packages/core/src/actions/choose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,13 @@ function resolveChoose(
export interface ChooseAction<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TParams extends ParameterizedObject['params'] | undefined,
TEvent extends EventObject,
TActor extends ProvidedActor,
TAction extends ParameterizedObject,
TGuard extends ParameterizedObject,
TDelay extends string
> {
(args: ActionArgs<TContext, TExpressionEvent, TEvent>, params: TParams): void;
(args: ActionArgs<TContext, TExpressionEvent, TEvent>, params: unknown): void;
_out_TActor?: TActor;
_out_TAction?: TAction;
_out_TGuard?: TGuard;
Expand All @@ -65,7 +64,6 @@ export function choose<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TEvent extends EventObject,
TParams extends ParameterizedObject['params'] | undefined,
TActor extends ProvidedActor,
TAction extends ParameterizedObject,
TGuard extends ParameterizedObject,
Expand All @@ -85,7 +83,6 @@ export function choose<
): ChooseAction<
TContext,
TExpressionEvent,
TParams,
TEvent,
TActor,
TAction,
Expand All @@ -94,7 +91,7 @@ export function choose<
> {
function choose(
args: ActionArgs<TContext, TExpressionEvent, TEvent>,
params: TParams
params: unknown
) {
if (isDevelopment) {
throw new Error(`This isn't supposed to be called`);
Expand Down
9 changes: 2 additions & 7 deletions packages/core/src/actions/pure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,13 @@ function resolvePure(
export interface PureAction<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TParams extends ParameterizedObject['params'] | undefined,
TEvent extends EventObject,
TActor extends ProvidedActor,
TAction extends ParameterizedObject,
TGuard extends ParameterizedObject,
TDelay extends string
> {
(args: ActionArgs<TContext, TExpressionEvent, TEvent>, params: TParams): void;
(args: ActionArgs<TContext, TExpressionEvent, TEvent>, params: unknown): void;
_out_TEvent?: TEvent;
_out_TActor?: TActor;
_out_TAction?: TAction;
Expand All @@ -59,9 +58,6 @@ export interface PureAction<
export function pure<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TParams extends ParameterizedObject['params'] | undefined =
| ParameterizedObject['params']
| undefined,
TEvent extends EventObject = TExpressionEvent,
TActor extends ProvidedActor = ProvidedActor,
TAction extends ParameterizedObject = ParameterizedObject,
Expand Down Expand Up @@ -89,7 +85,6 @@ export function pure<
): PureAction<
TContext,
TExpressionEvent,
TParams,
TEvent,
TActor,
TAction,
Expand All @@ -98,7 +93,7 @@ export function pure<
> {
function pure(
args: ActionArgs<TContext, TExpressionEvent, TEvent>,
params: TParams
params: unknown
) {
if (isDevelopment) {
throw new Error(`This isn't supposed to be called`);
Expand Down
91 changes: 71 additions & 20 deletions packages/core/src/guards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,35 @@ import type {
AnyMachineSnapshot,
NoRequiredParams,
NoInfer,
WithDynamicParams
WithDynamicParams,
Identity,
Elements
} from './types.ts';
import { isStateId } from './stateUtils.ts';

type SingleGuardArg<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TParams extends ParameterizedObject['params'] | undefined,
TGuardArg
> = [TGuardArg] extends [{ type: string }]
? Identity<TGuardArg>
: [TGuardArg] extends [string]
? TGuardArg
: GuardPredicate<TContext, TExpressionEvent, TParams, ParameterizedObject>;

type NormalizeGuardArg<TGuardArg> = TGuardArg extends { type: string }
? Identity<TGuardArg> & { params: unknown }
: TGuardArg extends string
? { type: TGuardArg; params: undefined }
: '_out_TGuard' extends keyof TGuardArg
? TGuardArg['_out_TGuard'] & ParameterizedObject
: never;

type NormalizeGuardArgArray<TArg extends unknown[]> = Elements<{
[K in keyof TArg]: NormalizeGuardArg<TArg[K]>;
}>;

export type GuardPredicate<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
Expand Down Expand Up @@ -116,12 +141,16 @@ function checkNot(
export function not<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TParams extends ParameterizedObject['params'] | undefined,
TGuard extends ParameterizedObject
TArg
>(
guard: Guard<TContext, TExpressionEvent, TParams, NoInfer<TGuard>>
): GuardPredicate<TContext, TExpressionEvent, TParams, TGuard> {
function not(args: GuardArgs<TContext, TExpressionEvent>, params: TParams) {
guard: SingleGuardArg<TContext, TExpressionEvent, unknown, TArg>
): GuardPredicate<
TContext,
TExpressionEvent,
unknown,
NormalizeGuardArg<NoInfer<TArg>>
> {
function not(args: GuardArgs<TContext, TExpressionEvent>, params: unknown) {
if (isDevelopment) {
throw new Error(`This isn't supposed to be called`);
}
Expand All @@ -145,14 +174,25 @@ function checkAnd(
export function and<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TParams extends ParameterizedObject['params'] | undefined,
TGuard extends ParameterizedObject
TArg extends unknown[]
>(
guards: ReadonlyArray<
Guard<TContext, TExpressionEvent, TParams, NoInfer<TGuard>>
>
): GuardPredicate<TContext, TExpressionEvent, TParams, TGuard> {
function and(args: GuardArgs<TContext, TExpressionEvent>, params: TParams) {
guards: readonly [
...{
[K in keyof TArg]: SingleGuardArg<
TContext,
TExpressionEvent,
unknown,
TArg[K]
>;
}
]
): GuardPredicate<
TContext,
TExpressionEvent,
unknown,
NormalizeGuardArgArray<NoInfer<TArg>>
> {
function and(args: GuardArgs<TContext, TExpressionEvent>, params: unknown) {
if (isDevelopment) {
throw new Error(`This isn't supposed to be called`);
}
Expand All @@ -176,14 +216,25 @@ function checkOr(
export function or<
TContext extends MachineContext,
TExpressionEvent extends EventObject,
TParams extends ParameterizedObject['params'] | undefined,
TGuard extends ParameterizedObject
TArg extends unknown[]
>(
guards: ReadonlyArray<
Guard<TContext, TExpressionEvent, TParams, NoInfer<TGuard>>
>
): GuardPredicate<TContext, TExpressionEvent, TParams, TGuard> {
function or(args: GuardArgs<TContext, TExpressionEvent>, params: TParams) {
guards: readonly [
...{
[K in keyof TArg]: SingleGuardArg<
TContext,
TExpressionEvent,
unknown,
TArg[K]
>;
}
]
): GuardPredicate<
TContext,
TExpressionEvent,
unknown,
NormalizeGuardArgArray<NoInfer<TArg>>
> {
function or(args: GuardArgs<TContext, TExpressionEvent>, params: unknown) {
if (isDevelopment) {
throw new Error(`This isn't supposed to be called`);
}
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type {
} from './system.ts';

export { and, not, or, stateIn } from './guards.ts';
export { setup } from './setup.ts';

declare global {
interface SymbolConstructor {
Expand Down
Loading

0 comments on commit a3a11c8

Please sign in to comment.