Skip to content

Commit

Permalink
Fixed event type narrowing in some of the builtin actions (#3855)
Browse files Browse the repository at this point in the history
* Fixed event type narrowing in some of the builtin actions

* mute irrelevant type errors
  • Loading branch information
Andarist committed Feb 24, 2023
1 parent 4314720 commit 02012c2
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 35 deletions.
5 changes: 5 additions & 0 deletions .changeset/stale-plants-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'xstate': patch
---

Fixed event type narrowing in some of the builtin actions.
50 changes: 35 additions & 15 deletions packages/core/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,10 +398,14 @@ const defaultLogExpr = <TContext, TEvent extends EventObject>(
* - `event` - the event that caused this action to be executed.
* @param label The label to give to the logged expression.
*/
export function log<TContext, TEvent extends EventObject>(
expr: string | LogExpr<TContext, TEvent> = defaultLogExpr,
export function log<
TContext,
TExpressionEvent extends EventObject,
TEvent extends EventObject = TExpressionEvent
>(
expr: string | LogExpr<TContext, TExpressionEvent> = defaultLogExpr,
label?: string
): LogAction<TContext, TEvent> {
): LogAction<TContext, TExpressionEvent, TEvent> {
return {
type: actionTypes.log,
label,
Expand Down Expand Up @@ -431,9 +435,13 @@ export const resolveLog = <TContext, TEvent extends EventObject>(
*
* @param sendId The `id` of the `send(...)` action to cancel.
*/
export const cancel = <TContext, TEvent extends EventObject>(
export const cancel = <
TContext,
TExpressionEvent extends EventObject,
TEvent extends EventObject
>(
sendId: string | number
): CancelAction<TContext, TEvent> => {
): CancelAction<TContext, TExpressionEvent, TEvent> => {
return {
type: actionTypes.cancel,
sendId
Expand Down Expand Up @@ -462,9 +470,13 @@ export function start<TContext, TEvent extends EventObject>(
*
* @param actorRef The activity to stop.
*/
export function stop<TContext, TEvent extends EventObject>(
actorRef: string | Expr<TContext, TEvent, string | { id: string }>
): StopAction<TContext, TEvent> {
export function stop<
TContext,
TExpressionEvent extends EventObject,
TEvent extends EventObject = TExpressionEvent
>(
actorRef: string | Expr<TContext, TExpressionEvent, string | { id: string }>
): StopAction<TContext, TExpressionEvent, TEvent> {
const activity = isFunction(actorRef)
? actorRef
: toActivityDefinition(actorRef);
Expand Down Expand Up @@ -584,18 +596,22 @@ export function error(id: string, data?: any): ErrorPlatformEvent & string {
return eventObject as ErrorPlatformEvent & string;
}

export function pure<TContext, TEvent extends EventObject>(
export function pure<
TContext,
TExpressionEvent extends EventObject,
TEvent extends EventObject = TExpressionEvent
>(
getActions: (
context: TContext,
event: TEvent
event: TExpressionEvent
) =>
| SingleOrArray<
| BaseActionObject
| BaseActionObject['type']
| ActionObject<TContext, TEvent>
| ActionObject<TContext, TExpressionEvent, TEvent>
>
| undefined
): PureAction<TContext, TEvent> {
): PureAction<TContext, TExpressionEvent, TEvent> {
return {
type: ActionTypes.Pure,
get: getActions
Expand Down Expand Up @@ -664,9 +680,13 @@ export function escalate<
);
}

export function choose<TContext, TEvent extends EventObject>(
conds: Array<ChooseCondition<TContext, TEvent>>
): ChooseAction<TContext, TEvent> {
export function choose<
TContext,
TExpressionEvent extends EventObject,
TEvent extends EventObject = TExpressionEvent
>(
conds: Array<ChooseCondition<TContext, TExpressionEvent, TEvent>>
): ChooseAction<TContext, TExpressionEvent, TEvent> {
return {
type: ActionTypes.Choose,
conds
Expand Down
34 changes: 18 additions & 16 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,13 @@ export type ActionFunction<
): void;
}['bivarianceHack'];

export interface ChooseCondition<TContext, TEvent extends EventObject> {
cond?: Condition<TContext, TEvent>;
actions: Actions<TContext, TEvent>;
export interface ChooseCondition<
TContext,
TExpressionEvent extends EventObject,
TEvent extends EventObject = TExpressionEvent
> {
cond?: Condition<TContext, TExpressionEvent>;
actions: Actions<TContext, TExpressionEvent, TEvent>;
}

export type Action<
Expand All @@ -155,9 +159,8 @@ type SimpleActionsOf<T extends BaseActionObject> = ActionObject<
/**
* Events that do not require payload
*/
export type SimpleEventsOf<
TEvent extends EventObject
> = ExtractWithSimpleSupport<TEvent>;
export type SimpleEventsOf<TEvent extends EventObject> =
ExtractWithSimpleSupport<TEvent>;

export type BaseAction<
TContext,
Expand Down Expand Up @@ -1985,16 +1988,15 @@ type Matches<TypegenEnabledArg, TypegenDisabledArg> = {
(stateValue: TypegenDisabledArg): any;
};

export type StateValueFrom<
TMachine extends AnyStateMachine
> = StateFrom<TMachine>['matches'] extends Matches<
infer TypegenEnabledArg,
infer TypegenDisabledArg
>
? TMachine['__TResolvedTypesMeta'] extends TypegenEnabled
? TypegenEnabledArg
: TypegenDisabledArg
: never;
export type StateValueFrom<TMachine extends AnyStateMachine> =
StateFrom<TMachine>['matches'] extends Matches<
infer TypegenEnabledArg,
infer TypegenDisabledArg
>
? TMachine['__TResolvedTypesMeta'] extends TypegenEnabled
? TypegenEnabledArg
: TypegenDisabledArg
: never;

export type PredictableActionArgumentsExec = (
action: ActionObject<unknown, EventObject>,
Expand Down
9 changes: 6 additions & 3 deletions packages/core/test/actionCreators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('action creators', () => {
(['start', 'stop'] as const).forEach((actionKey) => {
describe(`${actionKey}()`, () => {
it('should accept a string action', () => {
const action = actions[actionKey]('test');
const action = (actions[actionKey] as any)('test');
expect(action.type).toEqual(actionTypes[actionKey]);
expect(action).toEqual({
type: actionTypes[actionKey],
Expand All @@ -21,7 +21,10 @@ describe('action creators', () => {
});

it('should accept an action object', () => {
const action = actions[actionKey]({ type: 'test', foo: 'bar' } as any);
const action = (actions[actionKey] as any)({
type: 'test',
foo: 'bar'
} as any);
expect(action.type).toEqual(actionTypes[actionKey]);
expect(action).toEqual({
type: actionTypes[actionKey],
Expand All @@ -35,7 +38,7 @@ describe('action creators', () => {
});

it('should accept an activity definition', () => {
const action = actions[actionKey]({
const action = (actions[actionKey] as any)({
type: 'test',
foo: 'bar',
src: 'someSrc'
Expand Down
42 changes: 41 additions & 1 deletion packages/core/test/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
spawn,
ActorRefFrom
} from '../src/index';
import { raise, stop } from '../src/actions';
import { raise, stop, log } from '../src/actions';
import { createModel } from '../src/model';

function noop(_x: unknown) {
Expand Down Expand Up @@ -319,6 +319,46 @@ describe('Raise events', () => {
});
});

describe('log', () => {
it('should narrow down the event type in the expression', () => {
createMachine({
schema: {
events: {} as { type: 'FOO' } | { type: 'BAR' }
},
on: {
FOO: {
actions: log((_ctx, ev) => {
((_arg: 'FOO') => {})(ev.type);
// @ts-expect-error
((_arg: 'BAR') => {})(ev.type);
})
}
}
});
});
});

describe('stop', () => {
it('should narrow down the event type in the expression', () => {
createMachine({
schema: {
events: {} as { type: 'FOO' } | { type: 'BAR' }
},
on: {
FOO: {
actions: stop((_ctx, ev) => {
((_arg: 'FOO') => {})(ev.type);
// @ts-expect-error
((_arg: 'BAR') => {})(ev.type);

return 'fakeId';
})
}
}
});
});
});

describe('Typestates', () => {
// Using "none" because undefined and null are unavailable when not in strict mode.
type None = { type: 'none' };
Expand Down

0 comments on commit 02012c2

Please sign in to comment.