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