Skip to content

Commit

Permalink
Refactored type helpers and exported as public API
Browse files Browse the repository at this point in the history
Resolved #123
  • Loading branch information
piotrwitek committed Apr 21, 2019
1 parent 4ad21b9 commit 674c087
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 181 deletions.
34 changes: 26 additions & 8 deletions src/__snapshots__/create-reducer.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`With Action Creators fn(0, add(4)) (type) should match snapshot 1`] = `"number"`;

exports[`With Action Creators fn(0, add(4)) (type) should match snapshot 2`] = `"number"`;

exports[`With Action Creators fn(0, add(4)) (type) should match snapshot 3`] = `"number"`;
exports[`With Action Creators fn(0, add(4)) (type) should match snapshot 1`] = `
"Argument of type 'PayloadAction<\\"ADD\\", number>' is not assignable to parameter of type 'EmptyAction<\\"INCREMENT\\">'.
Types of property 'type' are incompatible.
Type '\\"ADD\\"' is not assignable to type '\\"INCREMENT\\"'."
`;

exports[`With Action Creators fn(0, add(4)) (type) should match snapshot 2`] = `
"Argument of type 'PayloadAction<\\"ADD\\", number>' is not assignable to parameter of type 'EmptyAction<\\"INCREMENT\\">'.
Types of property 'type' are incompatible.
Type '\\"ADD\\"' is not assignable to type '\\"INCREMENT\\"'."
`;

exports[`With Action Creators fn(0, add(4)) (type) should match snapshot 3`] = `
"Argument of type 'PayloadAction<\\"ADD\\", number>' is not assignable to parameter of type 'EmptyAction<\\"INCREMENT\\">'.
Types of property 'type' are incompatible.
Type '\\"ADD\\"' is not assignable to type '\\"INCREMENT\\"'."
`;

exports[`With Action Creators fn(0, increment()) (type) should match snapshot 1`] = `"number"`;

exports[`With Action Creators fn(0, increment()) (type) should match snapshot 2`] = `"number"`;

exports[`With Action Creators fn(0, increment()) (type) should match snapshot 3`] = `"number"`;

exports[`With Action Types fn(0, add(4)) (type) should match snapshot 1`] = `"number"`;
exports[`With Action Types fn(0, {} as any) (type) should match snapshot 1`] = `"number"`;

exports[`With Action Types fn(0, {} as any) (type) should match snapshot 2`] = `"number"`;

exports[`With Action Types fn(0, {} as any) (type) should match snapshot 3`] = `"number"`;

exports[`With Action Types fn(0, add(4)) (type) should match snapshot 1`] = `"Argument of type 'PayloadAction<\\"ADD\\", number>' is not assignable to parameter of type 'EmptyAction<\\"INCREMENT\\">'."`;

exports[`With Action Types fn(0, add(4)) (type) should match snapshot 2`] = `"number"`;
exports[`With Action Types fn(0, add(4)) (type) should match snapshot 2`] = `"Argument of type 'PayloadAction<\\"ADD\\", number>' is not assignable to parameter of type 'EmptyAction<\\"INCREMENT\\">'."`;

exports[`With Action Types fn(0, add(4)) (type) should match snapshot 3`] = `"number"`;
exports[`With Action Types fn(0, add(4)) (type) should match snapshot 3`] = `"Argument of type 'PayloadAction<\\"ADD\\", number>' is not assignable to parameter of type 'EmptyAction<\\"INCREMENT\\">'."`;

exports[`With Action Types fn(0, increment()) (type) should match snapshot 1`] = `"number"`;

Expand Down
20 changes: 10 additions & 10 deletions src/action.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,57 @@
import { StringType } from './type-helpers';
import { TypeConstant } from './type-helpers';
import {
checkIsEmpty,
throwIsEmpty,
checkInvalidActionType,
throwInvalidActionCreator,
} from './utils/validation';

export function action<T extends StringType, E>(
export function action<T extends TypeConstant, E>(
type: T,
payload: undefined,
meta: undefined,
error: E
): { type: T; error: E };

export function action<T extends StringType, M, E>(
export function action<T extends TypeConstant, M, E>(
type: T,
payload: undefined,
meta: M,
error: E
): { type: T; meta: M; error: E };

export function action<T extends StringType, P, E>(
export function action<T extends TypeConstant, P, E>(
type: T,
payload: P,
meta: undefined,
error: E
): { type: T; payload: P; error: E };

export function action<T extends StringType, P, M, E>(
export function action<T extends TypeConstant, P, M, E>(
type: T,
payload: P,
meta: M,
error: E
): { type: T; payload: P; meta: M; error: E };

export function action<T extends StringType, M>(
export function action<T extends TypeConstant, M>(
type: T,
payload: undefined,
meta: M
): { type: T; meta: M };

export function action<T extends StringType, P, M>(
export function action<T extends TypeConstant, P, M>(
type: T,
payload: P,
meta: M
): { type: T; payload: P; meta: M };

export function action<T extends StringType, P>(
export function action<T extends TypeConstant, P>(
type: T,
payload: P
): { type: T; payload: P };

export function action<T extends StringType>(type: T): { type: T };
export function action<T extends TypeConstant>(type: T): { type: T };

/**
* @description flux standard action factory
Expand All @@ -61,7 +61,7 @@ export function action<T extends StringType>(type: T): { type: T };
* ```
*/
export function action<
T extends StringType,
T extends TypeConstant,
P = undefined,
M = undefined,
E = undefined
Expand Down
10 changes: 5 additions & 5 deletions src/create-action-deprecated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
* DEPRECATED
*/

import { StringType } from './type-helpers';
import { TypeConstant } from './type-helpers';

interface FSA<T extends StringType, P = {}, M = {}, E = boolean> {
interface FSA<T extends TypeConstant, P = {}, M = {}, E = boolean> {
type: T;
payload?: P;
meta?: M;
Expand All @@ -16,7 +16,7 @@ interface FSA<T extends StringType, P = {}, M = {}, E = boolean> {
* @description create an action-creator of a given function that contains hidden "type" metadata
*/
export function createActionDeprecated<
T extends StringType,
T extends TypeConstant,
AC extends (...args: any[]) => FSA<T>
>(actionType: T, creatorFunction: AC): AC;

Expand All @@ -25,15 +25,15 @@ export function createActionDeprecated<
* @description create an action-creator of a given function that contains hidden "type" metadata
*/
export function createActionDeprecated<
T extends StringType,
T extends TypeConstant,
AC extends () => { type: T }
>(actionType: T): AC;

/**
* implementation
*/
export function createActionDeprecated<
T extends StringType,
T extends TypeConstant,
AC extends (...args: any[]) => FSA<T>
>(actionType: T, creatorFunction?: AC): AC {
let actionCreator: AC;
Expand Down
10 changes: 7 additions & 3 deletions src/create-action.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { StringType, ActionCreator } from './type-helpers';
import { TypeConstant, ActionCreator } from './type-helpers';
import { action } from './action';

export type PayloadMetaAction<T extends StringType, P, M> = P extends undefined
export type PayloadMetaAction<
T extends TypeConstant,
P,
M
> = P extends undefined
? M extends undefined
? { type: T }
: { type: T; meta: M }
Expand All @@ -13,7 +17,7 @@ export type PayloadMetaAction<T extends StringType, P, M> = P extends undefined
* @description typesafe action-creator factory
*/
export function createAction<
T extends StringType,
T extends TypeConstant,
AC extends ActionCreator<T> = () => { type: T }
>(
type: T,
Expand Down
20 changes: 10 additions & 10 deletions src/create-async-action.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {
StringType,
TypeConstant,
ActionBuilderConstructor,
// ActionBuilderMap,
} from './type-helpers';
import { createCustomAction } from './create-custom-action';
import { checkInvalidActionTypeInArray } from './utils/validation';

export interface AsyncActionBuilder<
T1 extends StringType,
T2 extends StringType,
T3 extends StringType
T1 extends TypeConstant,
T2 extends TypeConstant,
T3 extends TypeConstant
> {
// tslint:disable-next-line:callable-types
<P1, P2, P3>(): AsyncActionBuilderConstructor<T1, T2, T3, P1, P2, P3>;
Expand All @@ -21,9 +21,9 @@ export interface AsyncActionBuilder<
}

export type AsyncActionBuilderConstructor<
T1 extends StringType,
T2 extends StringType,
T3 extends StringType,
T1 extends TypeConstant,
T2 extends TypeConstant,
T3 extends TypeConstant,
P1,
P2,
P3
Expand All @@ -37,9 +37,9 @@ export type AsyncActionBuilderConstructor<
* implementation
*/
export function createAsyncAction<
T1 extends StringType,
T2 extends StringType,
T3 extends StringType
T1 extends TypeConstant,
T2 extends TypeConstant,
T3 extends TypeConstant
>(
requestType: T1,
successType: T2,
Expand Down
4 changes: 2 additions & 2 deletions src/create-custom-action.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ActionCreator, StringType } from './type-helpers';
import { ActionCreator, TypeConstant } from './type-helpers';
import {
checkIsEmpty,
throwIsEmpty,
Expand All @@ -10,7 +10,7 @@ import {
* @description create custom action-creator using constructor function with injected type argument
*/
export function createCustomAction<
T extends StringType,
T extends TypeConstant,
AC extends ActionCreator<T> = () => { type: T }
>(type: T, createHandler?: (type: T) => AC): AC {
if (checkIsEmpty(type)) {
Expand Down
8 changes: 6 additions & 2 deletions src/create-reducer.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ActionType, createStandardAction, createReducer } from '.';
import { createStandardAction } from './create-standard-action';
import { createReducer } from './create-reducer';
import { ActionType } from './type-helpers';

const add = createStandardAction('ADD')<number>();
const increment = createStandardAction('INCREMENT')();
const actions = { add, increment };

declare module '.' {
declare module './' {
export type RootAction = ActionType<typeof actions>;
}

Expand Down Expand Up @@ -64,6 +66,8 @@ describe('With Action Types', () => {
});

[counterReducer1, counterReducer2, counterReducer3].forEach(fn => {
// @dts-jest:pass:snap
fn(0, {} as any); // => 0
// @dts-jest:pass:snap
fn(0, increment()); // => 1
// @dts-jest:pass:snap
Expand Down
70 changes: 35 additions & 35 deletions src/create-reducer.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,49 @@
// @ts-ignore
import { RootAction } from '.';
import { RootAction } from './';
import { getType } from './get-type';
import {
checkValidActionCreator,
checkValidActionType,
throwInvalidActionTypeOrActionCreator,
} from './utils/validation';
import { Reducer, Action } from './type-helpers';

export function createReducer<S, A extends { type: string } = RootAction>(
type AddHandler<S, TAllActions extends Action> = <
TType extends TAllActions['type'],
TTypeAction extends TAllActions extends { type: TType } ? TAllActions : never,
TCreator extends (...args: any[]) => TAllActions,
TCreatorAction extends TAllActions extends ReturnType<TCreator>
? TAllActions
: never,
TActionIntersection extends TTypeAction extends TCreatorAction
? TTypeAction
: never
>(
actionsTypes: TType | TCreator | TType[] | TCreator[],
actionsHandler: (state: S, action: TActionIntersection) => S
) => Exclude<TAllActions, TTypeAction & TCreatorAction> extends never
? Reducer<S, TAllActions>
: Reducer<S, TAllActions> & {
addHandler: AddHandler<
S,
Exclude<TAllActions, TTypeAction & TCreatorAction>
>;
};

export function createReducer<S, A extends Action = RootAction>(
initialState: S
) {
type AddHandler<TAllActions extends A> = <
TType extends TAllActions['type'],
TTypeAction extends TAllActions extends { type: TType }
? TAllActions
: never,
TCreator extends (...args: any[]) => TAllActions,
TCreatorAction extends TAllActions extends ReturnType<TCreator>
? TAllActions
: never,
TActionIntersection extends TTypeAction extends TCreatorAction
? TTypeAction
: never
>(
actionsTypes: TType | TCreator | TType[] | TCreator[],
actionsHandler: (state: S, action: TActionIntersection) => S
) => Exclude<TAllActions, TTypeAction & TCreatorAction> extends never
? Reducer
: Reducer & {
addHandler: AddHandler<
Exclude<TAllActions, TTypeAction & TCreatorAction>
>;
};

type AddHandlerChain = { addHandler: AddHandler<A> };
const handlers: Record<string, (state: S, action: RootAction) => S> = {};

const handlers: Record<string, Reducer> = {};

type Reducer = (state: S, action: A) => S;
const reducer: Reducer = (state = initialState, action) => {
const reducer: Reducer<S, A> = (state = initialState, action) => {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action);
} else {
return state;
}
};

const addHandler = ((actionsTypes, actionsHandler: Reducer) => {
const addHandler = ((actionsTypes, actionsHandler) => {
const creatorsOrTypes = Array.isArray(actionsTypes)
? actionsTypes
: [actionsTypes];
Expand All @@ -60,12 +57,15 @@ export function createReducer<S, A extends { type: string } = RootAction>(
: throwInvalidActionTypeOrActionCreator()
)
.forEach(type => (handlers[type] = actionsHandler));
return chain;
}) as AddHandler<A>;

const chain: Reducer & AddHandlerChain = Object.assign(reducer, {
return chainApi;
}) as AddHandler<S, A>;

const chainApi: Reducer<S, A> & {
addHandler: AddHandler<S, A>;
} = Object.assign(reducer, {
addHandler,
});

return chain;
return chainApi;
}
6 changes: 3 additions & 3 deletions src/create-standard-action.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
StringType,
TypeConstant,
ActionBuilderConstructor,
ActionBuilderMap,
} from './type-helpers';
Expand All @@ -11,7 +11,7 @@ import {
throwInvalidActionType,
} from './utils/validation';

export interface ActionBuilder<T extends StringType> {
export interface ActionBuilder<T extends TypeConstant> {
<P = undefined, M = undefined>(): ActionBuilderConstructor<T, P, M>;
map<R, P = undefined, M = undefined>(
fn: (payload: P, meta: M) => R
Expand All @@ -21,7 +21,7 @@ export interface ActionBuilder<T extends StringType> {
/**
* @description create an action-creator of a given function that contains hidden "type" metadata
*/
export function createStandardAction<T extends StringType>(
export function createStandardAction<T extends TypeConstant>(
type: T
): ActionBuilder<T> {
if (checkIsEmpty(type)) {
Expand Down
Loading

0 comments on commit 674c087

Please sign in to comment.