add subdux actions

main
Yanick Champoux 2023-03-06 12:16:06 -05:00
parent d304fc0fcc
commit 5e39c2b162
6 changed files with 69 additions and 25 deletions

View File

@ -1,3 +1,4 @@
import * as R from 'remeda';
import {
createStore as reduxCreateStore,
applyMiddleware,
@ -5,7 +6,8 @@ import {
Action,
} from 'redux';
import { configureStore, Reducer, createAction } from '@reduxjs/toolkit';
import { withPayload } from './actions';
import { withPayload } from './actions.js';
import { AggregateActions, Dux, UnionToIntersection } from './types.js';
type ActionCreator = ReturnType<typeof createAction>;
@ -20,6 +22,7 @@ type Mutation<A extends ActionCreator = ActionCreator, S = any> = (
export default class Updux<
T_LocalState = Record<any, any>,
T_LocalActions = {},
SUBDUXES extends Record<string, Dux> = {},
> {
#localInitial: T_LocalState;
#localActions: T_LocalActions;
@ -27,16 +30,28 @@ export default class Updux<
string,
Mutation<ActionCreator, AggregateState<T_LocalState>>
> = {};
#subduxes: SUBDUXES;
#actions: Record<string, ActionCreator>;
constructor(
config: Partial<{
initial: T_LocalState;
actions: T_LocalActions;
subduxes: SUBDUXES;
}>,
) {
// TODO check that we can't alter the initial after the fact
this.#localInitial = config.initial ?? ({} as T_LocalState);
this.#localActions = config.actions ?? ({} as T_LocalActions);
this.#subduxes = config.subduxes ?? ({} as SUBDUXES);
}
get actions(): AggregateActions<T_LocalActions, SUBDUXES> {
return R.mergeAll([
this.#localActions,
...Object.values(this.#subduxes).map(R.pathOr(['actions'], {})),
]) as any;
}
// TODO memoize?
@ -44,10 +59,6 @@ export default class Updux<
return this.#localInitial;
}
get actions() {
return this.#localActions;
}
createStore(
options: Partial<{
initial: T_LocalState;

View File

@ -34,23 +34,6 @@ test('Updux config accepts actions', () => {
});
});
test('subduxes actions', () => {
const foo = new Updux({
actions: {
foo: null,
},
subduxes: {
beta: {
actions: {
bar: null,
},
},
},
});
expect(foo.actions).toHaveProperty('foo');
expect(foo.actions).toHaveProperty('bar');
});
test('throw if double action', () => {
expect(

27
src/actions.test.ts Normal file
View File

@ -0,0 +1,27 @@
import Updux, { createAction } from './index.js';
test('subduxes actions', () => {
const bar = createAction<number>('bar');
const baz = createAction('baz');
const foo = new Updux({
actions: {
bar,
},
subduxes: {
beta: {
actions: {
baz,
},
},
// to check if we can deal with empty actions
gamma: {},
},
});
expect(foo.actions).toHaveProperty('bar');
expect(foo.actions).toHaveProperty('baz');
expect(foo.actions.bar(2)).toHaveProperty('type', 'bar');
expect(foo.actions.baz()).toHaveProperty('type', 'baz');
});

View File

@ -1,5 +1,7 @@
import { createAction } from '@reduxjs/toolkit';
export { createAction } from '@reduxjs/toolkit';
export const withPayload =
<P>() =>
(payload: P) => ({ payload });
(payload: P) => ({ payload });

View File

@ -1,5 +1,5 @@
import Updux from './Updux';
import Updux from './Updux.js';
export { withPayload, createAction } from './actions';
export { withPayload, createAction } from './actions.js';
export default Updux;

21
src/types.ts Normal file
View File

@ -0,0 +1,21 @@
import { Action, ActionCreator } from 'redux';
export type Dux<
STATE = any,
ACTIONS extends Record<string, ActionCreator<string>> = {},
> = Partial<{
initial: STATE;
actions: ACTIONS;
}>;
type ActionsOf<DUX> = DUX extends { actions: infer A } ? A : {};
export type AggregateActions<A, S> = UnionToIntersection<
ActionsOf<S[keyof S]> | A
>;
export type UnionToIntersection<U> = (
U extends any ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never;