prettier
This commit is contained in:
parent
bb0bc14873
commit
d405c90a0d
@ -2,11 +2,23 @@
|
||||
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
PARENT_BRANCH: main
|
||||
|
||||
tasks:
|
||||
build: tsc
|
||||
|
||||
checks:
|
||||
deps: [test, build]
|
||||
deps: [lint, test, build]
|
||||
|
||||
integrate:
|
||||
deps: [checks]
|
||||
cmds:
|
||||
- git is-clean
|
||||
# did we had tests?
|
||||
- git diff-ls {{.PARENT_BRANCH}} | grep test
|
||||
- git checkout {{.PARENT_BRANCH}}
|
||||
- git weld -
|
||||
|
||||
test: vitest run src
|
||||
test:dev: vitest src
|
||||
|
@ -1,15 +1,7 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import Updux from './Updux';
|
||||
import Updux from './Updux.js';
|
||||
|
||||
test('subdux idempotency', () => {
|
||||
// const c = new Updux({
|
||||
// initialState: 2,
|
||||
// });
|
||||
// const b = new Updux({
|
||||
// subduxes: {
|
||||
// c,
|
||||
// },
|
||||
// });
|
||||
const foo = new Updux({
|
||||
subduxes: {
|
||||
a: new Updux({ initialState: 2 }),
|
||||
@ -18,21 +10,4 @@ test('subdux idempotency', () => {
|
||||
|
||||
let fooState = foo.reducer(undefined, { type: 'noop' });
|
||||
expect(foo.reducer(fooState, { type: 'noop' })).toBe(fooState);
|
||||
|
||||
return;
|
||||
const store = foo.createStore();
|
||||
|
||||
const s1 = store.getState();
|
||||
console.log(s1);
|
||||
|
||||
store.dispatch({ type: 'noop' });
|
||||
const s2 = store.getState();
|
||||
|
||||
expect(s2.a).toBe(s1.a);
|
||||
|
||||
let bState = b.reducer(undefined, { type: 'noop' });
|
||||
expect(b.reducer(bState, { type: 'noop' })).toBe(bState);
|
||||
|
||||
expect(s2.b).toBe(s1.b);
|
||||
expect(s2).toBe(s1);
|
||||
});
|
||||
|
167
src/Updux.ts
167
src/Updux.ts
@ -20,9 +20,14 @@ 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 { augmentGetState, augmentMiddlewareApi, buildEffectsMiddleware, EffectMiddleware } from './effects.js';
|
||||
import {
|
||||
augmentGetState,
|
||||
augmentMiddlewareApi,
|
||||
buildEffectsMiddleware,
|
||||
EffectMiddleware,
|
||||
} from './effects.js';
|
||||
import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js';
|
||||
import prepare from 'immer';
|
||||
import { produce } from 'immer';
|
||||
|
||||
type MyActionCreator = { type: string } & ((...args: any) => any);
|
||||
|
||||
@ -30,8 +35,8 @@ 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]>
|
||||
}
|
||||
[key in keyof S]: CurriedSelector<S[key]>;
|
||||
};
|
||||
|
||||
type ResolveAction<
|
||||
ActionType extends string,
|
||||
@ -40,10 +45,10 @@ type ResolveAction<
|
||||
? ActionArg
|
||||
: ActionArg extends (...args: any) => any
|
||||
? ActionCreatorWithPreparedPayload<
|
||||
Parameters<ActionArg>,
|
||||
ReturnType<ActionArg>,
|
||||
ActionType
|
||||
>
|
||||
Parameters<ActionArg>,
|
||||
ReturnType<ActionArg>,
|
||||
ActionType
|
||||
>
|
||||
: ActionCreatorWithoutPayload<ActionType>;
|
||||
|
||||
type ResolveActions<
|
||||
@ -51,21 +56,24 @@ type ResolveActions<
|
||||
[key: string]: any;
|
||||
},
|
||||
> = {
|
||||
[ActionType in keyof A]: ActionType extends string
|
||||
[ActionType in keyof A]: ActionType extends string
|
||||
? ResolveAction<ActionType, A[ActionType]>
|
||||
: never;
|
||||
};
|
||||
};
|
||||
|
||||
type Reaction<S = any, M extends MiddlewareAPI = MiddlewareAPI> =
|
||||
(api: M) => (state: S, previousState: S, unsubscribe: () => void) => any;
|
||||
type Reaction<S = any, M extends MiddlewareAPI = MiddlewareAPI> = (
|
||||
api: M,
|
||||
) => (state: S, previousState: S, unsubscribe: () => void) => any;
|
||||
|
||||
type AugmentedMiddlewareAPI<S, A, SELECTORS> =
|
||||
MiddlewareAPI<Dispatch<AnyAction>, S> & {
|
||||
dispatch: A,
|
||||
getState: CurriedSelectors<SELECTORS>,
|
||||
actions: A,
|
||||
selectors: SELECTORS,
|
||||
};
|
||||
type AugmentedMiddlewareAPI<S, A, SELECTORS> = MiddlewareAPI<
|
||||
Dispatch<AnyAction>,
|
||||
S
|
||||
> & {
|
||||
dispatch: A;
|
||||
getState: CurriedSelectors<SELECTORS>;
|
||||
actions: A;
|
||||
selectors: SELECTORS;
|
||||
};
|
||||
|
||||
export type Mutation<A extends Action<any> = Action<any>, S = any> = (
|
||||
payload: A extends {
|
||||
@ -134,7 +142,7 @@ export default class Updux<
|
||||
.filter(([slice, { selectors }]) => selectors)
|
||||
.map(([slice, { selectors }]) =>
|
||||
R.mapValues(selectors, (s) => (state = {}) => {
|
||||
return s(state?.[slice])
|
||||
return s(state?.[slice]);
|
||||
}),
|
||||
),
|
||||
);
|
||||
@ -157,25 +165,34 @@ export default class Updux<
|
||||
...Object.entries(this.#subduxes).flatMap(
|
||||
([slice, { effects }]) => {
|
||||
if (!effects) return [];
|
||||
return effects.map(effect => (api) => effect({
|
||||
...api,
|
||||
getState: () => api.getState()[slice],
|
||||
}))
|
||||
}
|
||||
)
|
||||
]
|
||||
return effects.map(
|
||||
(effect) => (api) =>
|
||||
effect({
|
||||
...api,
|
||||
getState: () => api.getState()[slice],
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
get reactions(): any {
|
||||
return [...this.#localReactions,
|
||||
...Object.entries(this.#subduxes).flatMap(
|
||||
([slice, { reactions }]) => reactions.map(
|
||||
(r) => (api, unsub) => r({
|
||||
...api,
|
||||
getState: () => api.getState()[slice],
|
||||
}, unsub)
|
||||
)
|
||||
)
|
||||
return [
|
||||
...this.#localReactions,
|
||||
...Object.entries(this.#subduxes).flatMap(
|
||||
([slice, { reactions }]) =>
|
||||
reactions.map(
|
||||
(r) => (api, unsub) =>
|
||||
r(
|
||||
{
|
||||
...api,
|
||||
getState: () => api.getState()[slice],
|
||||
},
|
||||
unsub,
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@ -192,7 +209,6 @@ export default class Updux<
|
||||
this.selectors,
|
||||
);
|
||||
|
||||
|
||||
const store = configureStore({
|
||||
reducer: this.reducer as Reducer<
|
||||
AggregateState<T_LocalState, T_Subduxes>,
|
||||
@ -208,7 +224,7 @@ export default class Updux<
|
||||
const action = (this.actions as any)[a](...args);
|
||||
dispatch(action);
|
||||
return action;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
store.getState = augmentGetState(store.getState, this.selectors);
|
||||
@ -223,20 +239,16 @@ export default class Updux<
|
||||
(store as any).actions = this.actions;
|
||||
(store as any).selectors = this.selectors;
|
||||
|
||||
|
||||
return store as ToolkitStore<
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
> & AugmentedMiddlewareAPI<
|
||||
AggregateState<T_LocalState, T_Subduxes>,
|
||||
AggregateActions<
|
||||
ResolveActions<T_LocalActions>,
|
||||
T_Subduxes
|
||||
>, AggregateSelectors<
|
||||
T_LocalSelectors,
|
||||
T_Subduxes,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
>
|
||||
>;
|
||||
return store as ToolkitStore<AggregateState<T_LocalState, T_Subduxes>> &
|
||||
AugmentedMiddlewareAPI<
|
||||
AggregateState<T_LocalState, T_Subduxes>,
|
||||
AggregateActions<ResolveActions<T_LocalActions>, T_Subduxes>,
|
||||
AggregateSelectors<
|
||||
T_LocalSelectors,
|
||||
T_Subduxes,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
>
|
||||
>;
|
||||
}
|
||||
|
||||
get selectors(): AggregateSelectors<
|
||||
@ -280,7 +292,7 @@ export default class Updux<
|
||||
matcher = matcher.match;
|
||||
}
|
||||
|
||||
const immerMutation = (...args) => prepare(mutation(...args));
|
||||
const immerMutation = (...args) => produce(mutation(...args));
|
||||
|
||||
this.#localMutations.push({
|
||||
terminal,
|
||||
@ -309,31 +321,27 @@ export default class Updux<
|
||||
this.actions,
|
||||
this.selectors,
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
#localReactions: any[] = [];
|
||||
addReaction(reaction: Reaction<AggregateState<T_LocalState, T_Subduxes>,
|
||||
AugmentedMiddlewareAPI<
|
||||
addReaction(
|
||||
reaction: Reaction<
|
||||
AggregateState<T_LocalState, T_Subduxes>,
|
||||
AggregateActions<
|
||||
ResolveActions<T_LocalActions>,
|
||||
T_Subduxes
|
||||
>, AggregateSelectors<
|
||||
T_LocalSelectors,
|
||||
T_Subduxes,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
AugmentedMiddlewareAPI<
|
||||
AggregateState<T_LocalState, T_Subduxes>,
|
||||
AggregateActions<ResolveActions<T_LocalActions>, T_Subduxes>,
|
||||
AggregateSelectors<
|
||||
T_LocalSelectors,
|
||||
T_Subduxes,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
>
|
||||
>
|
||||
>
|
||||
>) {
|
||||
|
||||
>,
|
||||
) {
|
||||
let previous: any;
|
||||
|
||||
const memoized = (api: any) => {
|
||||
api = augmentMiddlewareApi(api,
|
||||
this.actions,
|
||||
this.selectors
|
||||
);
|
||||
api = augmentMiddlewareApi(api, this.actions, this.selectors);
|
||||
const r = reaction(api);
|
||||
return (unsub: () => void) => {
|
||||
const state = api.getState();
|
||||
@ -341,19 +349,21 @@ export default class Updux<
|
||||
let p = previous;
|
||||
previous = state;
|
||||
r(state, p, unsub);
|
||||
}
|
||||
}
|
||||
;
|
||||
|
||||
};
|
||||
};
|
||||
this.#localReactions.push(memoized);
|
||||
}
|
||||
|
||||
// internal method REMOVE
|
||||
subscribeTo(store, subscription) {
|
||||
const localStore = augmentMiddlewareApi({
|
||||
...store,
|
||||
subscribe: (subscriber) => this.subscribeTo(store, subscriber), // TODO not sure
|
||||
}, this.actions, this.selectors);
|
||||
const localStore = augmentMiddlewareApi(
|
||||
{
|
||||
...store,
|
||||
subscribe: (subscriber) => this.subscribeTo(store, subscriber), // TODO not sure
|
||||
},
|
||||
this.actions,
|
||||
this.selectors,
|
||||
);
|
||||
|
||||
const subscriber = subscription(localStore);
|
||||
|
||||
@ -371,4 +381,3 @@ export default class Updux<
|
||||
return store.subscribe(memoSub);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,28 @@
|
||||
import { map, mapValues, merge } from 'lodash-es';
|
||||
export function buildSelectors(localSelectors, splatSelector = {}, subduxes = {}) {
|
||||
export function buildSelectors(
|
||||
localSelectors,
|
||||
splatSelector = {},
|
||||
subduxes = {},
|
||||
) {
|
||||
const subSelectors = map(subduxes, ({ selectors }, slice) => {
|
||||
if (!selectors)
|
||||
return {};
|
||||
if (slice === '*')
|
||||
return {};
|
||||
if (!selectors) return {};
|
||||
if (slice === '*') return {};
|
||||
return mapValues(selectors, (func) => (state) => func(state[slice]));
|
||||
});
|
||||
let splat = {};
|
||||
for (const name in splatSelector) {
|
||||
splat[name] =
|
||||
(state) => (...args) => {
|
||||
(state) =>
|
||||
(...args) => {
|
||||
const value = splatSelector[name](state)(...args);
|
||||
const res = () => value;
|
||||
return merge(res, mapValues(subduxes['*'].selectors, (selector) => () => selector(value)));
|
||||
return merge(
|
||||
res,
|
||||
mapValues(
|
||||
subduxes['*'].selectors,
|
||||
(selector) => () => selector(value),
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
||||
return merge({}, ...subSelectors, localSelectors, splat);
|
||||
|
@ -39,7 +39,7 @@ test('buildEffectsMiddleware', () => {
|
||||
|
||||
expect(seen).toEqual(0);
|
||||
const dispatch = vi.fn();
|
||||
mw({ getState: () => 'the state', dispatch })(() => { })({
|
||||
mw({ getState: () => 'the state', dispatch })(() => {})({
|
||||
type: 'noop',
|
||||
});
|
||||
expect(seen).toEqual(1);
|
||||
@ -82,7 +82,7 @@ test('subdux', () => {
|
||||
});
|
||||
|
||||
let seen = 0;
|
||||
bar.addEffect((api) => next => action => {
|
||||
bar.addEffect((api) => (next) => (action) => {
|
||||
seen++;
|
||||
expect(api.getState()).toBe('bar state');
|
||||
next(action);
|
||||
@ -93,7 +93,7 @@ test('subdux', () => {
|
||||
loaded: true,
|
||||
},
|
||||
subduxes: {
|
||||
bar
|
||||
bar,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -34,7 +34,9 @@ test('initialState to createStore', () => {
|
||||
initialState,
|
||||
});
|
||||
|
||||
expect(dux.createStore({ initialState: { a: 3, b: 4 } }).getState()).toEqual({
|
||||
expect(
|
||||
dux.createStore({ initialState: { a: 3, b: 4 } }).getState(),
|
||||
).toEqual({
|
||||
a: 3,
|
||||
b: 4,
|
||||
});
|
||||
|
@ -19,5 +19,8 @@ export function buildInitial(localInitial, subduxes) {
|
||||
);
|
||||
}
|
||||
|
||||
return u(localInitial, R.mapValues(subduxes, R.pathOr(['initialState'], {})));
|
||||
return u(
|
||||
localInitial,
|
||||
R.mapValues(subduxes, R.pathOr(['initialState'], {})),
|
||||
);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import * as R from 'remeda';
|
||||
import { Dux } from './types.js';
|
||||
import { Mutation } from './Updux.js';
|
||||
import u from '@yanick/updeep-remeda';
|
||||
import prepare from 'immer';
|
||||
import { produce } from 'immer';
|
||||
|
||||
export type MutationCase = {
|
||||
matcher: (action: Action) => boolean;
|
||||
@ -36,7 +36,7 @@ export function buildReducer(
|
||||
.forEach(({ mutation, terminal: t }) => {
|
||||
if (t) terminal = true;
|
||||
didSomething = true;
|
||||
state = prepare(
|
||||
state = produce(
|
||||
state,
|
||||
mutation((action as any).payload, action),
|
||||
);
|
||||
|
@ -19,8 +19,8 @@ test('basic selectors', () => {
|
||||
getY: ({ y }: { y: number }) => y,
|
||||
getYPlus:
|
||||
({ y }) =>
|
||||
(incr: number) =>
|
||||
(y + incr) as number,
|
||||
(incr: number) =>
|
||||
(y + incr) as number,
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user