diff --git a/src/Updux.ts b/src/Updux.ts index 2ee4ea8..5527ab2 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -1,15 +1,18 @@ import R from 'remeda'; +import { Action, ActionGenerator } from './actions'; /** * Configuration object typically passed to the constructor of the class Updux. */ -export interface UpduxConfig { +export interface UpduxConfig = Record, TSubduxes = {}> { /** * Local initial state. * @default {} */ initial?: TState; + actions?: TActions; + /** * Subduxes to be merged to this dux. */ @@ -22,13 +25,20 @@ export type DuxStateSubduxes = keyof C extends never ? unknown : { [K in keyof C]: StateOf }; -export class Updux { +export class Updux { #localInitial: any = {}; #subduxes; + #actions : TActions; - constructor(config: UpduxConfig) { + constructor(config: UpduxConfig) { this.#localInitial = config.initial ?? {}; this.#subduxes = config.subduxes ?? {}; + + this.#actions = config.actions ?? ([] as any); + } + + get actions() { + return this.#actions; } get initial(): TState & DuxStateSubduxes { diff --git a/src/actions.test.ts b/src/actions.test.ts new file mode 100644 index 0000000..16d20ae --- /dev/null +++ b/src/actions.test.ts @@ -0,0 +1,37 @@ +import { test, expect } from 'vitest'; + +import { action } from './actions.js'; + +import { Updux } from './Updux.js'; + +test('basic action', () => { + const foo = action('foo', (thing: string) => ({ thing })); + + expect(foo('bar')).toEqual({ + type: 'foo', + payload: { + thing: 'bar', + }, + }); +}); + +test( "Updux config accepts actions", () => { + const foo = new Updux({ + actions: { + one: action('one', (x: string) => ({x})), + two: action('two', x => x), + } + }); + + expect(Object.keys(foo.actions)).toHaveLength(2); + + expect( foo.actions.one ).toBeTypeOf('function'); + expect( foo.actions.one("potato") ).toEqual({ + type: 'one', + payload: { + x: 'potato' + } + }); + +} ) + diff --git a/src/actions.ts b/src/actions.ts new file mode 100644 index 0000000..c50eba8 --- /dev/null +++ b/src/actions.ts @@ -0,0 +1,51 @@ +export type Action = { + type: T; + meta?: Record; + payload?: TPayload; +}; + +export type ActionGenerator< + TType extends string = string, + TPayloadGen = (...args: any[]) => any, +> = { + type: TType; +} & (TPayloadGen extends (...args: any) => any + ? ReturnType extends void + ? (...args: Parameters) => { + type: TType; + } + : (...args: Parameters) => { + type: TType; + payload: ReturnType; + } + : (...args: any[]) => { type: TType; payload?: unknown }); + +export function action< + TType extends string, + TPayload extends (...args: any) => any, +>( + type: TType, + payloadFunction?: TPayload, + transformer?: Function, +): ActionGenerator { + let generator: any = function (...payloadArg:any[]) { + const result: Action = { type }; + + if (payloadFunction) { + result.payload = payloadFunction(...payloadArg); + } else { + if (payloadArg[0] !== undefined) result.payload = payloadArg[0]; + } + + return result; + }; + + if (transformer) { + const orig = generator; + generator = (...args: any) => transformer(orig(...args), args); + } + + generator.type = type; + + return generator; +}