Merge branch 'asDux' into dev-v4
This commit is contained in:
commit
39aaf231ac
43
src/Updux.ts
43
src/Updux.ts
@ -11,6 +11,7 @@ import {
|
||||
DuxSelectors,
|
||||
DuxState,
|
||||
Mutation,
|
||||
MutationEntry,
|
||||
} from './types.js';
|
||||
import baseMoize from 'moize/mjs/index.mjs';
|
||||
import { buildInitialState } from './initialState.js';
|
||||
@ -36,6 +37,7 @@ interface ActionCreator<T extends string, P, A extends Array<any>> {
|
||||
|
||||
const moize = (func) => baseMoize(func, { maxSize: 1 });
|
||||
|
||||
|
||||
/**
|
||||
* @description main Updux class
|
||||
*/
|
||||
@ -68,7 +70,7 @@ export default class Updux<D extends DuxConfig> {
|
||||
};
|
||||
|
||||
/** @internal */
|
||||
#effects = [];
|
||||
#effects: EffectMiddleware<D>[] = [];
|
||||
|
||||
/** @internal */
|
||||
#reactions: DuxReaction<D>[] = [];
|
||||
@ -76,12 +78,21 @@ export default class Updux<D extends DuxConfig> {
|
||||
/** @internal */
|
||||
#mutations: any[] = [];
|
||||
|
||||
/** @internal */
|
||||
#inheritedReducer: (state: DuxState<D> | undefined, action: AnyAction) => DuxState<D>;
|
||||
|
||||
constructor(private readonly duxConfig: D) {
|
||||
if (duxConfig.subduxes)
|
||||
this.#subduxes = D.map(duxConfig.subduxes, (s) =>
|
||||
s instanceof Updux ? s : new Updux(s),
|
||||
);
|
||||
|
||||
this.#inheritedReducer = duxConfig.reducer;
|
||||
|
||||
this.#effects = duxConfig.effects ?? [];
|
||||
|
||||
this.#reactions = (duxConfig.reactions as any) ?? [];
|
||||
|
||||
this.#actions = buildActions(duxConfig.actions, this.#subduxes) as any;
|
||||
}
|
||||
|
||||
@ -223,6 +234,7 @@ export default class Updux<D extends DuxConfig> {
|
||||
this.#mutations,
|
||||
this.#defaultMutation,
|
||||
this.#subduxes,
|
||||
this.#inheritedReducer,
|
||||
);
|
||||
}
|
||||
|
||||
@ -282,11 +294,11 @@ export default class Updux<D extends DuxConfig> {
|
||||
return this as any;
|
||||
}
|
||||
|
||||
get effects(): any {
|
||||
get effects(): any[] {
|
||||
return this.#memoBuildEffects(this.#effects, this.#subduxes);
|
||||
}
|
||||
|
||||
get upreducer() {
|
||||
get upreducer(): (action: AnyAction) => (state?: DuxState<D>) => DuxState<D> {
|
||||
return (action: AnyAction) => (state?: DuxState<D>) => this.reducer(state, action);
|
||||
}
|
||||
|
||||
@ -318,4 +330,29 @@ export default class Updux<D extends DuxConfig> {
|
||||
return this.#memoBuildReactions(this.#reactions, this.#subduxes);
|
||||
}
|
||||
|
||||
get defaultMutation() {
|
||||
return this.#defaultMutation;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns an object holding the Updux reducer and all its
|
||||
* paraphernalia.
|
||||
*/
|
||||
get asDux(): {
|
||||
initialState: DuxState<D>,
|
||||
actions: DuxActions<D>,
|
||||
reducer: (state: DuxState<D>, action: AnyAction) => DuxState<D>,
|
||||
effects: EffectMiddleware<D>[],
|
||||
reactions: DuxReaction<D>[],
|
||||
} {
|
||||
return {
|
||||
initialState: this.initialState,
|
||||
actions: this.actions,
|
||||
effects: this.effects,
|
||||
reactions: this.reactions,
|
||||
reducer: this.reducer,
|
||||
} as any;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
37
src/asDux.test.ts
Normal file
37
src/asDux.test.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import Updux, { createAction } from './index.js';
|
||||
|
||||
test('asDux', () => {
|
||||
const actionA = createAction('actionA');
|
||||
const defaultMutation = () => (state: number) => state + 1;
|
||||
const dux = new Updux({
|
||||
initialState: 13,
|
||||
actions: { actionA },
|
||||
})
|
||||
.setDefaultMutation(defaultMutation)
|
||||
.addReaction((api) => (state) => { })
|
||||
.addEffect((api) => (next) => (action) => next(action));
|
||||
|
||||
const asDux = dux.asDux;
|
||||
|
||||
expect(asDux.initialState).toEqual(13);
|
||||
|
||||
expect(asDux.reducer).toBeTypeOf('function');
|
||||
|
||||
expect(asDux.actions.actionA()).toMatchObject({ type: 'actionA' });
|
||||
|
||||
expect(asDux.effects).toHaveLength(1);
|
||||
|
||||
expect(asDux.reactions).toHaveLength(1);
|
||||
|
||||
const newDux = new Updux(asDux);
|
||||
|
||||
expect(newDux.initialState).toEqual(13);
|
||||
|
||||
expect(newDux.reducer).toBeTypeOf('function');
|
||||
|
||||
expect(newDux.actions.actionA()).toMatchObject({ type: 'actionA' });
|
||||
|
||||
expect(newDux.effects).toHaveLength(1);
|
||||
|
||||
expect(newDux.reactions).toHaveLength(1);
|
||||
});
|
@ -4,6 +4,7 @@ import * as rtk from '@reduxjs/toolkit';
|
||||
import { DuxConfig, Mutation } from './types.js';
|
||||
import { D } from '@mobily/ts-belt';
|
||||
import Updux from './Updux.js';
|
||||
import { AnyAction } from '@reduxjs/toolkit';
|
||||
|
||||
export type MutationCase = {
|
||||
matcher: (action: rtk.AnyAction) => boolean;
|
||||
@ -16,12 +17,10 @@ export function buildReducer(
|
||||
mutations: MutationCase[] = [],
|
||||
defaultMutation?: Omit<MutationCase, 'matcher'>,
|
||||
subduxes: Record<string, Updux<any>> = {},
|
||||
inheritedReducer?: (state: any, action: AnyAction) => any,
|
||||
) {
|
||||
const subReducers = D.map(subduxes, D.getUnsafe('reducer'));
|
||||
|
||||
// TODO matcherMutation
|
||||
// TODO defaultMutation
|
||||
//
|
||||
const reducer = (state = initialStateState, action: rtk.AnyAction) => {
|
||||
if (!action?.type) throw new Error('reducer called with a bad action');
|
||||
|
||||
@ -30,6 +29,13 @@ export function buildReducer(
|
||||
if (active.length === 0 && defaultMutation)
|
||||
active.push(defaultMutation as any);
|
||||
|
||||
if (!active.some(R.prop<any, any>('terminal')) && inheritedReducer) {
|
||||
active.push({
|
||||
mutation: (_payload, action) => (state) => {
|
||||
return u(state, inheritedReducer(state, action));
|
||||
},
|
||||
} as any);
|
||||
}
|
||||
if (
|
||||
!active.some(R.prop<any, any>('terminal')) &&
|
||||
D.values(subReducers).length > 0
|
||||
@ -49,10 +55,6 @@ export function buildReducer(
|
||||
} as any);
|
||||
}
|
||||
|
||||
// frozen objects don't play well with immer
|
||||
// if (Object.isFrozen(state)) {
|
||||
// state = { ...(state as any) };
|
||||
// }
|
||||
return active.reduce(
|
||||
(state, { mutation }) =>
|
||||
mutation((action as any).payload, action)(state),
|
||||
|
29
src/types.ts
29
src/types.ts
@ -5,6 +5,7 @@ import {
|
||||
Dispatch,
|
||||
MiddlewareAPI,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { EffectMiddleware } from './effects.js';
|
||||
import Updux from './Updux.js';
|
||||
|
||||
export type DuxConfig = Partial<{
|
||||
@ -12,6 +13,9 @@ export type DuxConfig = Partial<{
|
||||
subduxes: Record<string, DuxConfig>;
|
||||
actions: Record<string, ActionCreator<string> | Function>;
|
||||
selectors: Record<string, Function>;
|
||||
effects: EffectMiddleware<any>[];
|
||||
reactions: DuxReaction<any>[];
|
||||
reducer: (state: any, action: AnyAction) => any;
|
||||
}>;
|
||||
|
||||
type UpduxConfig<D> = D extends Updux<infer T> ? T : D;
|
||||
@ -62,6 +66,12 @@ export type DuxActions<D> = ResolveActions<
|
||||
|
||||
export type Subduxes = Record<string, Updux<any> | DuxConfig>;
|
||||
|
||||
export type MutationEntry<S = any> = {
|
||||
terminal: boolean;
|
||||
matcher?: (action: AnyAction) => boolean;
|
||||
mutation: Mutation<AnyAction, S>;
|
||||
};
|
||||
|
||||
export type Mutation<A = AnyAction, S = any> = (
|
||||
payload: A extends {
|
||||
payload: infer P;
|
||||
@ -89,14 +99,16 @@ export type AugmentedMiddlewareAPI<D> = MiddlewareAPI<
|
||||
selectors: DuxSelectors<D>;
|
||||
};
|
||||
|
||||
export type DuxSelectors<D> = ForceResolveObject<(D extends { selectors: infer S } ? S : {}) &
|
||||
export type DuxSelectors<D> = ForceResolveObject<
|
||||
(D extends { selectors: infer S } ? S : {}) &
|
||||
(D extends { subduxes: infer SUB }
|
||||
? UnionToIntersection<
|
||||
Values<{
|
||||
[key in keyof SUB]: RebaseSelectors<key, SUB[key]>;
|
||||
}>
|
||||
>
|
||||
: {})>;
|
||||
: {})
|
||||
>;
|
||||
|
||||
export type UnionToIntersection<U> = (
|
||||
U extends any ? (k: U) => void : never
|
||||
@ -117,9 +129,10 @@ type RebaseSelector<SLICE, S> = SLICE extends string
|
||||
: never;
|
||||
type Values<X> = X[keyof X];
|
||||
|
||||
|
||||
export type DuxReaction<D extends DuxConfig> =
|
||||
(api: AugmentedMiddlewareAPI<D>) => (state: DuxState<D>,
|
||||
previousState: DuxState<D> | undefined,
|
||||
unsubscribe: () => void) => void;
|
||||
|
||||
export type DuxReaction<D extends DuxConfig> = (
|
||||
api: AugmentedMiddlewareAPI<D>,
|
||||
) => (
|
||||
state: DuxState<D>,
|
||||
previousState: DuxState<D> | undefined,
|
||||
unsubscribe: () => void,
|
||||
) => void;
|
||||
|
Loading…
Reference in New Issue
Block a user