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] Strongly-typed state value #5006

Merged
merged 11 commits into from
Aug 23, 2024
63 changes: 63 additions & 0 deletions .changeset/two-hounds-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
'xstate': minor
---

The state value typings for setup state machine actors (`setup({}).createMachine({ ... })`) have been improved to represent the actual expected state values.

```ts
const machine = setup({}).createMachine({
initial: 'green',
states: {
green: {},
yellow: {},
red: {
initial: 'walk',
states: {
walk: {},
wait: {},
stop: {}
}
},
emergency: {
type: 'parallel',
states: {
main: {
initial: 'blinking',
states: {
blinking: {}
}
},
cross: {
initial: 'blinking',
states: {
blinking: {}
}
}
}
}
}
});

const actor = createActor(machine).start();

const stateValue = actor.getSnapshot().value;

if (stateValue === 'green') {
// ...
} else if (stateValue === 'yellow') {
// ...
} else if ('red' in stateValue) {
stateValue;
// {
// red: "walk" | "wait" | "stop";
// }
} else {
stateValue;
// {
// emergency: {
// main: "blinking";
// cross: "blinking";
// };
// }
}
```
8 changes: 4 additions & 4 deletions packages/core/src/State.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import type {
} from './types.ts';
import { matchesState } from './utils.ts';

type ToTestStateValue<TStateValue extends StateValue> =
export type ToTestStateValue<TStateValue extends StateValue> =
TStateValue extends string
? TStateValue
: IsNever<keyof TStateValue> extends true
Expand Down Expand Up @@ -57,7 +57,7 @@ interface MachineSnapshotBase<
TTag extends string,
TOutput,
TMeta,
TConfig extends StateSchema
TStateSchema extends StateSchema = StateSchema
> {
/** The state machine that produced this state snapshot. */
machine: StateMachine<
Expand All @@ -74,7 +74,7 @@ interface MachineSnapshotBase<
TOutput,
EventObject, // TEmitted
any, // TMeta
TConfig
TStateSchema
>;
/** The tags of the active state nodes that represent the current state value. */
tags: Set<string>;
Expand Down Expand Up @@ -137,7 +137,7 @@ interface MachineSnapshotBase<
can: (event: TEvent) => boolean;

getMeta: () => Record<
StateId<TConfig> & string,
StateId<TStateSchema> & string,
TMeta | undefined // States might not have meta defined
>;

Expand Down
40 changes: 1 addition & 39 deletions packages/core/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
AnyActorRef,
AnyEventObject,
Cast,
ConditionalRequired,
DelayConfig,
EventObject,
Invert,
Expand All @@ -18,8 +17,8 @@ import {
NonReducibleUnknown,
ParameterizedObject,
SetupTypes,
StateSchema,
ToChildren,
ToStateValue,
UnknownActorLogic,
Values
} from './types';
Expand Down Expand Up @@ -67,43 +66,6 @@ type ToProvidedActor<
};
}>;

type _GroupStateKeys<
T extends StateSchema,
S extends keyof T['states']
> = S extends any
? T['states'][S] extends { type: 'history' }
? [never, never]
: T extends { type: 'parallel' }
? [S, never]
: 'states' extends keyof T['states'][S]
? [S, never]
: [never, S]
: never;

type GroupStateKeys<T extends StateSchema, S extends keyof T['states']> = {
nonLeaf: _GroupStateKeys<T, S & string>[0];
leaf: _GroupStateKeys<T, S & string>[1];
};

type ToStateValue<T extends StateSchema> = T extends {
states: Record<infer S, any>;
}
? IsNever<S> extends true
? {}
:
| GroupStateKeys<T, S>['leaf']
| (IsNever<GroupStateKeys<T, S>['nonLeaf']> extends false
? ConditionalRequired<
{
[K in GroupStateKeys<T, S>['nonLeaf']]?: ToStateValue<
T['states'][K]
>;
},
T extends { type: 'parallel' } ? true : false
>
: never)
: {};

type RequiredSetupKeys<TChildrenMap> = IsNever<keyof TChildrenMap> extends true
? never
: 'actors';
Expand Down
42 changes: 42 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2551,3 +2551,45 @@ export type GetConcreteByKey<
TKey extends keyof T,
TValue extends T[TKey]
> = T & Record<TKey, TValue>;

type _GroupStateKeys<
T extends StateSchema,
S extends keyof T['states']
> = S extends any
? T['states'][S] extends { type: 'history' }
? [never, never]
: T extends { type: 'parallel' }
? [S, never]
: 'states' extends keyof T['states'][S]
? [S, never]
: [never, S]
: never;

type GroupStateKeys<T extends StateSchema, S extends keyof T['states']> = {
nonLeaf: _GroupStateKeys<T, S & string>[0];
leaf: _GroupStateKeys<T, S & string>[1];
};

export type ToStateValue<T extends StateSchema> = T extends {
states: Record<infer S, any>;
}
? IsNever<S> extends true
? {}
:
| GroupStateKeys<T, S>['leaf']
| (IsNever<GroupStateKeys<T, S>['nonLeaf']> extends false
? T extends { type: 'parallel' }
? {
[K in GroupStateKeys<T, S>['nonLeaf']]: ToStateValue<
T['states'][K]
>;
}
: Compute<
Values<{
[K in GroupStateKeys<T, S>['nonLeaf']]: {
[StateKey in K]: ToStateValue<T['states'][K]>;
};
}>
>
: never)
: {};
Loading