diff --git a/src/Updux.ts b/src/Updux.ts index 8045e73..cd27cca 100644 --- a/src/Updux.ts +++ b/src/Updux.ts @@ -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 extends Function ? R : () => R; +type CurriedSelector = S extends (...args: any) => infer R ? XSel : never; + +type CurriedSelectors = { + [key in keyof S]: CurriedSelector +} + 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 > & { @@ -183,6 +192,12 @@ export default class Updux< ResolveActions, T_Subduxes >; + } & { + getState: CurriedSelectors + >> }; } @@ -257,3 +272,4 @@ export default class Updux< } } + diff --git a/src/buildMiddleware/index.js b/src/buildMiddleware/index.js index 2085412..4e2d78d 100644 --- a/src/buildMiddleware/index.js +++ b/src/buildMiddleware/index.js @@ -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); } diff --git a/src/effects.ts b/src/effects.ts index 489e715..4d76595 100644 --- a/src/effects.ts +++ b/src/effects.ts @@ -11,6 +11,17 @@ export interface EffectMiddleware { 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 = {}, diff --git a/src/selectors.test.ts b/src/selectors.test.ts index 11fe428..d0695b9 100644 --- a/src/selectors.test.ts +++ b/src/selectors.test.ts @@ -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); });