holy heck the action shortcuts work

This commit is contained in:
Yanick Champoux 2023-03-07 10:33:05 -05:00
parent 1a2b2df9ac
commit 9ac3032a7f
3 changed files with 62 additions and 14 deletions

View File

@ -9,17 +9,46 @@ import {
configureStore, configureStore,
Reducer, Reducer,
createAction, createAction,
ActionCreator,
PrepareAction, PrepareAction,
ActionCreatorWithPayload,
ActionCreatorWithoutPayload,
ActionCreatorWithPreparedPayload,
} from '@reduxjs/toolkit'; } from '@reduxjs/toolkit';
import { withPayload } from './actions.js'; import { withPayload } from './actions.js';
import { AggregateActions, Dux, UnionToIntersection } from './types.js'; import { AggregateActions, Dux, UnionToIntersection } from './types.js';
import { buildActions } from './buildActions.js'; import { buildActions } from './buildActions.js';
type ActionCreator = ReturnType<typeof createAction>;
type AggregateState<L> = L; type AggregateState<L> = L;
type Mutation<A extends ActionCreator = ActionCreator, S = any> = ( type MyActionCreator = { type: string } & ((...args: any) => any);
type F = null extends any ? 'u' : 'n';
type ResolveAction<
ActionType extends string,
ActionArg extends any,
> = ActionArg extends MyActionCreator
? ActionArg
: ActionArg extends (...args: any) => any
? ActionCreatorWithPreparedPayload<
Parameters<ActionArg>,
ReturnType<ActionArg>,
ActionType
>
: ActionCreatorWithoutPayload<ActionType>;
type ResolveActions<
A extends {
[key: string]: any;
},
> = {
[ActionType in keyof A]: ActionType extends string
? ResolveAction<ActionType, A[ActionType]>
: never;
};
type Mutation<A extends ActionCreator<any> = ActionCreator<any>, S = any> = (
state: S, state: S,
payload: ReturnType<A>['payload'], payload: ReturnType<A>['payload'],
action: ReturnType<A>, action: ReturnType<A>,
@ -27,20 +56,22 @@ type Mutation<A extends ActionCreator = ActionCreator, S = any> = (
export default class Updux< export default class Updux<
T_LocalState = Record<any, any>, T_LocalState = Record<any, any>,
T_LocalActions = {}, T_LocalActions extends {
[actionType: string]: any;
} = {},
SUBDUXES extends Record<string, Dux> = {}, SUBDUXES extends Record<string, Dux> = {},
> { > {
#localInitial: T_LocalState; #localInitial: T_LocalState;
#localActions: T_LocalActions; #localActions: T_LocalActions;
#localMutations: Record< #localMutations: Record<
string, string,
Mutation<ActionCreator, AggregateState<T_LocalState>> Mutation<ActionCreator<any>, AggregateState<T_LocalState>>
> = {}; > = {};
#subduxes: SUBDUXES; #subduxes: SUBDUXES;
#name: string; #name: string;
#actions: AggregateActions<T_LocalActions, SUBDUXES>; #actions: AggregateActions<ResolveActions<T_LocalActions>, SUBDUXES>;
constructor( constructor(
config: Partial<{ config: Partial<{
@ -81,10 +112,10 @@ export default class Updux<
} }
// TODO force the actionCreator to be one of the actions? // TODO force the actionCreator to be one of the actions?
mutation<A extends ActionCreator>( mutation<A extends ActionCreator<any>>(
actionCreator: A, actionCreator: A,
mutation: Mutation<A, AggregateState<T_LocalState>>, mutation: Mutation<A, AggregateState<T_LocalState>>,
) { ) {
this.#localMutations[actionCreator.type] = mutation; this.#localMutations[(actionCreator as any).type] = mutation;
} }
} }

View File

@ -101,17 +101,22 @@ test('throw if double action', () => {
).toThrow(/action 'foo' defined both in subduxes 'gamma' and 'beta'/); ).toThrow(/action 'foo' defined both in subduxes 'gamma' and 'beta'/);
}); });
test.todo('action definition shortcut', () => { test('action definition shortcut', () => {
const foo = new Updux({ const foo = new Updux({
actions: { actions: {
foo: undefined, foo: 0,
bar: (x) => ({ x }), bar: (x: number) => ({ x }),
baz: createAction('baz', withPayload<boolean>()),
}, },
}); });
expect(foo.actions.foo('hello')).toEqual({ type: 'foo', payload: 'hello' }); expect(foo.actions.foo()).toEqual({ type: 'foo', payload: undefined });
expect(foo.actions.bar('hello')).toEqual({ expect(foo.actions.baz(false)).toEqual({
type: 'baz',
payload: false,
});
expect(foo.actions.bar(2)).toEqual({
type: 'bar', type: 'bar',
payload: { x: 'hello' }, payload: { x: 2 },
}); });
}); });

View File

@ -1,6 +1,18 @@
import { createAction } from '@reduxjs/toolkit';
import * as R from 'remeda'; import * as R from 'remeda';
import { withPayload } from './actions.js';
function resolveActions(configActions) {
return R.mapValues(configActions, (prepare, type: string) => {
if (typeof prepare === 'function' && prepare.type) return prepare;
return createAction(type, withPayload(prepare));
});
}
export function buildActions(localActions, subduxes) { export function buildActions(localActions, subduxes) {
localActions = resolveActions(localActions);
let actions: Record<string, string> = {}; let actions: Record<string, string> = {};
for (const slice in subduxes) { for (const slice in subduxes) {