selectors and store

This commit is contained in:
Yanick Champoux 2023-03-11 17:10:38 -05:00
parent 347d90267e
commit f322691906
4 changed files with 83 additions and 24 deletions

View File

@ -19,11 +19,18 @@ import { AggregateActions, AggregateSelectors, Dux } from './types.js';
import { buildActions } from './buildActions.js';
import { buildInitial, AggregateState } from './initial.js';
import { buildReducer, MutationCase } from './reducer.js';
import { buildEffectsMiddleware, EffectMiddleware } from './effects.js';
import { augmentGetState, buildEffectsMiddleware, EffectMiddleware } from './effects.js';
import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js';
type MyActionCreator = { type: string } & ((...args: any) => any);
type XSel<R> = R extends Function ? R : () => R;
type CurriedSelector<S> = S extends (...args: any) => infer R ? XSel<R> : never;
type CurriedSelectors<S> = {
[key in keyof S]: CurriedSelector<S[key]>
}
type ResolveAction<
ActionType extends string,
ActionArg extends any,
@ -176,6 +183,8 @@ export default class Updux<
}
}
store.getState = augmentGetState(store.getState, this.selectors);
return store as ToolkitStore<
AggregateState<T_LocalState, T_Subduxes>
> & {
@ -183,6 +192,12 @@ export default class Updux<
ResolveActions<T_LocalActions>,
T_Subduxes
>;
} & {
getState: CurriedSelectors<AggregateSelectors<
T_LocalSelectors,
T_Subduxes,
AggregateState<T_LocalState, T_Subduxes>
>>
};
}
@ -257,3 +272,4 @@ export default class Updux<
}
}

View File

@ -1,31 +1,42 @@
import { mapValues, map, get } from 'lodash-es';
const middlewareFor = (type, middleware) => (api) => (next) => (action) => {
if (type !== '*' && action.type !== type)
return next(action);
if (type !== '*' && action.type !== type) return next(action);
return middleware(api)(next)(action);
};
const sliceMw = (slice, mw) => (api) => {
const getSliceState = () => get(api.getState(), slice);
return mw(Object.assign(Object.assign({}, api), { getState: getSliceState }));
return mw(
Object.assign(Object.assign({}, api), { getState: getSliceState }),
);
};
export const augmentGetState = (getState, selectors) =>
Object.assign(
getState,
mapValues(selectors, (selector) => {
return (...args) => {
let result = selector(api.getState());
if (typeof result === 'function') return result(...args);
return result;
};
}),
);
export function augmentMiddlewareApi(api, actions, selectors) {
const getState = () => api.getState();
const getState = augmentGetState(() => api.getState(), selectors);
const dispatch = (action) => api.dispatch(action);
Object.assign(getState, mapValues(selectors, (selector) => {
return (...args) => {
let result = selector(api.getState());
if (typeof result === 'function')
return result(...args);
return result;
};
}));
Object.assign(dispatch, mapValues(actions, (action) => {
return (...args) => api.dispatch(action(...args));
}));
return Object.assign(Object.assign({}, api), { getState,
Object.assign(
dispatch,
mapValues(actions, (action) => {
return (...args) => api.dispatch(action(...args));
}),
);
return Object.assign(Object.assign({}, api), {
getState,
dispatch,
actions,
selectors });
selectors,
});
}
export const effectToMiddleware = (effect, actions, selectors) => {
let mw = effect;
@ -37,12 +48,23 @@ export const effectToMiddleware = (effect, actions, selectors) => {
}
return (api) => mw(augmentMiddlewareApi(api, actions, selectors));
};
const composeMw = (mws) => (api) => (original_next) => mws.reduceRight((next, mw) => mw(api)(next), original_next);
export function buildMiddleware(effects = [], actions = {}, selectors = {}, sub = {}, wrapper = undefined, dux = undefined) {
let inner = map(sub, ({ middleware }, slice) => slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined).filter((x) => x);
const local = effects.map((effect) => effectToMiddleware(effect, actions, selectors));
const composeMw = (mws) => (api) => (original_next) =>
mws.reduceRight((next, mw) => mw(api)(next), original_next);
export function buildMiddleware(
effects = [],
actions = {},
selectors = {},
sub = {},
wrapper = undefined,
dux = undefined,
) {
let inner = map(sub, ({ middleware }, slice) =>
slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined,
).filter((x) => x);
const local = effects.map((effect) =>
effectToMiddleware(effect, actions, selectors),
);
let mws = [...local, ...inner];
if (wrapper)
mws = wrapper(mws, dux);
if (wrapper) mws = wrapper(mws, dux);
return composeMw(mws);
}

View File

@ -11,6 +11,17 @@ export interface EffectMiddleware<S = any, D extends Dispatch = Dispatch> {
const composeMw = (mws) => (api) => (originalNext) =>
mws.reduceRight((next, mw) => mw(api)(next), originalNext);
export const augmentGetState = (originalGetState, selectors) => {
const getState = () => originalGetState();
for (const s in selectors) {
getState[s] = (...args) => {
let result = selectors[s](originalGetState());
if (typeof result === 'function') return result(...args);
return result;
};
}
return getState;
};
export function buildEffectsMiddleware(
effects = [],
actions = {},

View File

@ -17,6 +17,10 @@ test('basic selectors', () => {
initial: { y: 2 },
selectors: {
getY: ({ y }: { y: number }) => y,
getYPlus:
({ y }) =>
(incr: number) =>
(y + incr) as number,
},
}),
},
@ -29,4 +33,10 @@ test('basic selectors', () => {
expect(foo.selectors.getY(sample)).toBe(3);
expect(foo.selectors.getX(sample)).toBe(4);
expect(foo.selectors.getYPlus(sample)(3)).toBe(6);
const store = foo.createStore();
expect(store.getState.getY()).toBe(2);
expect(store.getState.getYPlus(3)).toBe(5);
});