Skip to content

Commit

Permalink
feat(store): merge Action and TypedAction intefaces (#4318)
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

The Action and TypedAction interfaces are merged into one interface.

BEFORE:

There was a separation between the Action and TypedAction interfaces.

AFTER:

The Action interface accepts a generic type parameter that represents the payload type (defaults to string).
The TypedAction interface is removed.
  • Loading branch information
timdeschryver authored May 7, 2024
1 parent 31979cb commit c8bde71
Show file tree
Hide file tree
Showing 8 changed files with 44 additions and 51 deletions.
6 changes: 3 additions & 3 deletions modules/effects/spec/types/effect_creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('createEffect()', () => {
expectSnippet(`
const effect = createEffect(() => of({ foo: 'a' }));
`).toFail(
/Type 'Observable<{ foo: string; }>' is not assignable to type 'EffectResult<Action>'./
/Type 'Observable<{ foo: string; }>' is not assignable to type 'EffectResult<Action<string>>'./
);
});

Expand Down Expand Up @@ -50,7 +50,7 @@ describe('createEffect()', () => {
expectSnippet(`
const effect = createEffect(() => of({ foo: 'a' }), { dispatch: true });
`).toFail(
/Type 'Observable<{ foo: string; }>' is not assignable to type 'EffectResult<Action>'./
/Type 'Observable<{ foo: string; }>' is not assignable to type 'EffectResult<Action<string>>'./
);
});

Expand Down Expand Up @@ -176,7 +176,7 @@ describe('createEffect()', () => {
);
`).toInfer(
'effect',
'FunctionalEffect<() => Observable<ActionCreator<"a", () => TypedAction<"a">>>>'
'FunctionalEffect<() => Observable<ActionCreator<"a", () => Action<"a">>>>'
);
});

Expand Down
16 changes: 5 additions & 11 deletions modules/effects/spec/types/of_type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,21 @@ describe('ofType()', () => {
expectSnippet(`
const actionA = createAction('Action A');
const effect = actions$.pipe(ofType(actionA))
`).toInfer('effect', 'Observable<TypedAction<"Action A">>');
`).toInfer('effect', 'Observable<Action<"Action A">>');
});

it('should infer correctly with props', () => {
expectSnippet(`
const actionA = createAction('Action A', props<{ foo: string }>());
const effect = actions$.pipe(ofType(actionA))
`).toInfer(
'effect',
'Observable<{ foo: string; } & TypedAction<"Action A">>'
);
`).toInfer('effect', 'Observable<{ foo: string; } & Action<"Action A">>');
});

it('should infer correctly with function', () => {
expectSnippet(`
const actionA = createAction('Action A', (foo: string) => ({ foo }));
const effect = actions$.pipe(ofType(actionA))
`).toInfer(
'effect',
'Observable<{ foo: string; } & TypedAction<"Action A">>'
);
`).toInfer('effect', 'Observable<{ foo: string; } & Action<"Action A">>');
});

it('should infer correctly with multiple actions (with over 5 actions)', () => {
Expand All @@ -55,7 +49,7 @@ describe('ofType()', () => {
const effect = actions$.pipe(ofType(actionA, actionB, actionC, actionD, actionE, actionF, actionG))
`).toInfer(
'effect',
'Observable<TypedAction<"Action A"> | TypedAction<"Action B"> | TypedAction<"Action C"> | TypedAction<"Action D"> | TypedAction<"Action E"> | TypedAction<...> | TypedAction<...>>'
'Observable<Action<"Action A"> | Action<"Action B"> | Action<"Action C"> | Action<"Action D"> | Action<"Action E"> | Action<"Action F"> | Action<...>>'
);
});
});
Expand Down Expand Up @@ -106,7 +100,7 @@ describe('ofType()', () => {
expectSnippet(`
const actions$ = {} as Actions<ActionA | ActionB | ActionC | ActionD | ActionE | ActionF>;
const effect = actions$.pipe(ofType(ACTION_A, ACTION_B, ACTION_C, ACTION_D, ACTION_E, ACTION_F))
`).toInfer('effect', 'Observable<Action>');
`).toInfer('effect', 'Observable<Action<string>>');
});

it('should infer to never when the action is not in Actions', () => {
Expand Down
18 changes: 9 additions & 9 deletions modules/store/spec/types/action_group_creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,30 @@ describe('createActionGroup', () => {
`ActionCreator<
"[Auth API] Login Success",
(props: { userId: number; token: string; }) =>
{ userId: number; token: string; } & TypedAction<"[Auth API] Login Success">
{ userId: number; token: string; } & Action<"[Auth API] Login Success">
>`
);
snippet.toInfer(
'loginFailure',
`ActionCreator<
"[Auth API] Login Failure",
(props: { error: string; }) =>
{ error: string; } & TypedAction<"[Auth API] Login Failure">
{ error: string; } & Action<"[Auth API] Login Failure">
>`
);
snippet.toInfer(
'logoutSuccess',
`ActionCreator<
"[Auth API] Logout Success",
() => TypedAction<"[Auth API] Logout Success">
() => Action<"[Auth API] Logout Success">
>`
);
snippet.toInfer(
'logoutFailure',
`FunctionWithParametersType<
[error: Error],
{ error: Error; } & TypedAction<"[Auth API] Logout Failure">
> & TypedAction<"[Auth API] Logout Failure">`
{ error: Error; } & Action<"[Auth API] Logout Failure">
> & Action<"[Auth API] Logout Failure">`
);
});

Expand Down Expand Up @@ -115,14 +115,14 @@ describe('createActionGroup', () => {
'loadBooksSuccess',
`ActionCreator<
"[Books API] Load BOOKS suCCess ",
() => TypedAction<"[Books API] Load BOOKS suCCess ">
() => Action<"[Books API] Load BOOKS suCCess ">
>`
);
snippet.toInfer(
'loadBooksFailure',
`ActionCreator<
"[Books API] loadBooksFailure",
() => TypedAction<"[Books API] loadBooksFailure">
() => Action<"[Books API] loadBooksFailure">
>`
);
});
Expand Down Expand Up @@ -164,7 +164,7 @@ describe('createActionGroup', () => {
let loadBooksSuccess: typeof booksApiActions.loadBooksSuccess;
`).toInfer(
'loadBooksSuccess',
'ActionCreator<"[Books API] Load Books Success", (props: { books: string[]; total: number; } | { books: symbol[]; }) => ({ books: string[]; total: number; } | { books: symbol[]; }) & TypedAction<"[Books API] Load Books Success">>'
'ActionCreator<"[Books API] Load Books Success", (props: { books: string[]; total: number; } | { books: symbol[]; }) => ({ books: string[]; total: number; } | { books: symbol[]; }) & Action<"[Books API] Load Books Success">>'
);
});

Expand All @@ -180,7 +180,7 @@ describe('createActionGroup', () => {
let loadBooksSuccess: typeof booksApiActions.loadBooksSuccess;
`).toInfer(
'loadBooksSuccess',
'ActionCreator<"[Books API] Load Books Success", (props: { books: string[]; } & { total: number; }) => { books: string[]; } & { total: number; } & TypedAction<"[Books API] Load Books Success">>'
'ActionCreator<"[Books API] Load Books Success", (props: { books: string[]; } & { total: number; }) => { books: string[]; } & { total: number; } & Action<"[Books API] Load Books Success">>'
);
});

Expand Down
14 changes: 10 additions & 4 deletions modules/store/spec/types/feature_creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('createFeature()', () => {
`);

snippet.toInfer('name', '"products"');
snippet.toInfer('reducer', 'ActionReducer<State, Action>');
snippet.toInfer('reducer', 'ActionReducer<State, Action<string>>');
snippet.toInfer(
'selectProductsState',
'MemoizedSelector<Record<string, any>, State, (featureState: State) => State>'
Expand Down Expand Up @@ -103,7 +103,10 @@ describe('createFeature()', () => {
`);

snippet.toInfer('name', '"counter"');
snippet.toInfer('reducer', 'ActionReducer<{ count: number; }, Action>');
snippet.toInfer(
'reducer',
'ActionReducer<{ count: number; }, Action<string>>'
);
snippet.toInfer(
'selectCounterState',
'MemoizedSelector<Record<string, any>, { count: number; }, (featureState: { count: number; }) => { count: number; }>'
Expand Down Expand Up @@ -260,7 +263,7 @@ describe('createFeature()', () => {
`);

snippet.toInfer('name', '"counter"');
snippet.toInfer('reducer', 'ActionReducer<State, Action>');
snippet.toInfer('reducer', 'ActionReducer<State, Action<string>>');
snippet.toInfer(
'selectCounterState',
'MemoizedSelector<Record<string, any>, State, (featureState: State) => State>'
Expand Down Expand Up @@ -314,7 +317,10 @@ describe('createFeature()', () => {
`);

snippet.toInfer('name', '"counter"');
snippet.toInfer('reducer', 'ActionReducer<{ count: number; }, Action>');
snippet.toInfer(
'reducer',
'ActionReducer<{ count: number; }, Action<string>>'
);
snippet.toInfer(
'selectCounterState',
'MemoizedSelector<Record<string, any>, { count: number; }, (featureState: { count: number; }) => { count: number; }>'
Expand Down
10 changes: 5 additions & 5 deletions modules/store/spec/types/reducer_creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('createReducer()', () => {
on(setAction, (_, { value }) => value),
on(resetAction, () => initialState),
);
`).toInfer('reducer', 'ActionReducer<State, Action>');
`).toInfer('reducer', 'ActionReducer<State, Action<string>>');
});

it('should support arrays', () => {
Expand All @@ -40,7 +40,7 @@ describe('createReducer()', () => {
on(setAction, (_, { value }) => value),
on(resetAction, () => initialState),
);
`).toInfer('reducer', 'ActionReducer<string[], Action>');
`).toInfer('reducer', 'ActionReducer<string[], Action<string>>');
});

it('should support primitive types', () => {
Expand All @@ -55,7 +55,7 @@ describe('createReducer()', () => {
on(setAction, (_, { value }) => value),
on(resetAction, () => initialState),
);
`).toInfer('reducer', 'ActionReducer<number, Action>');
`).toInfer('reducer', 'ActionReducer<number, Action<string>>');
});

it('should support a generic reducer factory', () => {
Expand Down Expand Up @@ -105,7 +105,7 @@ describe('createReducer()', () => {
foo: string;
}) => {
foo: string;
} & TypedAction<"FOO">>]>
} & Action<"FOO">>]>
`
);
});
Expand All @@ -120,7 +120,7 @@ describe('createReducer()', () => {
`
ReducerTypes<{
name: string;
}, [ActionCreator<"FOO", () => TypedAction<"FOO">>]>
}, [ActionCreator<"FOO", () => Action<"FOO">>]>
`
);
});
Expand Down
8 changes: 4 additions & 4 deletions modules/store/src/action_creator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
Creator,
ActionCreator,
TypedAction,
Action,
FunctionWithParametersType,
NotAllowedCheck,
ActionCreatorProps,
Expand All @@ -14,19 +14,19 @@ import { REGISTERED_ACTION_TYPES } from './globals';

export function createAction<T extends string>(
type: T
): ActionCreator<T, () => TypedAction<T>>;
): ActionCreator<T, () => Action<T>>;
export function createAction<T extends string, P extends object>(
type: T,
config: ActionCreatorProps<P> & NotAllowedCheck<P>
): ActionCreator<T, (props: P & NotAllowedCheck<P>) => P & TypedAction<T>>;
): ActionCreator<T, (props: P & NotAllowedCheck<P>) => P & Action<T>>;
export function createAction<
T extends string,
P extends any[],
R extends object
>(
type: T,
creator: Creator<P, R & NotAllowedCheck<R>>
): FunctionWithParametersType<P, R & TypedAction<T>> & TypedAction<T>;
): FunctionWithParametersType<P, R & Action<T>> & Action<T>;
/**
* @description
* Creates a configured `Creator` function that, when called, returns an object in the shape of the `Action` interface.
Expand Down
12 changes: 5 additions & 7 deletions modules/store/src/action_group_creator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Creator,
FunctionWithParametersType,
NotAllowedCheck,
TypedAction,
Action,
} from './models';
import { capitalize, uncapitalize } from './helpers';

Expand Down Expand Up @@ -48,19 +48,17 @@ type EventCreator<
Type extends string
> = PropsCreator extends ActionCreatorProps<infer Props>
? void extends Props
? ActionCreator<Type, () => TypedAction<Type>>
? ActionCreator<Type, () => Action<Type>>
: ActionCreator<
Type,
(
props: Props & NotAllowedCheck<Props & object>
) => Props & TypedAction<Type>
(props: Props & NotAllowedCheck<Props & object>) => Props & Action<Type>
>
: PropsCreator extends Creator<infer Props, infer Result>
? FunctionWithParametersType<
Props,
Result & NotAllowedCheck<Result> & TypedAction<Type>
Result & NotAllowedCheck<Result> & Action<Type>
> &
TypedAction<Type>
Action<Type>
: never;

type ActionName<EventName extends string> = Uncapitalize<
Expand Down
11 changes: 3 additions & 8 deletions modules/store/src/models.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import { type ValueEqualityFn } from '@angular/core';

export interface Action {
type: string;
}

// declare to make it property-renaming safe
export declare interface TypedAction<T extends string> extends Action {
readonly type: T;
export interface Action<Type extends string = string> {
type: Type;
}

export type ActionType<A> = A extends ActionCreator<infer T, infer C>
Expand Down Expand Up @@ -132,7 +127,7 @@ export type NotAllowedInPropsCheck<T> = T extends object
export type ActionCreator<
T extends string = string,
C extends Creator = Creator
> = C & TypedAction<T>;
> = C & Action<T>;

export interface ActionCreatorProps<T> {
_as: 'props';
Expand Down

0 comments on commit c8bde71

Please sign in to comment.