diff --git a/Taskfile.yml b/Taskfile.yml index 337dafb..daa9a40 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -13,12 +13,12 @@ tasks: test: jest - 'check:prettier': prettier --check . + 'prettier': prettier --check . - 'check:prettier:fix': prettier --write . + 'prettier:fix': prettier --write . check: - deps: [tsc, test, 'check:prettier'] + deps: [tsc, test, 'prettier'] 'test:types': tsd docs: diff --git a/src/Updux.ts b/src/Updux.ts index aaa2714..76f9848 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -136,7 +136,10 @@ export class Updux< if (typeof actionArg === 'function' && actionArg.type) { this.#actions[type] = actionArg; } else { - this.#actions[type] = action(type, actionArg); + const args = Array.isArray(actionArg) + ? actionArg + : [actionArg]; + this.#actions[type] = action(type, ...args); } } } @@ -226,7 +229,7 @@ export class Updux< this.#reactions = [...this.#reactions, subscription]; } - setAction(type, payloadFunc?: Function) { + setAction(type, payloadFunc?: (...args: any) => any) { const theAction = action(type, payloadFunc); this.#actions = { ...this.#actions, [type]: theAction }; @@ -404,7 +407,7 @@ export class Updux< for (const action in this.actions) { store.dispatch[action] = (...args) => { - return store.dispatch(this.actions[action](...args)); + return store.dispatch(this.actions[action](...(args as any))); }; } diff --git a/src/actions.ts b/src/actions.ts index 9db8252..957ee4b 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -7,14 +7,18 @@ export type Action = { export type ActionGenerator< TType extends string = string, - TPayloadGen = undefined + TPayloadGen = (...args: any[]) => any > = { type: TType; } & (TPayloadGen extends (...args: any) => any - ? (...args: Parameters) => { - type: TType; - payload: ReturnType; - } + ? ReturnType extends void + ? (...args: Parameters) => { + type: TType; + } + : (...args: Parameters) => { + type: TType; + payload: ReturnType; + } : (...args: any[]) => { type: TType; payload?: unknown }); // interface Action { @@ -25,7 +29,14 @@ export type ActionGenerator< // ): ActionGenerator; // } -export function action(type, payloadFunction = null) { +export function action< + TType extends string, + TPayload extends (...args: any) => any +>( + type: TType, + payloadFunction?: TPayload, + transformer?: Function +): ActionGenerator { const generator = function (...payloadArg) { const result: Action = { type }; @@ -40,5 +51,9 @@ export function action(type, payloadFunction = null) { generator.type = type; - return generator; + return ( + transformer + ? (...args: any) => transformer(generator(...args), args) + : generator + ) as any; } diff --git a/src/types.test.ts b/src/types.test.ts new file mode 100644 index 0000000..05d436d --- /dev/null +++ b/src/types.test.ts @@ -0,0 +1,42 @@ +import { Updux } from './Updux'; +import { Action, action, ActionGenerator } from './actions'; +import { expectAssignable, expectType, expectNotType } from 'tsd'; + +type SimplePayload = (x: number) => number; + +test('dux actions', () => { + const a1 = action('a1', (x: number) => x); + + const inner = new Updux({ + actions: { + a7: (x: string) => x, + }, + }); + + const dux = new Updux({ + actions: { + a1, + a2: () => {}, + a5: (x: number) => x, + a6: action('a6', undefined, (x) => ({ + ...x, + meta: 'bob', + })), + }, + subduxes: { + inner, + }, + }); + + expectAssignable>(dux.actions.a1); + expectAssignable>(dux.actions.a5); + expectAssignable>(dux.actions.a2()); + + expectType<{ payload: number }>(dux.actions.a1(12)); + + expectType<{ type: 'a2' }>(dux.actions.a2()); + + expectType<{ type: 'a6' }>(dux.actions.a6()); + + expectType<{ type: 'a7'; payload: string }>(dux.actions.a7('potato')); +}); diff --git a/src/types.ts b/src/types.ts index def3484..3e4460b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,14 +1,14 @@ -import { Action } from './actions'; +import { Action, ActionGenerator } from './actions'; export type Dict = Record; export type Upreducer = ( - action: Action + action: TAction ) => (state: TState) => TState; export type Reducer = ( state: TState | undefined, - action: Action + action: TAction ) => TState; export type UnionToIntersection = ( @@ -22,11 +22,7 @@ type Subdux = { }; type StateOf = D extends { initial: infer I } ? I : unknown; -type ActionsOf = C extends { actions: infer A } - ? { - [K in keyof A]: Function; - } - : {}; +type ActionsOf = C extends { actions: infer A } ? A : {}; type Subduxes = Record; @@ -44,5 +40,20 @@ type DuxActionsSubduxes = C extends object ? ActionsOf : unknown; export type ItemsOf = C extends object ? C[keyof C] : unknown; -export type AggregateDuxActions = TActions & - UnionToIntersection>>; +type Actionize = TArgs extends ActionGenerator + ? TArgs + : TArgs extends any[] + ? ActionGenerator + : ActionGenerator; + +export type MergeDuxActions = UnionToIntersection< + ActionsOf | TActions +>; + +export type AggregateDuxActionsInner = { + [K in keyof TActions]: K extends string ? Actionize : never; +}; + +export type AggregateDuxActions = AggregateDuxActionsInner< + MergeDuxActions +>;