diff --git a/packages/core/src/setup.ts b/packages/core/src/setup.ts index 8d2ce7a6a0..670e7d650d 100644 --- a/packages/core/src/setup.ts +++ b/packages/core/src/setup.ts @@ -359,7 +359,10 @@ export function setup< TInput, TOutput, TEmitted, - TMeta + TMeta, + ToParameterizedObject, + ToParameterizedObject, + TDelay >; actors?: { // union here enforces that all configured children have to be provided in actors diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 7e516e7948..1bff49f439 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1396,7 +1396,10 @@ export interface SetupTypes< TInput, TOutput, TEmitted extends EventObject, - TMeta extends MetaObject + TMeta extends MetaObject, + TAction extends ParameterizedObject = ParameterizedObject, + TGuard extends ParameterizedObject = ParameterizedObject, + TDelay extends string = string > { context?: TContext; events?: TEvent; @@ -1406,6 +1409,9 @@ export interface SetupTypes< output?: TOutput; emitted?: TEmitted; meta?: TMeta; + actions?: TAction; + guards?: TGuard; + delays?: TDelay; } export interface MachineTypes< @@ -1430,13 +1436,12 @@ export interface MachineTypes< TInput, TOutput, TEmitted, - TMeta + TMeta, + TAction, + TGuard, + TDelay > { actors?: TActor; - actions?: TAction; - guards?: TGuard; - delays?: TDelay; - meta?: TMeta; } export interface HistoryStateNode diff --git a/packages/core/test/setup.types.test.ts b/packages/core/test/setup.types.test.ts index 619ad9d166..df4839facb 100644 --- a/packages/core/test/setup.types.test.ts +++ b/packages/core/test/setup.types.test.ts @@ -3159,4 +3159,114 @@ describe('type-bound actions', () => { entry: spawn }); }); + + describe('type declarations in types', () => { + it('should infer action types from implementations', () => { + const { createMachine } = setup({ + types: {} as { + context: { count: number }; + events: { type: 'INC' }; + }, + actions: { + increment: assign(({ context }) => ({ count: context.count + 1 })), + log: (_, params: { message: string }) => console.log(params.message) + } + }); + + createMachine({ + context: { count: 0 }, + initial: 'idle', + states: { + idle: { + on: { + INC: { + actions: 'increment' + } + } + } + } + }); + }); + + it('should infer guard types from implementations', () => { + const { createMachine } = setup({ + types: {} as { + context: { count: number }; + events: { type: 'INC' }; + }, + guards: { + isPositive: ({ context }) => context.count > 0, + isGreaterThan: ({ context }, params: { value: number }) => + context.count > params.value + } + }); + + createMachine({ + context: { count: 0 }, + initial: 'idle', + states: { + idle: { + on: { + INC: { + guard: 'isPositive', + target: 'idle' + } + } + } + } + }); + }); + + it('should infer delay types from implementations', () => { + const { createMachine } = setup({ + types: {} as { + context: { timeout: number }; + events: { type: 'START' }; + }, + delays: { + shortDelay: 100, + longDelay: ({ context }) => context.timeout + } + }); + + createMachine({ + context: { timeout: 1000 }, + initial: 'idle', + states: { + idle: { + after: { + shortDelay: 'active' + } + }, + active: {} + } + }); + }); + + it('SetupTypes should include actions, guards, and delays slots', () => { + // This test verifies that SetupTypes has the action/guard/delay type parameters + // which enables explicit type annotations for breaking inference chains + type MySetupTypes = { + context: { count: number }; + events: { type: 'INC' }; + actions: { type: 'increment'; params: undefined }; + guards: { type: 'isValid'; params: undefined }; + delays: 'timeout'; + }; + + // The types can be used for explicit annotations + const _types: MySetupTypes = { + context: { count: 0 }, + events: { type: 'INC' }, + actions: { type: 'increment', params: undefined }, + guards: { type: 'isValid', params: undefined }, + delays: 'timeout' + }; + + // Verify the shape is correct + expect(_types.actions.type).toBe('increment'); + expect(_types.guards.type).toBe('isValid'); + expect(_types.delays).toBe('timeout'); + }); + }); });