Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion packages/core/src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,10 @@ export function setup<
TInput,
TOutput,
TEmitted,
TMeta
TMeta,
ToParameterizedObject<TActions>,
ToParameterizedObject<TGuards>,
TDelay
>;
actors?: {
// union here enforces that all configured children have to be provided in actors
Expand Down
17 changes: 11 additions & 6 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -1406,6 +1409,9 @@ export interface SetupTypes<
output?: TOutput;
emitted?: TEmitted;
meta?: TMeta;
actions?: TAction;
guards?: TGuard;
delays?: TDelay;
}

export interface MachineTypes<
Expand All @@ -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<TContext extends MachineContext>
Expand Down
110 changes: 110 additions & 0 deletions packages/core/test/setup.types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
});