From 263a3f4cbdaef20a68f252f4ce1cd78e2d9c08c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Thu, 7 Sep 2023 19:33:29 +0200 Subject: [PATCH 01/12] Introduce `TConfig` --- packages/core/src/Machine.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/core/src/Machine.ts b/packages/core/src/Machine.ts index 7a3cf1afbf..65c7532335 100644 --- a/packages/core/src/Machine.ts +++ b/packages/core/src/Machine.ts @@ -11,6 +11,13 @@ import { import { TypegenConstraint, ResolveTypegenMeta } from './typegenTypes.ts'; import { StateMachine } from './StateMachine.ts'; +interface StatesSchema { + states?: { + [key: string]: StatesSchema; + }; + [k: string]: unknown; +} + export function createMachine< TContext extends MachineContext, TEvent extends AnyEventObject, // TODO: consider using a stricter `EventObject` here @@ -21,7 +28,8 @@ export function createMachine< TTag extends string, TInput, TOutput extends NonReducibleUnknown, - TTypesMeta extends TypegenConstraint + TTypesMeta extends TypegenConstraint, + TConfig extends StatesSchema >( config: MachineConfig< TContext, @@ -34,7 +42,8 @@ export function createMachine< TInput, TOutput, TTypesMeta - >, + > & + TConfig, implementations?: InternalMachineImplementations< TContext, TEvent, From b1f54336cfc2aede045be5241072df876b97c980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 8 Sep 2023 12:10:52 +0200 Subject: [PATCH 02/12] add the index signature as it wont work without it --- packages/core/src/Machine.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/Machine.ts b/packages/core/src/Machine.ts index 65c7532335..31d50fcf67 100644 --- a/packages/core/src/Machine.ts +++ b/packages/core/src/Machine.ts @@ -12,9 +12,7 @@ import { TypegenConstraint, ResolveTypegenMeta } from './typegenTypes.ts'; import { StateMachine } from './StateMachine.ts'; interface StatesSchema { - states?: { - [key: string]: StatesSchema; - }; + states?: Record; [k: string]: unknown; } From 20b70de3dfbf5c45c11b51b976a5d85ebc3b8f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 8 Sep 2023 15:16:51 +0200 Subject: [PATCH 03/12] Use a different strategy to infer `TConfig` --- packages/core/src/Machine.ts | 33 +++++++++++-------- packages/core/src/StateMachine.ts | 3 +- packages/core/src/types.ts | 18 +++------- packages/core/test/types.test.ts | 19 ----------- .../test/createActorContext.test.tsx | 4 +-- 5 files changed, 27 insertions(+), 50 deletions(-) diff --git a/packages/core/src/Machine.ts b/packages/core/src/Machine.ts index 31d50fcf67..e7907e4946 100644 --- a/packages/core/src/Machine.ts +++ b/packages/core/src/Machine.ts @@ -6,16 +6,12 @@ import { ProvidedActor, NonReducibleUnknown, Prop, - AnyEventObject + AnyEventObject, + MachineTypes } from './types.ts'; import { TypegenConstraint, ResolveTypegenMeta } from './typegenTypes.ts'; import { StateMachine } from './StateMachine.ts'; -interface StatesSchema { - states?: Record; - [k: string]: unknown; -} - export function createMachine< TContext extends MachineContext, TEvent extends AnyEventObject, // TODO: consider using a stricter `EventObject` here @@ -27,9 +23,7 @@ export function createMachine< TInput, TOutput extends NonReducibleUnknown, TTypesMeta extends TypegenConstraint, - TConfig extends StatesSchema ->( - config: MachineConfig< + TConfig extends MachineConfig< TContext, TEvent, TActor, @@ -38,10 +32,23 @@ export function createMachine< TDelay, TTag, TInput, - TOutput, - TTypesMeta - > & - TConfig, + TOutput + > +>( + config: { + types?: MachineTypes< + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TTag, + TInput, + TOutput, + TTypesMeta + >; + } & TConfig, implementations?: InternalMachineImplementations< TContext, TEvent, diff --git a/packages/core/src/StateMachine.ts b/packages/core/src/StateMachine.ts index 86c2a3bc8c..da52a443ed 100644 --- a/packages/core/src/StateMachine.ts +++ b/packages/core/src/StateMachine.ts @@ -127,8 +127,7 @@ export class StateMachine< any, any, any, - TOutput, - any + TOutput >, implementations?: MachineImplementationsSimplified ) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 63c397796b..2cfe579f8f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1294,8 +1294,7 @@ export type MachineConfig< TDelay extends string = string, TTag extends string = string, TInput = any, - TOutput = unknown, - TTypesMeta = TypegenDisabled + TOutput = unknown > = (RootStateNodeConfig< NoInfer, NoInfer, @@ -1313,18 +1312,9 @@ export type MachineConfig< * The machine's own version. */ version?: string; - types?: MachineTypes< - TContext, - TEvent, - TActor, - TAction, - TGuard, - TDelay, - TTag, - TInput, - TOutput, - TTypesMeta - >; + // we need to avoid failing the common property check for empty machine configs or ones that only contain `types` property + // without it whe inferred config type (like `{}` or `{ types: {} }`) fails the assignability check to its constraint + types?: unknown; }) & (Equals extends true ? { context?: InitialContext, TInput> } diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index 32a57ebd8e..f50b734fea 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -333,25 +333,6 @@ describe('output', () => { }); }); -it('should infer context type from `config.context` when there is no `schema.context`', () => { - createMachine( - { - context: { - foo: 'test' - } - }, - { - actions: { - someAction: ({ context }) => { - ((_accept: string) => {})(context.foo); - // @ts-expect-error - ((_accept: number) => {})(context.foo); - } - } - } - ); -}); - it('should not use actions as possible inference sites', () => { createMachine( { diff --git a/packages/xstate-react/test/createActorContext.test.tsx b/packages/xstate-react/test/createActorContext.test.tsx index 0f3ed03807..d7e28641f9 100644 --- a/packages/xstate-react/test/createActorContext.test.tsx +++ b/packages/xstate-react/test/createActorContext.test.tsx @@ -114,14 +114,14 @@ describe('createActorContext', () => { }, on: { INC: { - actions: assign(({ context }) => ({ + actions: assign(({ context }) => ({ obj: { counter: context.obj.counter + 1 } })) }, PUSH: { - actions: assign(({ context }) => ({ + actions: assign(({ context }) => ({ arr: [...context.arr, Math.random().toString(36).slice(2)] })) } From 6b98e33838463a501e890da874195168311d5bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 8 Sep 2023 15:20:20 +0200 Subject: [PATCH 04/12] fix failures related to top-level context factories --- packages/core/src/types.ts | 4 ++-- packages/core/test/invoke.test.ts | 11 +++++------ packages/core/test/transient.test.ts | 9 +++++---- .../xstate-react/test/createActorContext.test.tsx | 3 ++- packages/xstate-react/test/useActor.test.tsx | 7 ++++--- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2cfe579f8f..11246ff002 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1317,8 +1317,8 @@ export type MachineConfig< types?: unknown; }) & (Equals extends true - ? { context?: InitialContext, TInput> } - : { context: InitialContext, TInput> }); + ? { context?: InitialContext } + : { context: InitialContext }); export interface ProvidedActor { src: string; diff --git a/packages/core/test/invoke.test.ts b/packages/core/test/invoke.test.ts index 28cf57a19b..48897ebcc3 100644 --- a/packages/core/test/invoke.test.ts +++ b/packages/core/test/invoke.test.ts @@ -768,14 +768,13 @@ describe('invoke', () => { promiseTypes.forEach(({ type, createPromise }) => { describe(`with promises (${type})`, () => { const invokePromiseMachine = createMachine({ - types: {} as { context: { id: number; succeed: boolean } }, + types: {} as { + context: { id: number; succeed: boolean }; + input: { id?: number; succeed?: boolean }; + }, id: 'invokePromise', initial: 'pending', - context: ({ - input - }: { - input: { id?: number; succeed?: boolean }; - }) => ({ + context: ({ input }) => ({ id: 42, succeed: true, ...input diff --git a/packages/core/test/transient.test.ts b/packages/core/test/transient.test.ts index dac5b9045f..1ec884ae4f 100644 --- a/packages/core/test/transient.test.ts +++ b/packages/core/test/transient.test.ts @@ -569,13 +569,14 @@ describe('transient states (eventless transitions)', () => { it("shouldn't crash when invoking a machine with initial transient transition depending on custom data", () => { const timerMachine = createMachine({ + types: {} as { + context: { duration: number }; + input: { duration: number }; + }, initial: 'initial', - context: ({ input }: { input: { duration: number } }) => ({ + context: ({ input }) => ({ duration: input.duration }), - types: { - context: {} as { duration: number } - }, states: { initial: { always: [ diff --git a/packages/xstate-react/test/createActorContext.test.tsx b/packages/xstate-react/test/createActorContext.test.tsx index d7e28641f9..5082b9c603 100644 --- a/packages/xstate-react/test/createActorContext.test.tsx +++ b/packages/xstate-react/test/createActorContext.test.tsx @@ -446,8 +446,9 @@ describe('createActorContext', () => { createMachine({ types: {} as { context: { doubled: number }; + input: number; }, - context: ({ input }: { input: number }) => ({ + context: ({ input }) => ({ doubled: input * 2 }) }) diff --git a/packages/xstate-react/test/useActor.test.tsx b/packages/xstate-react/test/useActor.test.tsx index 9ecae6a4a3..e3b0ddcb11 100644 --- a/packages/xstate-react/test/useActor.test.tsx +++ b/packages/xstate-react/test/useActor.test.tsx @@ -836,11 +836,12 @@ describeEachReactMode('useActor (%s)', ({ suiteKey, render }) => { it('custom data should be available right away for the invoked actor', () => { const childMachine = createMachine({ - types: { - context: {} as { value: number } + types: {} as { + context: { value: number }; + input: { value: number }; }, initial: 'intitial', - context: ({ input }: { input: { value: number } }) => { + context: ({ input }) => { return { value: input.value }; From 2a9ef8e75aab8a446ba9617608ca8c4a48e8f681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 8 Sep 2023 15:30:41 +0200 Subject: [PATCH 05/12] fix extra test case --- packages/core/test/input.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/test/input.test.ts b/packages/core/test/input.test.ts index 07d9f2b972..085432dccb 100644 --- a/packages/core/test/input.test.ts +++ b/packages/core/test/input.test.ts @@ -225,7 +225,8 @@ describe('input', () => { const spy = jest.fn(); const child = createMachine({ - context: ({ input }: { input: number }) => { + types: {} as { input: number }, + context: ({ input }) => { spy(input); return {}; } From 2a86964dca9214456c791aeef44c55183b3bb856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 8 Sep 2023 15:39:00 +0200 Subject: [PATCH 06/12] Add explicit `types` in one of the tests --- packages/core/test/types.test.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index f50b734fea..97b56d55bb 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -1646,12 +1646,14 @@ describe('actor types', () => { it('when some provided actors have specified ids index signature should be allowed', () => { const child1 = createMachine({ + types: {} as { context: { counter: number } }, context: { counter: 0 } }); const child2 = createMachine({ + types: {} as { context: { answer: string } }, context: { answer: '' } From c0ef0cb97911f5247ce34e5af14d687e33801ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 8 Sep 2023 20:39:49 +0200 Subject: [PATCH 07/12] skip one error --- packages/core/test/types.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index 97b56d55bb..d4abbd3f06 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -3301,7 +3301,7 @@ describe('delays', () => { delays: 'one second' | 'one minute'; }, after: { - // @ts-expect-error + // @x-ts-expect-error it would be cool to error here but the added number index signature makes this non-weak type and the common property check doesn't kick in 'unknown delay': {} } }); From 0739a1f517aadb1fa8509e5805f267b7936ed3c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 8 Sep 2023 21:29:41 +0200 Subject: [PATCH 08/12] drop redundant `NoInfer`s --- packages/core/src/types.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a5447b822b..a5a9a65f18 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1296,14 +1296,14 @@ export type MachineConfig< TInput = any, TOutput = unknown > = (RootStateNodeConfig< - NoInfer, - NoInfer, - NoInfer, - NoInfer, - NoInfer, - NoInfer, - NoInfer, - NoInfer + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TTag, + TOutput > & { /** * The initial context (extended state) From d93e70f1ad00e33006ccda8cd1b49ebe5a6ca20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 8 Sep 2023 21:37:24 +0200 Subject: [PATCH 09/12] Move inline bit to `InferenceSources` helper --- packages/core/src/Machine.ts | 56 ++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/packages/core/src/Machine.ts b/packages/core/src/Machine.ts index e7907e4946..96783513bd 100644 --- a/packages/core/src/Machine.ts +++ b/packages/core/src/Machine.ts @@ -7,11 +7,38 @@ import { NonReducibleUnknown, Prop, AnyEventObject, - MachineTypes + MachineTypes, + EventObject } from './types.ts'; import { TypegenConstraint, ResolveTypegenMeta } from './typegenTypes.ts'; import { StateMachine } from './StateMachine.ts'; +interface InferenceSources< + TContext extends MachineContext, + TEvent extends EventObject, + TActor extends ProvidedActor, + TAction extends ParameterizedObject, + TGuard extends ParameterizedObject, + TDelay extends string, + TTag extends string, + TInput, + TOutput extends NonReducibleUnknown, + TTypesMeta extends TypegenConstraint +> { + types?: MachineTypes< + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TTag, + TInput, + TOutput, + TTypesMeta + >; +} + export function createMachine< TContext extends MachineContext, TEvent extends AnyEventObject, // TODO: consider using a stricter `EventObject` here @@ -35,20 +62,19 @@ export function createMachine< TOutput > >( - config: { - types?: MachineTypes< - TContext, - TEvent, - TActor, - TAction, - TGuard, - TDelay, - TTag, - TInput, - TOutput, - TTypesMeta - >; - } & TConfig, + config: InferenceSources< + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TTag, + TInput, + TOutput, + TTypesMeta + > & + TConfig, implementations?: InternalMachineImplementations< TContext, TEvent, From ddb5388dbfded0ff1d04763c4d53c826aa903041 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 9 Sep 2023 00:38:04 +0200 Subject: [PATCH 10/12] tweak things slightly --- packages/core/src/Machine.ts | 20 ++++++++++---------- packages/core/src/types.ts | 11 ++++++----- packages/core/test/types.test.ts | 2 +- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/core/src/Machine.ts b/packages/core/src/Machine.ts index 96783513bd..48f4374f63 100644 --- a/packages/core/src/Machine.ts +++ b/packages/core/src/Machine.ts @@ -1,19 +1,19 @@ +import { StateMachine } from './StateMachine.ts'; +import { ResolveTypegenMeta, TypegenConstraint } from './typegenTypes.ts'; import { + AnyEventObject, + EventObject, + InternalMachineImplementations, MachineConfig, MachineContext, - InternalMachineImplementations, - ParameterizedObject, - ProvidedActor, + MachineTypes, NonReducibleUnknown, + ParameterizedObject, Prop, - AnyEventObject, - MachineTypes, - EventObject + ProvidedActor } from './types.ts'; -import { TypegenConstraint, ResolveTypegenMeta } from './typegenTypes.ts'; -import { StateMachine } from './StateMachine.ts'; -interface InferenceSources< +interface InferenceSource< TContext extends MachineContext, TEvent extends EventObject, TActor extends ProvidedActor, @@ -62,7 +62,7 @@ export function createMachine< TOutput > >( - config: InferenceSources< + config: InferenceSource< TContext, TEvent, TActor, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index a5a9a65f18..3cb0c6909c 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1295,7 +1295,7 @@ export type MachineConfig< TTag extends string = string, TInput = any, TOutput = unknown -> = (RootStateNodeConfig< +> = RootStateNodeConfig< TContext, TEvent, TActor, @@ -1312,13 +1312,14 @@ export type MachineConfig< * The machine's own version. */ version?: string; + context?: InitialContext; // we need to avoid failing the common property check for empty machine configs or ones that only contain `types` property // without it whe inferred config type (like `{}` or `{ types: {} }`) fails the assignability check to its constraint types?: unknown; -}) & - (Equals extends true - ? { context?: InitialContext } - : { context: InitialContext }); +} & ConditionalRequired< + { context?: unknown }, + MachineContext extends TContext ? false : true + >; export interface ProvidedActor { src: string; diff --git a/packages/core/test/types.test.ts b/packages/core/test/types.test.ts index d4abbd3f06..31d27bc05b 100644 --- a/packages/core/test/types.test.ts +++ b/packages/core/test/types.test.ts @@ -368,7 +368,7 @@ it('should work with generic context', () => { createMachineWithExtras({ counter: 42 }); }); -it('should not widen literal types defined in `schema.context` based on `config.context`', () => { +it('should not widen literal types defined in `types.context` based on `config.context`', () => { createMachine({ types: { context: {} as { From 808f668a508ca64d832dec24ffbc696bbf0fc35c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 16 Sep 2023 09:16:02 +0200 Subject: [PATCH 11/12] Add `TStringLiteral` to fix widening issues --- packages/core/src/Machine.ts | 6 +- packages/core/src/StateMachine.ts | 3 +- packages/core/src/StateNode.ts | 5 +- packages/core/src/actions/pure.ts | 6 +- packages/core/src/stateUtils.ts | 2 +- packages/core/src/types.ts | 218 ++++++++++++++++++++++-------- packages/xstate-test/src/types.ts | 14 +- 7 files changed, 189 insertions(+), 65 deletions(-) diff --git a/packages/core/src/Machine.ts b/packages/core/src/Machine.ts index 48f4374f63..a22b8b7c6c 100644 --- a/packages/core/src/Machine.ts +++ b/packages/core/src/Machine.ts @@ -59,8 +59,10 @@ export function createMachine< TDelay, TTag, TInput, - TOutput - > + TOutput, + TStringLiteral + >, + TStringLiteral extends string >( config: InferenceSource< TContext, diff --git a/packages/core/src/StateMachine.ts b/packages/core/src/StateMachine.ts index 21a2dfd668..594f424f42 100644 --- a/packages/core/src/StateMachine.ts +++ b/packages/core/src/StateMachine.ts @@ -128,7 +128,8 @@ export class StateMachine< any, any, any, - TOutput + TOutput, + string >, implementations?: MachineImplementationsSimplified ) { diff --git a/packages/core/src/StateNode.ts b/packages/core/src/StateNode.ts index 686ea87a8f..6592c0da42 100644 --- a/packages/core/src/StateNode.ts +++ b/packages/core/src/StateNode.ts @@ -159,7 +159,8 @@ export class StateNode< TODO, // output TODO, // guards TODO, // delays - TODO // tags + TODO, // tags + string // TStringLiteral >, options: StateNodeOptions ) { @@ -293,6 +294,7 @@ export class StateNode< ProvidedActor, ParameterizedObject, ParameterizedObject, + string, string > > { @@ -337,6 +339,7 @@ export class StateNode< ProvidedActor, ParameterizedObject, ParameterizedObject, + string, string >; }) diff --git a/packages/core/src/actions/pure.ts b/packages/core/src/actions/pure.ts index e5f39ce943..f21c77bd7d 100644 --- a/packages/core/src/actions/pure.ts +++ b/packages/core/src/actions/pure.ts @@ -65,7 +65,8 @@ export function pure< TActor extends ProvidedActor = ProvidedActor, TAction extends ParameterizedObject = ParameterizedObject, TGuard extends ParameterizedObject = ParameterizedObject, - TDelay extends string = string + TDelay extends string = string, + TStringLiteral extends string = string >( getActions: ({ context, @@ -82,7 +83,8 @@ export function pure< TActor, NoInfer, NoInfer, - TDelay + TDelay, + TStringLiteral > | undefined ): PureAction< diff --git a/packages/core/src/stateUtils.ts b/packages/core/src/stateUtils.ts index 0d25737c22..febbf6f194 100644 --- a/packages/core/src/stateUtils.ts +++ b/packages/core/src/stateUtils.ts @@ -460,7 +460,7 @@ export function formatInitialTransition< stateNode: AnyStateNode, _target: | SingleOrArray - | InitialTransitionConfig + | InitialTransitionConfig ): InitialTransitionDefinition { if (typeof _target === 'string' || isArray(_target)) { const targets = toArray(_target).map((t) => { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d514b878f4..8855bd1d7e 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -158,7 +158,8 @@ export interface ChooseBranch< TActor extends ProvidedActor = ProvidedActor, TAction extends ParameterizedObject = ParameterizedObject, TGuard extends ParameterizedObject = ParameterizedObject, - TDelay extends string = string + TDelay extends string = string, + TStringLiteral extends string = string > { guard?: Guard; actions: Actions< @@ -169,7 +170,8 @@ export interface ChooseBranch< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral >; } @@ -213,7 +215,8 @@ export type Action< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > = // TODO: consider merging `NoRequiredParams` and `WithDynamicParams` into one // this way we could iterate over `TAction` (and `TGuard` in the `Guard` type) once and not twice @@ -238,6 +241,7 @@ export type UnknownAction = Action< ProvidedActor, ParameterizedObject, ParameterizedObject, + string, string >; @@ -249,7 +253,8 @@ export type Actions< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > = SingleOrArray< Action< TContext, @@ -259,7 +264,8 @@ export type Actions< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; @@ -286,7 +292,8 @@ export interface TransitionConfig< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > { guard?: Guard; actions?: Actions< @@ -297,7 +304,8 @@ export interface TransitionConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral >; reenter?: boolean; target?: TransitionTarget | undefined; @@ -311,7 +319,8 @@ export interface InitialTransitionConfig< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > extends TransitionConfig< TContext, TEvent, @@ -319,7 +328,8 @@ export interface InitialTransitionConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > { target: TransitionTarget; } @@ -331,7 +341,8 @@ export type AnyTransitionConfig = TransitionConfig< any, any, any, - any + any, + string >; export interface InvokeDefinition< @@ -340,7 +351,8 @@ export interface InvokeDefinition< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > { id: string; @@ -364,7 +376,8 @@ export interface InvokeDefinition< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; /** @@ -380,7 +393,8 @@ export interface InvokeDefinition< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; @@ -394,12 +408,13 @@ export interface InvokeDefinition< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; toJSON: () => Omit< - InvokeDefinition, + InvokeDefinition, 'onDone' | 'onError' | 'toJSON' >; } @@ -412,7 +427,8 @@ export type DelayedTransitions< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > = | { [K in Delay]?: @@ -425,7 +441,8 @@ export type DelayedTransitions< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; } @@ -437,7 +454,8 @@ export type DelayedTransitions< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > & { delay: | Delay @@ -470,7 +488,8 @@ export type StatesConfig< TGuard extends ParameterizedObject, TDelay extends string, TTag extends string, - TOutput + TOutput, + TStringLiteral extends string > = { [K in string]: StateNodeConfig< TContext, @@ -480,7 +499,8 @@ export type StatesConfig< TGuard, TDelay, TTag, - TOutput + TOutput, + TStringLiteral >; }; @@ -500,7 +520,8 @@ export type TransitionConfigOrTarget< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > = SingleOrArray< | TransitionConfigTarget | TransitionConfig< @@ -510,7 +531,8 @@ export type TransitionConfigOrTarget< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; @@ -520,7 +542,8 @@ export type TransitionsConfig< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > = | { [K in EventDescriptor]?: TransitionConfigOrTarget< @@ -530,7 +553,8 @@ export type TransitionsConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral >; }; @@ -559,7 +583,8 @@ type DistributeActors< TAction extends ParameterizedObject, TGuard extends ParameterizedObject, TDelay extends string, - TSpecificActor extends ProvidedActor + TSpecificActor extends ProvidedActor, + TStringLiteral extends string > = TSpecificActor extends { src: infer TSrc } ? Compute< { @@ -588,7 +613,8 @@ type DistributeActors< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; /** @@ -604,7 +630,8 @@ type DistributeActors< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; @@ -618,7 +645,8 @@ type DistributeActors< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; } & (TSpecificActor['id'] extends string @@ -645,21 +673,31 @@ export type InvokeConfig< TActor extends ProvidedActor, TAction extends ParameterizedObject, TGuard extends ParameterizedObject, - TDelay extends string + TDelay extends string, + TStringLiteral extends string > = IsLiteralString extends true - ? DistributeActors + ? DistributeActors< + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TActor, + TStringLiteral + > : { /** * The unique identifier for the invoked machine. If not specified, this * will be the machine's own `id`, or the URL (from `src`). */ - id?: string; + id?: TStringLiteral; systemId?: string; /** * The source of the machine to be invoked, or the machine itself. */ - src: AnyActorLogic | string; // TODO: fix types + src: AnyActorLogic | TStringLiteral; // TODO: fix types input?: | Mapper @@ -677,7 +715,8 @@ export type InvokeConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; /** @@ -693,7 +732,8 @@ export type InvokeConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; @@ -707,12 +747,21 @@ export type InvokeConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > >; }; -export type AnyInvokeConfig = InvokeConfig; +export type AnyInvokeConfig = InvokeConfig< + any, + any, + any, + any, + any, + any, + string +>; export interface StateNodeConfig< TContext extends MachineContext, @@ -722,13 +771,22 @@ export interface StateNodeConfig< TGuard extends ParameterizedObject, TDelay extends string, TTag extends string, - TOutput + TOutput, + TStringLiteral extends string > { /** * The initial state transition. */ initial?: - | InitialTransitionConfig + | InitialTransitionConfig< + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TStringLiteral + > | SingleOrArray | undefined; /** @@ -759,19 +817,36 @@ export interface StateNodeConfig< TGuard, TDelay, TTag, - NonReducibleUnknown + NonReducibleUnknown, + TStringLiteral > | undefined; /** * The services to invoke upon entering this state node. These services will be stopped upon exiting this state node. */ invoke?: SingleOrArray< - InvokeConfig + InvokeConfig< + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TStringLiteral + > >; /** * The mapping of event types to their potential transition(s). */ - on?: TransitionsConfig; + on?: TransitionsConfig< + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TStringLiteral + >; /** * The action(s) to be executed upon entering the state node. */ @@ -783,7 +858,8 @@ export interface StateNodeConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral >; /** * The action(s) to be executed upon exiting the state node. @@ -796,7 +872,8 @@ export interface StateNodeConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral >; /** * The potential transition(s) to be taken upon reaching a final child state node. @@ -813,7 +890,8 @@ export interface StateNodeConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral > > | undefined; @@ -821,7 +899,15 @@ export interface StateNodeConfig< * The mapping (or array) of delays (in milliseconds) to their potential transition(s). * The delayed transitions are taken after the specified delay in an interpreter. */ - after?: DelayedTransitions; + after?: DelayedTransitions< + TContext, + TEvent, + TActor, + TAction, + TGuard, + TDelay, + TStringLiteral + >; /** * An eventless transition that is always taken when this state node is active. @@ -833,7 +919,8 @@ export interface StateNodeConfig< TActor, TAction, TGuard, - TDelay + TDelay, + TStringLiteral >; /** * @private @@ -883,7 +970,8 @@ export type AnyStateNodeConfig = StateNodeConfig< any, any, any, - any + any, + string >; export interface StateNodeDefinition< @@ -905,7 +993,9 @@ export interface StateNodeDefinition< meta: any; order: number; output?: FinalStateNodeConfig['output']; - invoke: Array>; + invoke: Array< + InvokeDefinition + >; description?: string; tags: string[]; } @@ -954,7 +1044,8 @@ export interface AtomicStateNodeConfig< TODO, TODO, TODO, - TODO + TODO, + string > { initial?: undefined; parallel?: false | undefined; @@ -987,7 +1078,17 @@ export type SimpleOrStateNodeConfig< TEvent extends EventObject > = | AtomicStateNodeConfig - | StateNodeConfig; + | StateNodeConfig< + TContext, + TEvent, + TODO, + TODO, + TODO, + TODO, + TODO, + TODO, + string + >; export type ActionFunctionMap< TContext extends MachineContext, @@ -1320,7 +1421,8 @@ type RootStateNodeConfig< TGuard extends ParameterizedObject, TDelay extends string, TTag extends string, - TOutput + TOutput, + TStringLiteral extends string > = Omit< StateNodeConfig< TContext, @@ -1330,7 +1432,8 @@ type RootStateNodeConfig< TGuard, TDelay, TTag, - TOutput + TOutput, + TStringLiteral >, 'states' > & { @@ -1343,7 +1446,8 @@ type RootStateNodeConfig< TGuard, TDelay, TTag, - TOutput + TOutput, + TStringLiteral > | undefined; }; @@ -1357,7 +1461,8 @@ export type MachineConfig< TDelay extends string = string, TTag extends string = string, TInput = any, - TOutput = unknown + TOutput = unknown, + TStringLiteral extends string = string > = RootStateNodeConfig< TContext, TEvent, @@ -1366,7 +1471,8 @@ export type MachineConfig< TGuard, TDelay, TTag, - TOutput + TOutput, + TStringLiteral > & { /** * The initial context (extended state) @@ -1598,7 +1704,7 @@ export interface TransitionDefinition< TContext extends MachineContext, TEvent extends EventObject > extends Omit< - TransitionConfig, + TransitionConfig, | 'target' // `guard` is correctly rejected by `extends` here and `actions` should be too // however, `any` passed to `TransitionConfig` as `TAction` collapses its `.actions` to `any` and it's accidentally allowed here diff --git a/packages/xstate-test/src/types.ts b/packages/xstate-test/src/types.ts index 7b23c0b001..45944e947d 100644 --- a/packages/xstate-test/src/types.ts +++ b/packages/xstate-test/src/types.ts @@ -55,7 +55,8 @@ export interface TestStateNodeConfig< ParameterizedObject, TODO, TODO, - TODO + TODO, + string >, | 'type' | 'history' @@ -169,7 +170,16 @@ export interface TestTransitionConfig< TContext extends MachineContext, TEvent extends EventObject, TTestContext -> extends TransitionConfig { +> extends TransitionConfig< + TContext, + TEvent, + TEvent, + TODO, + TODO, + TODO, + string, + string + > { test?: ( state: State, testContext: TTestContext From c4cad266c25d8c31eb57fd0929cc4395f1231cca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 16 Sep 2023 09:28:35 +0200 Subject: [PATCH 12/12] Actually utilize `TStringLiteral` for typing actions --- packages/core/src/guards.ts | 25 +++++++++++++------ packages/core/src/types.ts | 50 +++++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/packages/core/src/guards.ts b/packages/core/src/guards.ts index 466129a048..d05c651141 100644 --- a/packages/core/src/guards.ts +++ b/packages/core/src/guards.ts @@ -35,10 +35,11 @@ export type Guard< TContext extends MachineContext, TExpressionEvent extends EventObject, TExpressionGuard extends ParameterizedObject | undefined, - TGuard extends ParameterizedObject + TGuard extends ParameterizedObject, + TStringLiteral extends string > = | NoRequiredParams - | WithDynamicParams + | WithDynamicParams | GuardPredicate; export type UnknownGuard = UnknownReferencedGuard | UnknownInlineGuard; @@ -47,14 +48,16 @@ type UnknownReferencedGuard = Guard< MachineContext, EventObject, ParameterizedObject, - ParameterizedObject + ParameterizedObject, + string >; type UnknownInlineGuard = Guard< MachineContext, EventObject, undefined, - ParameterizedObject + ParameterizedObject, + string >; interface BuiltinGuard { @@ -114,7 +117,15 @@ export function not< TExpressionEvent extends EventObject, TExpressionGuard extends ParameterizedObject | undefined, TGuard extends ParameterizedObject ->(guard: Guard>) { +>( + guard: Guard< + TContext, + TExpressionEvent, + TExpressionGuard, + NoInfer, + string + > +) { function not(_: GuardArgs) { if (isDevelopment) { throw new Error(`This isn't supposed to be called`); @@ -148,7 +159,7 @@ export function and< TGuard extends ParameterizedObject >( guards: ReadonlyArray< - Guard> + Guard, string> > ) { function and(_: GuardArgs) { @@ -184,7 +195,7 @@ export function or< TGuard extends ParameterizedObject >( guards: ReadonlyArray< - Guard> + Guard, string> > ) { function or(_: GuardArgs) { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 8855bd1d7e..dbf1ca6ca6 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -161,7 +161,7 @@ export interface ChooseBranch< TDelay extends string = string, TStringLiteral extends string = string > { - guard?: Guard; + guard?: Guard; actions: Actions< TContext, TExpressionEvent, @@ -188,24 +188,29 @@ type ConditionalRequired = Condition extends true export type WithDynamicParams< TContext extends MachineContext, TExpressionEvent extends EventObject, - T extends ParameterizedObject -> = T extends any - ? ConditionalRequired< - { - type: T['type']; - params?: - | T['params'] - | (({ - context, - event - }: { - context: TContext; - event: TExpressionEvent; - }) => T['params']); - }, - undefined extends T['params'] ? false : true - > - : never; + T extends ParameterizedObject, + TStringLiteral extends string +> = T['type'] extends any + ? T extends any + ? ConditionalRequired< + { + type: T['type']; + params?: + | T['params'] + | (({ + context, + event + }: { + context: TContext; + event: TExpressionEvent; + }) => T['params']); + }, + undefined extends T['params'] ? false : true + > + : never + : { + type: TStringLiteral; + }; export type Action< TContext extends MachineContext, @@ -221,7 +226,7 @@ export type Action< // TODO: consider merging `NoRequiredParams` and `WithDynamicParams` into one // this way we could iterate over `TAction` (and `TGuard` in the `Guard` type) once and not twice | NoRequiredParams - | WithDynamicParams + | WithDynamicParams | ActionFunction< TContext, TExpressionEvent, @@ -295,7 +300,7 @@ export interface TransitionConfig< TDelay extends string, TStringLiteral extends string > { - guard?: Guard; + guard?: Guard; actions?: Actions< TContext, TExpressionEvent, @@ -1262,7 +1267,8 @@ type MachineImplementationsGuards< TContext, MaybeNarrowedEvent, Cast, - Cast, ParameterizedObject> + Cast, ParameterizedObject>, + string >; };