add the dist directory
This commit is contained in:
parent
b20334c017
commit
ad72570f49
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,7 +1,6 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
tsconfig.tsbuildinfo
|
tsconfig.tsbuildinfo
|
||||||
**/*.orig
|
**/*.orig
|
||||||
dist
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.nyc_output/
|
.nyc_output/
|
||||||
|
82
dist/Updux.d.ts
vendored
Normal file
82
dist/Updux.d.ts
vendored
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import * as rtk from '@reduxjs/toolkit';
|
||||||
|
import { Action, AnyAction, EnhancedStore, PayloadAction } from '@reduxjs/toolkit';
|
||||||
|
import { AugmentedMiddlewareAPI, DuxActions, DuxConfig, DuxReaction, DuxSelectors, DuxState, Mutation } from './types.js';
|
||||||
|
import { EffectMiddleware } from './effects.js';
|
||||||
|
type CreateStoreOptions<D> = Partial<{
|
||||||
|
preloadedState: DuxState<D>;
|
||||||
|
}>;
|
||||||
|
interface ActionCreator<T extends string, P, A extends Array<any>> {
|
||||||
|
type: T;
|
||||||
|
match: (action: Action<string>) => action is PayloadAction<P, T, never, never>;
|
||||||
|
(...args: A): PayloadAction<P, T, never, never>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description main Updux class
|
||||||
|
*/
|
||||||
|
export default class Updux<D extends DuxConfig> {
|
||||||
|
#private;
|
||||||
|
private readonly duxConfig;
|
||||||
|
constructor(duxConfig: D);
|
||||||
|
/**
|
||||||
|
* @description Initial state of the dux.
|
||||||
|
*/
|
||||||
|
get initialState(): DuxState<D>;
|
||||||
|
get subduxes(): {};
|
||||||
|
get actions(): DuxActions<D>;
|
||||||
|
createStore(options?: CreateStoreOptions<D>): EnhancedStore<DuxState<D>> & AugmentedMiddlewareAPI<D>;
|
||||||
|
addAction(action: ActionCreator<any, any, any>): void;
|
||||||
|
addSelector<R, N extends string>(name: N, selector: (state: DuxState<D>) => R): Updux<D & {
|
||||||
|
selectors: Record<N, (state: DuxState<D>) => R>;
|
||||||
|
}>;
|
||||||
|
addMutation<A extends keyof DuxActions<D>>(actionType: A, mutation: Mutation<DuxActions<D>[A] extends (...args: any[]) => infer R ? R : AnyAction, DuxState<D>>, terminal?: boolean): Updux<D>;
|
||||||
|
addMutation<AC extends rtk.ActionCreatorWithPreparedPayload<any, any, string, never, never>>(actionCreator: AC, mutation: Mutation<ReturnType<AC>, DuxState<D>>, terminal?: boolean): Updux<D & {
|
||||||
|
actions: Record<AC extends {
|
||||||
|
type: infer T;
|
||||||
|
} ? T : never, AC>;
|
||||||
|
}>;
|
||||||
|
addMutation<A extends string, P, X extends Array<any>>(actionCreator: ActionCreator<A, P, X>, mutation: Mutation<ReturnType<ActionCreator<A, P, X>>, DuxState<D>>, terminal?: boolean): Updux<D & {
|
||||||
|
actions: Record<A, ActionCreator<A, P, X>>;
|
||||||
|
}>;
|
||||||
|
addMutation(matcher: (action: AnyAction) => boolean, mutation: Mutation<AnyAction, DuxState<D>>, terminal?: boolean): Updux<D>;
|
||||||
|
setDefaultMutation(mutation: Mutation<any, DuxState<D>>, terminal?: boolean): Updux<D>;
|
||||||
|
get reducer(): any;
|
||||||
|
get selectors(): DuxSelectors<D>;
|
||||||
|
addEffect<A extends keyof DuxActions<D>>(actionType: A, effect: EffectMiddleware<D, DuxActions<D>[A] extends (...args: any[]) => infer R ? R : AnyAction>): Updux<D>;
|
||||||
|
addEffect<AC extends rtk.ActionCreatorWithPreparedPayload<any, any, string, never, never>>(actionCreator: AC, effect: EffectMiddleware<D, ReturnType<AC>>): Updux<D & {
|
||||||
|
actions: Record<AC extends {
|
||||||
|
type: infer T;
|
||||||
|
} ? T : never, AC>;
|
||||||
|
}>;
|
||||||
|
addEffect(guardFunc: (action: AnyAction) => boolean, effect: EffectMiddleware<D>): Updux<D>;
|
||||||
|
addEffect(effect: EffectMiddleware<D>): Updux<D>;
|
||||||
|
get effects(): any[];
|
||||||
|
get upreducer(): (action: AnyAction) => (state?: DuxState<D>) => DuxState<D>;
|
||||||
|
addReaction(reaction: DuxReaction<D>): this;
|
||||||
|
get reactions(): any;
|
||||||
|
get defaultMutation(): {
|
||||||
|
terminal: boolean;
|
||||||
|
mutation: Mutation<any, (D extends {
|
||||||
|
initialState: any;
|
||||||
|
} ? D["initialState"] : {}) & (D extends {
|
||||||
|
subduxes: any;
|
||||||
|
} ? import("./types.js").SubduxesState<D> : unknown) extends infer T ? T extends (D extends {
|
||||||
|
initialState: any;
|
||||||
|
} ? D["initialState"] : {}) & (D extends {
|
||||||
|
subduxes: any;
|
||||||
|
} ? import("./types.js").SubduxesState<D> : unknown) ? T extends {
|
||||||
|
[key: string]: any;
|
||||||
|
} ? { [key in keyof T]: T[key]; } : T : never : never>;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @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>[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export {};
|
210
dist/Updux.js
vendored
Normal file
210
dist/Updux.js
vendored
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
||||||
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
||||||
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
||||||
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
||||||
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
||||||
|
};
|
||||||
|
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
||||||
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
||||||
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
||||||
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
||||||
|
};
|
||||||
|
var _Updux_subduxes, _Updux_memoInitialState, _Updux_memoBuildReducer, _Updux_memoBuildSelectors, _Updux_memoBuildEffects, _Updux_memoBuildReactions, _Updux_actions, _Updux_defaultMutation, _Updux_effects, _Updux_reactions, _Updux_mutations, _Updux_inheritedReducer, _Updux_selectors;
|
||||||
|
import * as rtk from '@reduxjs/toolkit';
|
||||||
|
const { configureStore, } = rtk;
|
||||||
|
import baseMoize from 'moize/mjs/index.mjs';
|
||||||
|
import { buildInitialState } from './initialState.js';
|
||||||
|
import { buildActions } from './actions.js';
|
||||||
|
import { D } from '@mobily/ts-belt';
|
||||||
|
import { buildReducer } from './reducer.js';
|
||||||
|
import { buildSelectors } from './selectors.js';
|
||||||
|
import { buildEffectsMiddleware, buildEffects, augmentMiddlewareApi } from './effects.js';
|
||||||
|
import { augmentGetState, augmentDispatch } from './createStore.js';
|
||||||
|
import { buildReactions } from './reactions.js';
|
||||||
|
const moize = (func) => baseMoize(func, { maxSize: 1 });
|
||||||
|
/**
|
||||||
|
* @description main Updux class
|
||||||
|
*/
|
||||||
|
export default class Updux {
|
||||||
|
constructor(duxConfig) {
|
||||||
|
var _a, _b, _c;
|
||||||
|
this.duxConfig = duxConfig;
|
||||||
|
/** @internal */
|
||||||
|
_Updux_subduxes.set(this, void 0);
|
||||||
|
/** @internal */
|
||||||
|
_Updux_memoInitialState.set(this, moize(buildInitialState));
|
||||||
|
/** @internal */
|
||||||
|
_Updux_memoBuildReducer.set(this, moize(buildReducer));
|
||||||
|
/** @internal */
|
||||||
|
_Updux_memoBuildSelectors.set(this, moize(buildSelectors));
|
||||||
|
/** @internal */
|
||||||
|
_Updux_memoBuildEffects.set(this, moize(buildEffects));
|
||||||
|
/** @internal */
|
||||||
|
_Updux_memoBuildReactions.set(this, moize(buildReactions));
|
||||||
|
/** @internal */
|
||||||
|
_Updux_actions.set(this, {});
|
||||||
|
/** @internal */
|
||||||
|
_Updux_defaultMutation.set(this, void 0);
|
||||||
|
/** @internal */
|
||||||
|
_Updux_effects.set(this, []);
|
||||||
|
/** @internal */
|
||||||
|
_Updux_reactions.set(this, []);
|
||||||
|
/** @internal */
|
||||||
|
_Updux_mutations.set(this, []);
|
||||||
|
/** @internal */
|
||||||
|
_Updux_inheritedReducer.set(this, void 0);
|
||||||
|
/** @internal */
|
||||||
|
_Updux_selectors.set(this, {});
|
||||||
|
if (duxConfig.subduxes)
|
||||||
|
__classPrivateFieldSet(this, _Updux_subduxes, D.map(duxConfig.subduxes, (s) => s instanceof Updux ? s : new Updux(s)), "f");
|
||||||
|
__classPrivateFieldSet(this, _Updux_inheritedReducer, duxConfig.reducer, "f");
|
||||||
|
__classPrivateFieldSet(this, _Updux_effects, (_a = duxConfig.effects) !== null && _a !== void 0 ? _a : [], "f");
|
||||||
|
__classPrivateFieldSet(this, _Updux_reactions, (_b = duxConfig.reactions) !== null && _b !== void 0 ? _b : [], "f");
|
||||||
|
__classPrivateFieldSet(this, _Updux_actions, buildActions(duxConfig.actions, __classPrivateFieldGet(this, _Updux_subduxes, "f")), "f");
|
||||||
|
__classPrivateFieldSet(this, _Updux_selectors, (_c = duxConfig.selectors) !== null && _c !== void 0 ? _c : {}, "f");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description Initial state of the dux.
|
||||||
|
*/
|
||||||
|
get initialState() {
|
||||||
|
return __classPrivateFieldGet(this, _Updux_memoInitialState, "f").call(this, this.duxConfig.initialState, __classPrivateFieldGet(this, _Updux_subduxes, "f"));
|
||||||
|
}
|
||||||
|
get subduxes() {
|
||||||
|
return __classPrivateFieldGet(this, _Updux_subduxes, "f");
|
||||||
|
}
|
||||||
|
get actions() {
|
||||||
|
return __classPrivateFieldGet(this, _Updux_actions, "f");
|
||||||
|
}
|
||||||
|
createStore(options = {}) {
|
||||||
|
const preloadedState = options.preloadedState;
|
||||||
|
const effects = buildEffectsMiddleware(this.effects, this.actions, this.selectors);
|
||||||
|
let middleware = (gdm) => gdm().concat(effects);
|
||||||
|
const store = configureStore({
|
||||||
|
preloadedState,
|
||||||
|
reducer: this.reducer,
|
||||||
|
middleware,
|
||||||
|
});
|
||||||
|
store.dispatch = augmentDispatch(store.dispatch, this.actions);
|
||||||
|
store.getState = augmentGetState(store.getState, this.selectors);
|
||||||
|
store.actions = this.actions;
|
||||||
|
store.selectors = this.selectors;
|
||||||
|
for (const reaction of this.reactions) {
|
||||||
|
let unsub;
|
||||||
|
const r = reaction(store);
|
||||||
|
unsub = store.subscribe(() => r(unsub));
|
||||||
|
}
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
addAction(action) {
|
||||||
|
const { type } = action;
|
||||||
|
if (!__classPrivateFieldGet(this, _Updux_actions, "f")[type]) {
|
||||||
|
__classPrivateFieldSet(this, _Updux_actions, Object.assign(Object.assign({}, __classPrivateFieldGet(this, _Updux_actions, "f")), { [type]: action }), "f");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (__classPrivateFieldGet(this, _Updux_actions, "f")[type] !== action)
|
||||||
|
throw new Error(`redefining action ${type}`);
|
||||||
|
}
|
||||||
|
addSelector(name, selector) {
|
||||||
|
__classPrivateFieldSet(this, _Updux_selectors, Object.assign(Object.assign({}, __classPrivateFieldGet(this, _Updux_selectors, "f")), { [name]: selector }), "f");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
addMutation(actionCreator, mutation, terminal = false) {
|
||||||
|
var _a;
|
||||||
|
if (typeof actionCreator === 'string') {
|
||||||
|
if (!this.actions[actionCreator])
|
||||||
|
throw new Error(`action ${actionCreator} not found`);
|
||||||
|
actionCreator = this.actions[actionCreator];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.addAction(actionCreator);
|
||||||
|
}
|
||||||
|
__classPrivateFieldSet(this, _Updux_mutations, __classPrivateFieldGet(this, _Updux_mutations, "f").concat({
|
||||||
|
terminal,
|
||||||
|
matcher: (_a = actionCreator.match) !== null && _a !== void 0 ? _a : actionCreator,
|
||||||
|
mutation,
|
||||||
|
}), "f");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
setDefaultMutation(mutation, terminal = false) {
|
||||||
|
__classPrivateFieldSet(this, _Updux_defaultMutation, { terminal, mutation }, "f");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
get reducer() {
|
||||||
|
return __classPrivateFieldGet(this, _Updux_memoBuildReducer, "f").call(this, this.initialState, __classPrivateFieldGet(this, _Updux_mutations, "f"), __classPrivateFieldGet(this, _Updux_defaultMutation, "f"), __classPrivateFieldGet(this, _Updux_subduxes, "f"), __classPrivateFieldGet(this, _Updux_inheritedReducer, "f"));
|
||||||
|
}
|
||||||
|
get selectors() {
|
||||||
|
return __classPrivateFieldGet(this, _Updux_memoBuildSelectors, "f").call(this, __classPrivateFieldGet(this, _Updux_selectors, "f"), __classPrivateFieldGet(this, _Updux_subduxes, "f"));
|
||||||
|
}
|
||||||
|
addEffect(...args) {
|
||||||
|
let effect;
|
||||||
|
if (args.length === 1) {
|
||||||
|
effect = args[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let [actionCreator, originalEffect] = args;
|
||||||
|
if (typeof actionCreator === 'string') {
|
||||||
|
if (this.actions[actionCreator]) {
|
||||||
|
actionCreator = this.actions[actionCreator];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error(`action '${actionCreator}' is unknown`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!this.actions[actionCreator.type])
|
||||||
|
this.addAction(actionCreator);
|
||||||
|
const test = actionCreator.hasOwnProperty('match')
|
||||||
|
? actionCreator.match
|
||||||
|
: actionCreator;
|
||||||
|
effect = (api) => (next) => {
|
||||||
|
const e = originalEffect(api)(next);
|
||||||
|
return (action) => {
|
||||||
|
const func = test(action) ? e : next;
|
||||||
|
return func(action);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
__classPrivateFieldSet(this, _Updux_effects, __classPrivateFieldGet(this, _Updux_effects, "f").concat(effect), "f");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
get effects() {
|
||||||
|
return __classPrivateFieldGet(this, _Updux_memoBuildEffects, "f").call(this, __classPrivateFieldGet(this, _Updux_effects, "f"), __classPrivateFieldGet(this, _Updux_subduxes, "f"));
|
||||||
|
}
|
||||||
|
get upreducer() {
|
||||||
|
return (action) => (state) => this.reducer(state, action);
|
||||||
|
}
|
||||||
|
addReaction(reaction) {
|
||||||
|
let previous;
|
||||||
|
const memoized = (api) => {
|
||||||
|
api = augmentMiddlewareApi(api, this.actions, this.selectors);
|
||||||
|
const r = reaction(api);
|
||||||
|
const rMemoized = (localState, unsub) => {
|
||||||
|
let p = previous;
|
||||||
|
previous = localState;
|
||||||
|
r(localState, p, unsub);
|
||||||
|
};
|
||||||
|
return (unsub) => rMemoized(api.getState(), unsub);
|
||||||
|
};
|
||||||
|
__classPrivateFieldSet(this, _Updux_reactions, __classPrivateFieldGet(this, _Updux_reactions, "f").concat(memoized), "f");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
get reactions() {
|
||||||
|
return __classPrivateFieldGet(this, _Updux_memoBuildReactions, "f").call(this, __classPrivateFieldGet(this, _Updux_reactions, "f"), __classPrivateFieldGet(this, _Updux_subduxes, "f"));
|
||||||
|
}
|
||||||
|
get defaultMutation() {
|
||||||
|
return __classPrivateFieldGet(this, _Updux_defaultMutation, "f");
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @description Returns an object holding the Updux reducer and all its
|
||||||
|
* paraphernalia.
|
||||||
|
*/
|
||||||
|
get asDux() {
|
||||||
|
return {
|
||||||
|
initialState: this.initialState,
|
||||||
|
actions: this.actions,
|
||||||
|
effects: this.effects,
|
||||||
|
reactions: this.reactions,
|
||||||
|
reducer: this.reducer,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_Updux_subduxes = new WeakMap(), _Updux_memoInitialState = new WeakMap(), _Updux_memoBuildReducer = new WeakMap(), _Updux_memoBuildSelectors = new WeakMap(), _Updux_memoBuildEffects = new WeakMap(), _Updux_memoBuildReactions = new WeakMap(), _Updux_actions = new WeakMap(), _Updux_defaultMutation = new WeakMap(), _Updux_effects = new WeakMap(), _Updux_reactions = new WeakMap(), _Updux_mutations = new WeakMap(), _Updux_inheritedReducer = new WeakMap(), _Updux_selectors = new WeakMap();
|
11
dist/Updux.test.js
vendored
Normal file
11
dist/Updux.test.js
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
test('subdux idempotency', () => {
|
||||||
|
const foo = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
a: new Updux({ initialState: 2 }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let fooState = foo.reducer(undefined, { type: 'noop' });
|
||||||
|
expect(foo.reducer(fooState, { type: 'noop' })).toBe(fooState);
|
||||||
|
});
|
16
dist/actions.d.ts
vendored
Normal file
16
dist/actions.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { DuxActions, DuxConfig, Subduxes } from './types.js';
|
||||||
|
export { createAction } from '@reduxjs/toolkit';
|
||||||
|
export declare function withPayload<P>(): (input: P) => {
|
||||||
|
payload: P;
|
||||||
|
};
|
||||||
|
export declare function withPayload<P, A extends any[]>(prepare: (...args: A) => P): (...input: A) => {
|
||||||
|
payload: P;
|
||||||
|
};
|
||||||
|
export declare function buildActions<L extends DuxConfig['actions']>(localActions: L): DuxActions<{
|
||||||
|
actions: L;
|
||||||
|
}>;
|
||||||
|
export declare function buildActions<L extends DuxActions<any>, S extends Subduxes>(localActions: L, subduxes: S): DuxActions<{
|
||||||
|
actions: L;
|
||||||
|
subduxes: S;
|
||||||
|
}>;
|
||||||
|
export declare function expandAction(prepare: any, actionType?: string): any;
|
40
dist/actions.js
vendored
Normal file
40
dist/actions.js
vendored
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
import { D } from '@mobily/ts-belt';
|
||||||
|
export { createAction } from '@reduxjs/toolkit';
|
||||||
|
export function withPayload(prepare = (input) => input) {
|
||||||
|
return (...input) => ({
|
||||||
|
payload: prepare.apply(null, input),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function buildActions(localActions = {}, subduxes = {}) {
|
||||||
|
localActions = D.mapWithKey(localActions, (key, value) => expandAction(value, String(key)));
|
||||||
|
let actions = {};
|
||||||
|
for (const slice in subduxes) {
|
||||||
|
const subdux = subduxes[slice].actions;
|
||||||
|
if (!subdux)
|
||||||
|
continue;
|
||||||
|
for (const a in subdux) {
|
||||||
|
if (actions[a] && subduxes[actions[a]].actions[a] !== subdux[a]) {
|
||||||
|
throw new Error(`action '${a}' defined both in subduxes '${actions[a]}' and '${slice}'`);
|
||||||
|
}
|
||||||
|
actions[a] = slice;
|
||||||
|
}
|
||||||
|
for (const a in localActions) {
|
||||||
|
if (actions[a]) {
|
||||||
|
throw new Error(`action '${a}' defined both locally and in subdux '${actions[a]}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
localActions,
|
||||||
|
...D.values(subduxes).map((s) => { var _a; return (_a = s.actions) !== null && _a !== void 0 ? _a : {}; }),
|
||||||
|
].reduce(D.merge);
|
||||||
|
}
|
||||||
|
export function expandAction(prepare, actionType) {
|
||||||
|
if (typeof prepare === 'function' && prepare.type)
|
||||||
|
return prepare;
|
||||||
|
if (typeof prepare === 'function')
|
||||||
|
return createAction(actionType, withPayload(prepare));
|
||||||
|
if (actionType)
|
||||||
|
return createAction(actionType);
|
||||||
|
}
|
1
dist/actions.test.d.ts
vendored
Normal file
1
dist/actions.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
74
dist/actions.test.js
vendored
Normal file
74
dist/actions.test.js
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { buildActions, createAction, withPayload } from './actions.js';
|
||||||
|
import Updux from './index.js';
|
||||||
|
test('basic action', () => {
|
||||||
|
const foo = createAction('foo', withPayload((thing) => ({ thing })));
|
||||||
|
expect(foo('bar')).toEqual({
|
||||||
|
type: 'foo',
|
||||||
|
payload: {
|
||||||
|
thing: 'bar',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expectTypeOf(foo).parameters.toMatchTypeOf();
|
||||||
|
expectTypeOf(foo).returns.toMatchTypeOf();
|
||||||
|
});
|
||||||
|
test('withPayload, just the type', () => {
|
||||||
|
const foo = createAction('foo', withPayload());
|
||||||
|
expect(foo('bar')).toEqual({
|
||||||
|
type: 'foo',
|
||||||
|
payload: 'bar',
|
||||||
|
});
|
||||||
|
expectTypeOf(foo).parameters.toMatchTypeOf();
|
||||||
|
expectTypeOf(foo).returns.toMatchTypeOf();
|
||||||
|
});
|
||||||
|
test('buildActions', () => {
|
||||||
|
const actions = buildActions({
|
||||||
|
one: createAction('one'),
|
||||||
|
two: (x) => x,
|
||||||
|
withoutValue: null,
|
||||||
|
}, { a: { actions: buildActions({ three: () => 3 }) } });
|
||||||
|
expect(actions.one()).toEqual({
|
||||||
|
type: 'one',
|
||||||
|
});
|
||||||
|
expectTypeOf(actions.one()).toMatchTypeOf();
|
||||||
|
expect(actions.two('potato')).toEqual({ type: 'two', payload: 'potato' });
|
||||||
|
expectTypeOf(actions.two('potato')).toMatchTypeOf();
|
||||||
|
expect(actions.three()).toEqual({ type: 'three', payload: 3 });
|
||||||
|
expectTypeOf(actions.three()).toMatchTypeOf();
|
||||||
|
expect(actions.withoutValue()).toEqual({ type: 'withoutValue' });
|
||||||
|
expectTypeOf(actions.withoutValue()).toMatchTypeOf();
|
||||||
|
});
|
||||||
|
describe('Updux interactions', () => {
|
||||||
|
var _a;
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: { a: 3 },
|
||||||
|
actions: {
|
||||||
|
add: (x) => x,
|
||||||
|
withNull: null,
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
subdux1: new Updux({
|
||||||
|
actions: {
|
||||||
|
fromSubdux: (x) => x,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
subdux2: {
|
||||||
|
actions: {
|
||||||
|
ohmy: () => { },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
test('actions getter', () => {
|
||||||
|
expect(dux.actions.add).toBeTruthy();
|
||||||
|
});
|
||||||
|
expectTypeOf(dux.actions.withNull).toMatchTypeOf();
|
||||||
|
expect(dux.actions.withNull()).toMatchObject({
|
||||||
|
type: 'withNull',
|
||||||
|
});
|
||||||
|
expectTypeOf(dux.actions).toMatchTypeOf();
|
||||||
|
expectTypeOf((_a = dux.actions) === null || _a === void 0 ? void 0 : _a.add).not.toEqualTypeOf();
|
||||||
|
expect(dux.actions.fromSubdux('payload')).toEqual({
|
||||||
|
type: 'fromSubdux',
|
||||||
|
payload: 'payload',
|
||||||
|
});
|
||||||
|
});
|
1
dist/asDux.test.d.ts
vendored
Normal file
1
dist/asDux.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
24
dist/asDux.test.js
vendored
Normal file
24
dist/asDux.test.js
vendored
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import Updux, { createAction } from './index.js';
|
||||||
|
test('asDux', () => {
|
||||||
|
const actionA = createAction('actionA');
|
||||||
|
const defaultMutation = () => (state) => 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);
|
||||||
|
});
|
34
dist/buildActions.js
vendored
Normal file
34
dist/buildActions.js
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
import * as R from 'remeda';
|
||||||
|
import { withPayload } from './actions.js';
|
||||||
|
function resolveActions(configActions) {
|
||||||
|
return R.mapValues(configActions, (prepare, type) => {
|
||||||
|
if (typeof prepare === 'function' && prepare.type)
|
||||||
|
return prepare;
|
||||||
|
return createAction(type, withPayload(prepare));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
export function buildActions(localActions, subduxes) {
|
||||||
|
localActions = resolveActions(localActions);
|
||||||
|
let actions = {};
|
||||||
|
for (const slice in subduxes) {
|
||||||
|
const subdux = subduxes[slice].actions;
|
||||||
|
if (!subdux)
|
||||||
|
continue;
|
||||||
|
for (const a in subdux) {
|
||||||
|
if (actions[a] && subduxes[actions[a]].actions[a] !== subdux[a]) {
|
||||||
|
throw new Error(`action '${a}' defined both in subduxes '${actions[a]}' and '${slice}'`);
|
||||||
|
}
|
||||||
|
actions[a] = slice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const a in localActions) {
|
||||||
|
if (actions[a]) {
|
||||||
|
throw new Error(`action '${a}' defined both locally and in subdux '${actions[a]}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return R.mergeAll([
|
||||||
|
localActions,
|
||||||
|
...Object.values(subduxes).map(R.pathOr(['actions'], {})),
|
||||||
|
]);
|
||||||
|
}
|
8
dist/buildInitial.js
vendored
Normal file
8
dist/buildInitial.js
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import * as R from 'remeda';
|
||||||
|
export function buildInitial(localInitial, subduxes) {
|
||||||
|
if (Object.keys(subduxes).length > 0 && typeof localInitial !== 'object') {
|
||||||
|
throw new Error("can't have subduxes when the initial value is not an object");
|
||||||
|
}
|
||||||
|
return u(localInitial, R.mapValues(subduxes, R.pathOr(['initial'], {})));
|
||||||
|
}
|
14
dist/buildInitial.test.js
vendored
Normal file
14
dist/buildInitial.test.js
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import { buildInitial } from './initial.js';
|
||||||
|
test('basic', () => {
|
||||||
|
expect(buildInitial({ a: 1 }, { b: { initialState: { c: 2 } }, d: { initialState: 'e' } })).toEqual({
|
||||||
|
a: 1,
|
||||||
|
b: { c: 2 },
|
||||||
|
d: 'e',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('throw if subduxes and initialState is not an object', () => {
|
||||||
|
expect(() => {
|
||||||
|
buildInitial(3, { bar: 'foo' });
|
||||||
|
}).toThrow();
|
||||||
|
});
|
1
dist/buildInitialState.d.ts
vendored
Normal file
1
dist/buildInitialState.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export declare function buildInitialState(localInitialState: any, subduxes: any): any;
|
12
dist/buildInitialState.js
vendored
Normal file
12
dist/buildInitialState.js
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import { D } from '@mobily/ts-belt';
|
||||||
|
export function buildInitialState(localInitialState, subduxes) {
|
||||||
|
let state = localInitialState !== null && localInitialState !== void 0 ? localInitialState : {};
|
||||||
|
if (subduxes) {
|
||||||
|
if (typeof state !== 'object') {
|
||||||
|
throw new Error('root initial state is not an object');
|
||||||
|
}
|
||||||
|
state = u(state, D.map(subduxes, D.prop('initialState')));
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
48
dist/buildMiddleware/index.js
vendored
Normal file
48
dist/buildMiddleware/index.js
vendored
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { mapValues, map, get } from 'lodash-es';
|
||||||
|
const middlewareFor = (type, middleware) => (api) => (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 }));
|
||||||
|
};
|
||||||
|
export function augmentMiddlewareApi(api, actions, selectors) {
|
||||||
|
const getState = () => api.getState();
|
||||||
|
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,
|
||||||
|
dispatch,
|
||||||
|
actions,
|
||||||
|
selectors });
|
||||||
|
}
|
||||||
|
export const effectToMiddleware = (effect, actions, selectors) => {
|
||||||
|
let mw = effect;
|
||||||
|
let action = '*';
|
||||||
|
if (Array.isArray(effect)) {
|
||||||
|
action = effect[0];
|
||||||
|
mw = effect[1];
|
||||||
|
mw = middlewareFor(action, mw);
|
||||||
|
}
|
||||||
|
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));
|
||||||
|
let mws = [...local, ...inner];
|
||||||
|
if (wrapper)
|
||||||
|
mws = wrapper(mws, dux);
|
||||||
|
return composeMw(mws);
|
||||||
|
}
|
20
dist/buildSelectors/index.js
vendored
Normal file
20
dist/buildSelectors/index.js
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { map, mapValues, merge } from 'lodash-es';
|
||||||
|
export function buildSelectors(localSelectors, splatSelector = {}, subduxes = {}) {
|
||||||
|
const subSelectors = map(subduxes, ({ selectors }, slice) => {
|
||||||
|
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) => {
|
||||||
|
const value = splatSelector[name](state)(...args);
|
||||||
|
const res = () => value;
|
||||||
|
return merge(res, mapValues(subduxes['*'].selectors, (selector) => () => selector(value)));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return merge({}, ...subSelectors, localSelectors, splat);
|
||||||
|
}
|
2
dist/createStore.d.ts
vendored
Normal file
2
dist/createStore.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export declare function augmentGetState(originalGetState: any, selectors: any): () => any;
|
||||||
|
export declare function augmentDispatch(dispatch: any, actions: any): any;
|
22
dist/createStore.js
vendored
Normal file
22
dist/createStore.js
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export function 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 augmentDispatch(dispatch, actions) {
|
||||||
|
for (const a in actions) {
|
||||||
|
dispatch[a] = (...args) => {
|
||||||
|
const action = actions[a](...args);
|
||||||
|
dispatch(action);
|
||||||
|
return action;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return dispatch;
|
||||||
|
}
|
1
dist/createStore.test.d.ts
vendored
Normal file
1
dist/createStore.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
25
dist/createStore.test.js
vendored
Normal file
25
dist/createStore.test.js
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import Updux, { createAction, withPayload } from './index.js';
|
||||||
|
const incr = createAction('incr', withPayload());
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: 1,
|
||||||
|
// selectors: {
|
||||||
|
// double: (x: string) => x + x,
|
||||||
|
// },
|
||||||
|
}).addMutation(incr, (i, action) => state => {
|
||||||
|
expectTypeOf(i).toEqualTypeOf();
|
||||||
|
expectTypeOf(state).toEqualTypeOf();
|
||||||
|
expectTypeOf(action).toEqualTypeOf();
|
||||||
|
return state + i;
|
||||||
|
});
|
||||||
|
suite.only('store dispatch actions', () => {
|
||||||
|
const store = dux.createStore();
|
||||||
|
test('dispatch actions', () => {
|
||||||
|
expect(store.dispatch.incr).toBeTypeOf('function');
|
||||||
|
expectTypeOf(store.dispatch.incr).toMatchTypeOf();
|
||||||
|
});
|
||||||
|
test('reducer does something', () => {
|
||||||
|
store.dispatch.incr(7);
|
||||||
|
expect(store.getState()).toEqual(8);
|
||||||
|
});
|
||||||
|
});
|
21
dist/effe.ts.2023-08-18.js
vendored
Normal file
21
dist/effe.ts.2023-08-18.js
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const composeMw = (mws) => (api) => (originalNext) => mws.reduceRight((next, mw) => mw(api)(next), originalNext);
|
||||||
|
const augmentDispatch = (originalDispatch, actions) => {
|
||||||
|
const dispatch = (action) => originalDispatch(action);
|
||||||
|
for (const a in actions) {
|
||||||
|
dispatch[a] = (...args) => dispatch(actions[a](...args));
|
||||||
|
}
|
||||||
|
return dispatch;
|
||||||
|
};
|
||||||
|
export const augmentMiddlewareApi = (api, actions, selectors) => {
|
||||||
|
return Object.assign(Object.assign({}, api), { getState: augmentGetState(api.getState, selectors), dispatch: augmentDispatch(api.dispatch, actions), actions,
|
||||||
|
selectors });
|
||||||
|
};
|
||||||
|
export function buildEffectsMiddleware(effects = [], actions = {}, selectors = {}) {
|
||||||
|
return (api) => {
|
||||||
|
const newApi = augmentMiddlewareApi(api, actions, selectors);
|
||||||
|
let mws = effects.map((e) => e(newApi));
|
||||||
|
return (originalNext) => {
|
||||||
|
return mws.reduceRight((next, mw) => mw(next), originalNext);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
119
dist/effe.ts.2023-08-18.test.js
vendored
Normal file
119
dist/effe.ts.2023-08-18.test.js
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import { buildEffectsMiddleware } from './effects.js';
|
||||||
|
import Updux, { createAction } from './index.js';
|
||||||
|
test('buildEffectsMiddleware', () => {
|
||||||
|
let seen = 0;
|
||||||
|
const mw = buildEffectsMiddleware([
|
||||||
|
(api) => (next) => (action) => {
|
||||||
|
seen++;
|
||||||
|
expect(api).toHaveProperty('getState');
|
||||||
|
expect(api.getState).toBeTypeOf('function');
|
||||||
|
expect(api.getState()).toEqual('the state');
|
||||||
|
expect(action).toHaveProperty('type');
|
||||||
|
expect(next).toBeTypeOf('function');
|
||||||
|
expect(api).toHaveProperty('actions');
|
||||||
|
expect(api.actions.action1()).toHaveProperty('type', 'action1');
|
||||||
|
api.dispatch.action1();
|
||||||
|
expect(api.selectors.getFoo(2)).toBe(2);
|
||||||
|
expect(api.getState.getFoo()).toBe('the state');
|
||||||
|
expect(api.getState.getBar(2)).toBe('the state2');
|
||||||
|
next(action);
|
||||||
|
},
|
||||||
|
], {
|
||||||
|
action1: createAction('action1'),
|
||||||
|
}, {
|
||||||
|
getFoo: (state) => state,
|
||||||
|
getBar: (state) => (i) => state + i,
|
||||||
|
});
|
||||||
|
expect(seen).toEqual(0);
|
||||||
|
const dispatch = vi.fn();
|
||||||
|
mw({ getState: () => 'the state', dispatch })(() => { })({
|
||||||
|
type: 'noop',
|
||||||
|
});
|
||||||
|
expect(seen).toEqual(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'action1' });
|
||||||
|
});
|
||||||
|
test('basic', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
loaded: true,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
foo: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let seen = 0;
|
||||||
|
dux.addEffect((api) => (next) => (action) => {
|
||||||
|
seen++;
|
||||||
|
expect(api).toHaveProperty('getState');
|
||||||
|
expect(api.getState()).toHaveProperty('loaded');
|
||||||
|
expect(action).toHaveProperty('type');
|
||||||
|
expect(next).toBeTypeOf('function');
|
||||||
|
next(action);
|
||||||
|
});
|
||||||
|
const store = dux.createStore();
|
||||||
|
expect(seen).toEqual(0);
|
||||||
|
store.dispatch.foo();
|
||||||
|
expect(seen).toEqual(1);
|
||||||
|
});
|
||||||
|
test('subdux', () => {
|
||||||
|
const bar = new Updux({
|
||||||
|
initialState: 'bar state',
|
||||||
|
actions: { foo: 0 },
|
||||||
|
});
|
||||||
|
let seen = 0;
|
||||||
|
bar.addEffect((api) => (next) => (action) => {
|
||||||
|
seen++;
|
||||||
|
expect(api.getState()).toBe('bar state');
|
||||||
|
next(action);
|
||||||
|
});
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
loaded: true,
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
bar,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const store = dux.createStore();
|
||||||
|
expect(seen).toEqual(0);
|
||||||
|
store.dispatch.foo();
|
||||||
|
expect(seen).toEqual(1);
|
||||||
|
});
|
||||||
|
test('addEffect with actionCreator', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
actions: {
|
||||||
|
foo: null,
|
||||||
|
bar: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const next = vi.fn();
|
||||||
|
const spy = vi.fn();
|
||||||
|
const mw = dux.addEffect(dux.actions.foo, (api) => (next) => (action) => next(spy(action)));
|
||||||
|
mw({})(next)(dux.actions.bar());
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
next.mockReset();
|
||||||
|
mw({})(next)(dux.actions.foo());
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
test('addEffect with function', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
actions: {
|
||||||
|
foo: () => { },
|
||||||
|
bar: () => { },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const next = vi.fn();
|
||||||
|
const spy = vi.fn();
|
||||||
|
const mw = dux.addEffect((action) => action.type[0] === 'f', (api) => (next) => (action) => next(spy(action)));
|
||||||
|
mw({})(next)(dux.actions.bar());
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
next.mockReset();
|
||||||
|
mw({})(next)(dux.actions.foo());
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
// TODO subdux effects
|
||||||
|
// TODO allow to subscribe / unsubscribe effects?
|
8
dist/effects.d.ts
vendored
Normal file
8
dist/effects.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import * as rtk from '@reduxjs/toolkit';
|
||||||
|
import { AugmentedMiddlewareAPI } from './types.js';
|
||||||
|
export interface EffectMiddleware<D, A = rtk.AnyAction> {
|
||||||
|
(api: AugmentedMiddlewareAPI<D>): (next: rtk.Dispatch<rtk.AnyAction>) => (action: A) => any;
|
||||||
|
}
|
||||||
|
export declare function buildEffects(localEffects: any, subduxes?: {}): any[];
|
||||||
|
export declare function buildEffectsMiddleware(effects?: any[], actions?: {}, selectors?: {}): (api: any) => (originalNext: any) => any;
|
||||||
|
export declare const augmentMiddlewareApi: (api: any, actions: any, selectors: any) => any;
|
31
dist/effects.js
vendored
Normal file
31
dist/effects.js
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { augmentGetState } from './createStore.js';
|
||||||
|
export function buildEffects(localEffects, subduxes = {}) {
|
||||||
|
return [
|
||||||
|
...localEffects,
|
||||||
|
...Object.entries(subduxes).flatMap(([slice, { effects, actions, selectors }]) => {
|
||||||
|
if (!effects)
|
||||||
|
return [];
|
||||||
|
return effects.map((effect) => (api) => effect(augmentMiddlewareApi(Object.assign(Object.assign({}, api), { getState: () => api.getState()[slice] }), actions, selectors)));
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
export function buildEffectsMiddleware(effects = [], actions = {}, selectors = {}) {
|
||||||
|
return (api) => {
|
||||||
|
const newApi = augmentMiddlewareApi(api, actions, selectors);
|
||||||
|
let mws = effects.map((e) => e(newApi));
|
||||||
|
return (originalNext) => {
|
||||||
|
return mws.reduceRight((next, mw) => mw(next), originalNext);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
export const augmentMiddlewareApi = (api, actions, selectors) => {
|
||||||
|
return Object.assign(Object.assign({}, api), { getState: augmentGetState(api.getState, selectors), dispatch: augmentDispatch(api.dispatch, actions), actions,
|
||||||
|
selectors });
|
||||||
|
};
|
||||||
|
const augmentDispatch = (originalDispatch, actions) => {
|
||||||
|
const dispatch = (action) => originalDispatch(action);
|
||||||
|
for (const a in actions) {
|
||||||
|
dispatch[a] = (...args) => dispatch(actions[a](...args));
|
||||||
|
}
|
||||||
|
return dispatch;
|
||||||
|
};
|
1
dist/effects.test.d.ts
vendored
Normal file
1
dist/effects.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
195
dist/effects.test.js
vendored
Normal file
195
dist/effects.test.js
vendored
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
import { buildEffectsMiddleware } from './effects.js';
|
||||||
|
import { createAction, withPayload } from './index.js';
|
||||||
|
test('addEffect signatures', () => {
|
||||||
|
const someAction = createAction('someAction', withPayload());
|
||||||
|
const dux = new Updux({
|
||||||
|
actions: {
|
||||||
|
someAction,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dux.addEffect((api) => (next) => (action) => {
|
||||||
|
expectTypeOf(action).toMatchTypeOf();
|
||||||
|
expectTypeOf(next).toMatchTypeOf();
|
||||||
|
expectTypeOf(api).toMatchTypeOf();
|
||||||
|
});
|
||||||
|
dux.addEffect('someAction', (api) => (next) => (action) => {
|
||||||
|
expectTypeOf(action).toMatchTypeOf();
|
||||||
|
expectTypeOf(next).toMatchTypeOf();
|
||||||
|
expectTypeOf(api).toMatchTypeOf();
|
||||||
|
});
|
||||||
|
dux.addEffect(someAction, (api) => (next) => (action) => {
|
||||||
|
expectTypeOf(action).toMatchTypeOf();
|
||||||
|
expectTypeOf(next).toMatchTypeOf();
|
||||||
|
expectTypeOf(api).toMatchTypeOf();
|
||||||
|
});
|
||||||
|
dux.addEffect((action) => (action === null || action === void 0 ? void 0 : action.payload) === 3, (api) => (next) => (action) => {
|
||||||
|
expectTypeOf(action).toMatchTypeOf();
|
||||||
|
expectTypeOf(next).toMatchTypeOf();
|
||||||
|
expectTypeOf(api).toMatchTypeOf();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('buildEffectsMiddleware', () => {
|
||||||
|
let seen = 0;
|
||||||
|
const mw = buildEffectsMiddleware([
|
||||||
|
(api) => (next) => (action) => {
|
||||||
|
seen++;
|
||||||
|
expect(api).toHaveProperty('getState');
|
||||||
|
expect(api.getState).toBeTypeOf('function');
|
||||||
|
expect(api.getState()).toEqual('the state');
|
||||||
|
expect(action).toHaveProperty('type');
|
||||||
|
expect(next).toBeTypeOf('function');
|
||||||
|
expect(api).toHaveProperty('actions');
|
||||||
|
expect(api.actions.action1()).toHaveProperty('type', 'action1');
|
||||||
|
api.dispatch.action1();
|
||||||
|
expect(api.selectors.getFoo(2)).toBe(2);
|
||||||
|
expect(api.getState.getFoo()).toBe('the state');
|
||||||
|
expect(api.getState.getBar(2)).toBe('the state2');
|
||||||
|
next(action);
|
||||||
|
},
|
||||||
|
], {
|
||||||
|
action1: createAction('action1'),
|
||||||
|
}, {
|
||||||
|
getFoo: (state) => state,
|
||||||
|
getBar: (state) => (i) => state + i,
|
||||||
|
});
|
||||||
|
expect(seen).toEqual(0);
|
||||||
|
const dispatch = vi.fn();
|
||||||
|
mw({ getState: () => 'the state', dispatch })(() => { })({
|
||||||
|
type: 'noop',
|
||||||
|
});
|
||||||
|
expect(seen).toEqual(1);
|
||||||
|
expect(dispatch).toHaveBeenCalledWith({ type: 'action1' });
|
||||||
|
});
|
||||||
|
test('basic', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
loaded: true,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
foo: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let seen = 0;
|
||||||
|
dux.addEffect((api) => (next) => (action) => {
|
||||||
|
seen++;
|
||||||
|
expect(api).toHaveProperty('getState');
|
||||||
|
expect(api.getState()).toHaveProperty('loaded');
|
||||||
|
expect(action).toHaveProperty('type');
|
||||||
|
expect(next).toBeTypeOf('function');
|
||||||
|
next(action);
|
||||||
|
});
|
||||||
|
const store = dux.createStore();
|
||||||
|
expect(seen).toEqual(0);
|
||||||
|
store.dispatch.foo();
|
||||||
|
expect(seen).toEqual(1);
|
||||||
|
});
|
||||||
|
test('subdux', () => {
|
||||||
|
const bar = new Updux({
|
||||||
|
initialState: 'bar state',
|
||||||
|
actions: { foo: null },
|
||||||
|
});
|
||||||
|
let seen = 0;
|
||||||
|
bar.addEffect((api) => (next) => (action) => {
|
||||||
|
seen++;
|
||||||
|
expect(api.getState()).toBe('bar state');
|
||||||
|
next(action);
|
||||||
|
});
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
loaded: true,
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
bar,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const store = dux.createStore();
|
||||||
|
expect(seen).toEqual(0);
|
||||||
|
store.dispatch.foo();
|
||||||
|
expect(seen).toEqual(1);
|
||||||
|
});
|
||||||
|
test('addEffect with actionCreator', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
actions: {
|
||||||
|
foo: null,
|
||||||
|
bar: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const next = vi.fn();
|
||||||
|
const spy = vi.fn();
|
||||||
|
const [mw] = dux.addEffect(dux.actions.foo, (api) => (next) => (action) => next(spy(action))).effects;
|
||||||
|
mw({})(next)(dux.actions.bar());
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
next.mockReset();
|
||||||
|
mw({})(next)(dux.actions.foo());
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
test('addEffect with function', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
actions: {
|
||||||
|
foo: () => { },
|
||||||
|
bar: () => { },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const next = vi.fn();
|
||||||
|
const spy = vi.fn();
|
||||||
|
const [mw] = dux.addEffect((action) => action.type[0] === 'f', (api) => (next) => (action) => next(spy(action))).effects;
|
||||||
|
mw({})(next)(dux.actions.bar());
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
next.mockReset();
|
||||||
|
mw({})(next)(dux.actions.foo());
|
||||||
|
expect(next).toHaveBeenCalled();
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
test('catchall addEffect', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
a: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const spy = vi.fn();
|
||||||
|
dux.addEffect((api) => (next) => (action) => {
|
||||||
|
expectTypeOf(api.getState()).toMatchTypeOf();
|
||||||
|
spy();
|
||||||
|
next(action);
|
||||||
|
});
|
||||||
|
const store = dux.createStore();
|
||||||
|
expect(spy).not.toHaveBeenCalled();
|
||||||
|
store.dispatch({ type: 'noop' });
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
test('addEffect with unknown actionCreator adds it', () => {
|
||||||
|
const foo = createAction('foo');
|
||||||
|
const dux = new Updux({}).addEffect(foo, () => () => () => { });
|
||||||
|
expectTypeOf(dux.actions.foo).toMatchTypeOf();
|
||||||
|
expect(dux.actions.foo()).toMatchObject({ type: 'foo' });
|
||||||
|
});
|
||||||
|
test('effects of subduxes', () => {
|
||||||
|
const foo = new Updux({
|
||||||
|
initialState: 12,
|
||||||
|
actions: {
|
||||||
|
bar: null,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
addHundred: (x) => x + 100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.addMutation(createAction('setFoo', withPayload()), (state) => () => state)
|
||||||
|
.addEffect(({ type: t }) => t === 'doit', (api) => next => action => {
|
||||||
|
api.dispatch.setFoo(api.getState.addHundred());
|
||||||
|
});
|
||||||
|
const dux = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
foo
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const store = dux.createStore();
|
||||||
|
store.dispatch({ type: "doit" });
|
||||||
|
expect(store.getState()).toMatchObject({ foo: 112 });
|
||||||
|
});
|
||||||
|
// TODO subdux effects
|
||||||
|
// TODO allow to subscribe / unsubscribe effects?
|
17
dist/foo.js
vendored
Normal file
17
dist/foo.js
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
class Foo {
|
||||||
|
constuctor(t) {
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
something(a) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Bar extends Foo {
|
||||||
|
constructor() {
|
||||||
|
super(...arguments);
|
||||||
|
this.something = (a) => { };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const x = new Bar();
|
||||||
|
x.something;
|
||||||
|
export {};
|
3
dist/index.d.ts
vendored
Normal file
3
dist/index.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Updux from './Updux.js';
|
||||||
|
export { withPayload, createAction } from './actions.js';
|
||||||
|
export default Updux;
|
3
dist/index.js
vendored
Normal file
3
dist/index.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Updux from './Updux.js';
|
||||||
|
export { withPayload, createAction } from './actions.js';
|
||||||
|
export default Updux;
|
8
dist/initial.js
vendored
Normal file
8
dist/initial.js
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import * as R from 'remeda';
|
||||||
|
export function buildInitial(localInitial, subduxes) {
|
||||||
|
if (Object.keys(subduxes).length > 0 && typeof localInitial !== 'object') {
|
||||||
|
throw new Error("can't have subduxes when the initialState value is not an object");
|
||||||
|
}
|
||||||
|
return u(localInitial, R.mapValues(subduxes, R.pathOr(['initialState'], {})));
|
||||||
|
}
|
74
dist/initial.test.js
vendored
Normal file
74
dist/initial.test.js
vendored
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { expectType } from './tutorial.test.js';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
const bar = new Updux({ initialState: 123 });
|
||||||
|
const foo = new Updux({
|
||||||
|
initialState: { root: 'abc' },
|
||||||
|
subduxes: {
|
||||||
|
bar,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
test('default', () => {
|
||||||
|
const { initialState } = new Updux({});
|
||||||
|
expect(initialState).toBeTypeOf('object');
|
||||||
|
expect(initialState).toEqual({});
|
||||||
|
});
|
||||||
|
test('number', () => {
|
||||||
|
const { initialState } = new Updux({ initialState: 3 });
|
||||||
|
expect(initialState).toBeTypeOf('number');
|
||||||
|
expect(initialState).toEqual(3);
|
||||||
|
});
|
||||||
|
test('initialState to createStore', () => {
|
||||||
|
const initialState = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
};
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState,
|
||||||
|
});
|
||||||
|
expect(dux.createStore({ preloadedState: { a: 3, b: 4 } }).getState()).toEqual({
|
||||||
|
a: 3,
|
||||||
|
b: 4,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('single dux', () => {
|
||||||
|
const foo = new Updux({
|
||||||
|
initialState: { a: 1 },
|
||||||
|
});
|
||||||
|
expect(foo.initialState).toEqual({ a: 1 });
|
||||||
|
});
|
||||||
|
// TODO add 'check for no todo eslint rule'
|
||||||
|
test('initialState value', () => {
|
||||||
|
expect(foo.initialState).toEqual({
|
||||||
|
root: 'abc',
|
||||||
|
bar: 123,
|
||||||
|
});
|
||||||
|
expectType(foo.initialState);
|
||||||
|
});
|
||||||
|
test('no initialState', () => {
|
||||||
|
const dux = new Updux({});
|
||||||
|
expectType(dux.initialState);
|
||||||
|
expect(dux.initialState).toEqual({});
|
||||||
|
});
|
||||||
|
test('no initialState for subdux', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
bar: new Updux({}),
|
||||||
|
baz: new Updux({ initialState: 'potato' }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expectType(dux.initialState);
|
||||||
|
expect(dux.initialState).toEqual({ bar: {}, baz: 'potato' });
|
||||||
|
});
|
||||||
|
test.todo('splat initialState', () => {
|
||||||
|
const bar = new Updux({
|
||||||
|
initialState: { id: 0 },
|
||||||
|
});
|
||||||
|
const foo = new Updux({
|
||||||
|
subduxes: { '*': bar },
|
||||||
|
});
|
||||||
|
expect(foo.initialState).toEqual([]);
|
||||||
|
expect(new Updux({
|
||||||
|
initialState: 'overriden',
|
||||||
|
subduxes: { '*': bar },
|
||||||
|
}).initialState).toEqual('overriden');
|
||||||
|
});
|
1
dist/initialState.d.ts
vendored
Normal file
1
dist/initialState.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export declare function buildInitialState(localInitialState: any, subduxes: any): any;
|
12
dist/initialState.js
vendored
Normal file
12
dist/initialState.js
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import { D } from '@mobily/ts-belt';
|
||||||
|
export function buildInitialState(localInitialState, subduxes) {
|
||||||
|
let state = localInitialState !== null && localInitialState !== void 0 ? localInitialState : {};
|
||||||
|
if (subduxes) {
|
||||||
|
if (typeof state !== 'object') {
|
||||||
|
throw new Error('root initial state is not an object');
|
||||||
|
}
|
||||||
|
state = u(state, D.map(subduxes, D.prop('initialState')));
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
1
dist/initialState.test.d.ts
vendored
Normal file
1
dist/initialState.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
86
dist/initialState.test.js
vendored
Normal file
86
dist/initialState.test.js
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { buildInitialState } from './initialState.js';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
test('default', () => {
|
||||||
|
const dux = new Updux({});
|
||||||
|
expect(dux.initialState).toBeTypeOf('object');
|
||||||
|
expect(dux.initialState).toEqual({});
|
||||||
|
expectTypeOf(dux.initialState).toEqualTypeOf();
|
||||||
|
});
|
||||||
|
test('number', () => {
|
||||||
|
const dux = new Updux({ initialState: 3 });
|
||||||
|
expect(dux.initialState).toEqual(3);
|
||||||
|
expectTypeOf(dux.initialState).toEqualTypeOf();
|
||||||
|
const f = { initialState: dux.initialState };
|
||||||
|
});
|
||||||
|
test('single dux', () => {
|
||||||
|
const foo = new Updux({
|
||||||
|
initialState: { a: 1 },
|
||||||
|
});
|
||||||
|
expect(foo.initialState).toEqual({ a: 1 });
|
||||||
|
expectTypeOf(foo.initialState).toEqualTypeOf();
|
||||||
|
});
|
||||||
|
test('no initialState for subdux', () => {
|
||||||
|
const subduxes = {
|
||||||
|
bar: new Updux({}),
|
||||||
|
baz: new Updux({ initialState: 'potato' }),
|
||||||
|
};
|
||||||
|
const dux = new Updux({
|
||||||
|
subduxes,
|
||||||
|
});
|
||||||
|
expectTypeOf(dux.initialState).toEqualTypeOf();
|
||||||
|
expect(dux.initialState).toEqual({ bar: {}, baz: 'potato' });
|
||||||
|
});
|
||||||
|
test('basic', () => {
|
||||||
|
expect(buildInitialState({ a: 1 }, { b: { initialState: { c: 2 } }, d: { initialState: 'e' } })).toEqual({
|
||||||
|
a: 1,
|
||||||
|
b: { c: 2 },
|
||||||
|
d: 'e',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('throw if subduxes and initialState is not an object', () => {
|
||||||
|
expect(() => {
|
||||||
|
buildInitialState(3, { bar: 'foo' });
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
const bar = new Updux({ initialState: 123 });
|
||||||
|
const foo = new Updux({
|
||||||
|
initialState: { root: 'abc' },
|
||||||
|
subduxes: {
|
||||||
|
bar
|
||||||
|
},
|
||||||
|
});
|
||||||
|
test('initialState to createStore', () => {
|
||||||
|
const initialState = {
|
||||||
|
a: 1,
|
||||||
|
b: 2,
|
||||||
|
};
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState,
|
||||||
|
});
|
||||||
|
expect(dux.createStore({ preloadedState: { a: 3, b: 4 } }).getState()).toEqual({
|
||||||
|
a: 3,
|
||||||
|
b: 4,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// TODO add 'check for no todo eslint rule'
|
||||||
|
test('initialState value', () => {
|
||||||
|
expect(foo.initialState).toEqual({
|
||||||
|
root: 'abc',
|
||||||
|
bar: 123,
|
||||||
|
});
|
||||||
|
expectTypeOf(foo.initialState.bar).toMatchTypeOf();
|
||||||
|
expectTypeOf(foo.initialState).toMatchTypeOf();
|
||||||
|
});
|
||||||
|
test.todo('splat initialState', async () => {
|
||||||
|
const bar = new Updux({
|
||||||
|
initialState: { id: 0 },
|
||||||
|
});
|
||||||
|
const foo = new Updux({
|
||||||
|
subduxes: { '*': bar },
|
||||||
|
});
|
||||||
|
expect(foo.initialState).toEqual([]);
|
||||||
|
expect(new Updux({
|
||||||
|
initialState: 'overriden',
|
||||||
|
subduxes: { '*': bar },
|
||||||
|
}).initialState).toEqual('overriden');
|
||||||
|
});
|
1
dist/mutations.test.d.ts
vendored
Normal file
1
dist/mutations.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
96
dist/mutations.test.js
vendored
Normal file
96
dist/mutations.test.js
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import { withPayload } from './actions.js';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
test('set a mutation', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: 'potato',
|
||||||
|
actions: {
|
||||||
|
foo: (x) => ({ x }),
|
||||||
|
bar: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
let didIt = false;
|
||||||
|
dux.addMutation(dux.actions.foo, (payload, action) => () => {
|
||||||
|
expectTypeOf(payload).toMatchTypeOf();
|
||||||
|
didIt = true;
|
||||||
|
expect(payload).toEqual({ x: 'hello ' });
|
||||||
|
expect(action).toEqual(dux.actions.foo('hello '));
|
||||||
|
return payload.x + action.type;
|
||||||
|
});
|
||||||
|
const result = dux.reducer(undefined, dux.actions.foo('hello '));
|
||||||
|
expect(didIt).toBeTruthy();
|
||||||
|
expect(result).toEqual('hello foo');
|
||||||
|
});
|
||||||
|
test('catch-all mutation', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: '',
|
||||||
|
});
|
||||||
|
dux.addMutation(() => true, (payload, action) => () => 'got it');
|
||||||
|
expect(dux.reducer(undefined, { type: 'foo' })).toEqual('got it');
|
||||||
|
});
|
||||||
|
test('default mutation', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: '',
|
||||||
|
actions: {
|
||||||
|
foo: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dux.addMutation(dux.actions.foo, () => () => 'got it');
|
||||||
|
dux.setDefaultMutation((_payload, action) => () => action.type);
|
||||||
|
expect(dux.reducer(undefined, { type: 'foo' })).toEqual('got it');
|
||||||
|
expect(dux.reducer(undefined, { type: 'bar' })).toEqual('bar');
|
||||||
|
});
|
||||||
|
test('mutation of a subdux', () => {
|
||||||
|
const baz = createAction('baz');
|
||||||
|
const noop = createAction('noop');
|
||||||
|
const stopit = createAction('stopit');
|
||||||
|
const bar = new Updux({
|
||||||
|
initialState: 0,
|
||||||
|
actions: {
|
||||||
|
baz,
|
||||||
|
stopit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
bar.addMutation(baz, () => () => 1);
|
||||||
|
bar.addMutation(stopit, () => () => 2);
|
||||||
|
const foo = new Updux({
|
||||||
|
subduxes: { bar },
|
||||||
|
});
|
||||||
|
foo.addMutation(stopit, () => (state) => state, true);
|
||||||
|
expect(foo.reducer(undefined, noop())).toHaveProperty('bar', 0);
|
||||||
|
expect(foo.reducer(undefined, baz())).toHaveProperty('bar', 1);
|
||||||
|
expect(foo.reducer(undefined, stopit())).toHaveProperty('bar', 0);
|
||||||
|
});
|
||||||
|
test('actionType as string', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
actions: {
|
||||||
|
doIt: (id) => id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dux.addMutation('doIt', (payload) => (state) => {
|
||||||
|
expectTypeOf(payload).toMatchTypeOf();
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
expect(() => {
|
||||||
|
// @ts-ignore
|
||||||
|
dux.addMutation('unknown', () => (x) => x);
|
||||||
|
}).toThrow();
|
||||||
|
});
|
||||||
|
test('setDefaultMutation return value', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: 13,
|
||||||
|
});
|
||||||
|
let withDM = dux.setDefaultMutation(() => (state) => state);
|
||||||
|
expect(withDM).toEqual(dux);
|
||||||
|
expectTypeOf(withDM.initialState).toBeNumber();
|
||||||
|
});
|
||||||
|
test('addMutation with createAction', () => {
|
||||||
|
const setName = createAction('setName', withPayload());
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
name: '',
|
||||||
|
round: 1,
|
||||||
|
},
|
||||||
|
}).addMutation(setName, (name) => (state) => (Object.assign(Object.assign({}, state), { name })));
|
||||||
|
});
|
2
dist/new-types.js
vendored
Normal file
2
dist/new-types.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
(DuxState) = D['initialState'];
|
||||||
|
export {};
|
1
dist/reactions.d.ts
vendored
Normal file
1
dist/reactions.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export declare function buildReactions(reactions: any, subduxes: any): any[];
|
6
dist/reactions.js
vendored
Normal file
6
dist/reactions.js
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function buildReactions(reactions, subduxes) {
|
||||||
|
return [
|
||||||
|
...reactions,
|
||||||
|
...Object.entries(subduxes !== null && subduxes !== void 0 ? subduxes : {}).flatMap(([slice, { reactions }]) => reactions.map((r) => (api, unsub) => r(Object.assign(Object.assign({}, api), { getState: () => api.getState()[slice] }), unsub))),
|
||||||
|
];
|
||||||
|
}
|
1
dist/reactions.test.d.ts
vendored
Normal file
1
dist/reactions.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
72
dist/reactions.test.js
vendored
Normal file
72
dist/reactions.test.js
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import Updux from './index.js';
|
||||||
|
test('basic reactions', () => {
|
||||||
|
const foo = new Updux({
|
||||||
|
initialState: 0,
|
||||||
|
actions: { inc: null, reset: null },
|
||||||
|
});
|
||||||
|
foo.addMutation(foo.actions.inc, () => (state) => state + 1);
|
||||||
|
foo.addMutation(foo.actions.reset, () => (state) => 0);
|
||||||
|
foo.addReaction((api) => (state, _previous, unsubscribe) => {
|
||||||
|
if (state < 3)
|
||||||
|
return;
|
||||||
|
unsubscribe();
|
||||||
|
api.dispatch.reset();
|
||||||
|
});
|
||||||
|
// TODO
|
||||||
|
//reaction: (api) => (state,previous,unsubscribe)
|
||||||
|
const store = foo.createStore();
|
||||||
|
store.dispatch.inc();
|
||||||
|
expect(store.getState()).toEqual(1);
|
||||||
|
store.dispatch.inc();
|
||||||
|
store.dispatch.inc();
|
||||||
|
expect(store.getState()).toEqual(0); // we've been reset
|
||||||
|
store.dispatch.inc();
|
||||||
|
store.dispatch.inc();
|
||||||
|
store.dispatch.inc();
|
||||||
|
store.dispatch.inc();
|
||||||
|
expect(store.getState()).toEqual(4); // we've unsubscribed
|
||||||
|
});
|
||||||
|
test('subdux reactions', () => {
|
||||||
|
const bar = new Updux({
|
||||||
|
initialState: 0,
|
||||||
|
actions: { inc: null, reset: null },
|
||||||
|
selectors: {
|
||||||
|
getIt: (x) => x,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
bar.addMutation(bar.actions.inc, () => (state) => {
|
||||||
|
return state + 1;
|
||||||
|
});
|
||||||
|
bar.addMutation(bar.actions.reset, () => (state) => 0);
|
||||||
|
let seen = 0;
|
||||||
|
bar.addReaction((api) => (state, _previous, unsubscribe) => {
|
||||||
|
seen++;
|
||||||
|
expect(api.actions).not.toHaveProperty('notInBar');
|
||||||
|
expect(state).toBeTypeOf('number');
|
||||||
|
if (state < 3)
|
||||||
|
return;
|
||||||
|
unsubscribe();
|
||||||
|
api.dispatch.reset();
|
||||||
|
});
|
||||||
|
const foo = new Updux({
|
||||||
|
actions: { notInBar: null },
|
||||||
|
subduxes: { bar },
|
||||||
|
});
|
||||||
|
const store = foo.createStore();
|
||||||
|
store.dispatch.inc();
|
||||||
|
expect(seen).toEqual(1);
|
||||||
|
expect(store.getState()).toEqual({ bar: 1 });
|
||||||
|
expect(store.getState.getIt()).toEqual(1);
|
||||||
|
store.dispatch.inc();
|
||||||
|
expect(seen).toEqual(2);
|
||||||
|
store.dispatch.inc();
|
||||||
|
expect(seen).toEqual(3);
|
||||||
|
expect(store.getState.getIt()).toEqual(0); // we've been reset
|
||||||
|
store.dispatch.inc();
|
||||||
|
store.dispatch.inc();
|
||||||
|
store.dispatch.inc();
|
||||||
|
store.dispatch.inc();
|
||||||
|
expect(seen).toEqual(3);
|
||||||
|
expect(store.getState.getIt()).toEqual(4); // we've unsubscribed
|
||||||
|
});
|
10
dist/reducer.d.ts
vendored
Normal file
10
dist/reducer.d.ts
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import * as rtk from '@reduxjs/toolkit';
|
||||||
|
import { Mutation } from './types.js';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
import { AnyAction } from '@reduxjs/toolkit';
|
||||||
|
export type MutationCase = {
|
||||||
|
matcher: (action: rtk.AnyAction) => boolean;
|
||||||
|
mutation: Mutation;
|
||||||
|
terminal: boolean;
|
||||||
|
};
|
||||||
|
export declare function buildReducer(initialStateState: unknown, mutations?: MutationCase[], defaultMutation?: Omit<MutationCase, 'matcher'>, subduxes?: Record<string, Updux<any>>, inheritedReducer?: (state: any, action: AnyAction) => any): (state: unknown, action: rtk.AnyAction) => unknown;
|
32
dist/reducer.js
vendored
Normal file
32
dist/reducer.js
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import * as R from 'remeda';
|
||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import { D } from '@mobily/ts-belt';
|
||||||
|
export function buildReducer(initialStateState, mutations = [], defaultMutation, subduxes = {}, inheritedReducer) {
|
||||||
|
const subReducers = D.map(subduxes, D.getUnsafe('reducer'));
|
||||||
|
const reducer = (state = initialStateState, action) => {
|
||||||
|
if (!(action === null || action === void 0 ? void 0 : action.type))
|
||||||
|
throw new Error('reducer called with a bad action');
|
||||||
|
let active = mutations.filter(({ matcher }) => matcher(action));
|
||||||
|
if (active.length === 0 && defaultMutation)
|
||||||
|
active.push(defaultMutation);
|
||||||
|
if (!active.some(R.prop('terminal')) && inheritedReducer) {
|
||||||
|
active.push({
|
||||||
|
mutation: (_payload, action) => (state) => {
|
||||||
|
return u(state, inheritedReducer(state, action));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!active.some(R.prop('terminal')) &&
|
||||||
|
D.values(subReducers).length > 0) {
|
||||||
|
active.push({
|
||||||
|
mutation: (payload, action) => (state) => {
|
||||||
|
return u(state, R.mapValues(subReducers, (reducer, slice) => (state) => {
|
||||||
|
return reducer(state, action);
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return active.reduce((state, { mutation }) => mutation(action.payload, action)(state), state);
|
||||||
|
};
|
||||||
|
return reducer;
|
||||||
|
}
|
1
dist/reducer.test.d.ts
vendored
Normal file
1
dist/reducer.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
79
dist/reducer.test.js
vendored
Normal file
79
dist/reducer.test.js
vendored
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import { withPayload } from './actions.js';
|
||||||
|
import { buildReducer } from './reducer.js';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
test('buildReducer, initialState state', () => {
|
||||||
|
const reducer = buildReducer({ a: 1 });
|
||||||
|
expect(reducer(undefined, { type: 'foo' })).toEqual({ a: 1 });
|
||||||
|
});
|
||||||
|
test('buildReducer, mutation', () => {
|
||||||
|
const reducer = buildReducer(1, [
|
||||||
|
{
|
||||||
|
matcher: ({ type }) => type === 'inc',
|
||||||
|
mutation: () => (state) => state + 1,
|
||||||
|
terminal: false,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
expect(reducer(undefined, { type: 'foo' })).toEqual(1);
|
||||||
|
expect(reducer(undefined, { type: 'inc' })).toEqual(2);
|
||||||
|
});
|
||||||
|
test('basic reducer', () => {
|
||||||
|
const add = createAction('add', withPayload());
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: 12,
|
||||||
|
}).addMutation(add, (incr, action) => (state) => {
|
||||||
|
expectTypeOf(incr).toMatchTypeOf();
|
||||||
|
expectTypeOf(state).toMatchTypeOf();
|
||||||
|
expectTypeOf(action).toMatchTypeOf();
|
||||||
|
return state + incr;
|
||||||
|
});
|
||||||
|
expect(dux.reducer).toBeTypeOf('function');
|
||||||
|
expect(dux.reducer(1, { type: 'noop' })).toEqual(1); // noop
|
||||||
|
expect(dux.reducer(1, dux.actions.add(2))).toEqual(3);
|
||||||
|
});
|
||||||
|
test('defaultMutation', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: { a: 0, b: 0 },
|
||||||
|
actions: {
|
||||||
|
add: (x) => x,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dux.addMutation(dux.actions.add, (incr) => (state) => (Object.assign(Object.assign({}, state), { a: state.a + incr })));
|
||||||
|
dux.setDefaultMutation((payload) => (state) => (Object.assign(Object.assign({}, state), { b: state.b + 1 })));
|
||||||
|
expect(dux.reducer({ a: 0, b: 0 }, { type: 'noop' })).toMatchObject({
|
||||||
|
a: 0,
|
||||||
|
b: 1,
|
||||||
|
}); // noop catches the default mutation
|
||||||
|
expect(dux.reducer({ a: 1, b: 0 }, dux.actions.add(2))).toMatchObject({
|
||||||
|
a: 3,
|
||||||
|
b: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('subduxes mutations', () => {
|
||||||
|
const sub1 = new Updux({
|
||||||
|
initialState: 0,
|
||||||
|
actions: {
|
||||||
|
sub1action: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
sub1.addMutation(sub1.actions.sub1action, () => (state) => state + 1);
|
||||||
|
const dux = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
sub1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(dux.reducer(undefined, dux.actions.sub1action())).toMatchObject({
|
||||||
|
sub1: 1,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('upreducer', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: 0,
|
||||||
|
actions: {
|
||||||
|
incr: (x) => x,
|
||||||
|
},
|
||||||
|
}).addMutation('incr', (i) => state => state + i);
|
||||||
|
expect(dux.reducer(undefined, dux.actions.incr(5))).toEqual(5);
|
||||||
|
expect(dux.upreducer(dux.actions.incr(7))()).toEqual(7);
|
||||||
|
});
|
1
dist/schema.d.ts
vendored
Normal file
1
dist/schema.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default function buildSchema(schema?: any, initialState?: any, subduxes?: {}): any;
|
27
dist/schema.js
vendored
Normal file
27
dist/schema.js
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import * as R from 'remeda';
|
||||||
|
export default function buildSchema(schema = {}, initialState = undefined, subduxes = {}) {
|
||||||
|
if (typeof initialState !== 'undefined')
|
||||||
|
schema = u(schema, { default: u.constant(initialState) });
|
||||||
|
if (!schema.type) {
|
||||||
|
schema = u(schema, {
|
||||||
|
type: Array.isArray(schema.default)
|
||||||
|
? 'array'
|
||||||
|
: typeof schema.default,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (schema.type === 'object') {
|
||||||
|
// TODO will break with objects and arrays
|
||||||
|
schema = u(schema, {
|
||||||
|
properties: R.mapValues(schema.default, (v) => ({
|
||||||
|
type: typeof v,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Object.keys(subduxes).length > 0) {
|
||||||
|
schema = u(schema, {
|
||||||
|
properties: R.mapValues(subduxes, R.prop('schema')),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return schema;
|
||||||
|
}
|
1
dist/schema.test.d.ts
vendored
Normal file
1
dist/schema.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
34
dist/schema.test.js
vendored
Normal file
34
dist/schema.test.js
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
test('default schema', () => {
|
||||||
|
const dux = new Updux({});
|
||||||
|
expect(dux.schema).toMatchObject({
|
||||||
|
type: 'object',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('basic schema', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
schema: { type: 'number' },
|
||||||
|
});
|
||||||
|
expect(dux.schema).toMatchObject({
|
||||||
|
type: 'number',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('schema default inherits from initial state', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
schema: { type: 'number' },
|
||||||
|
initialState: 8,
|
||||||
|
});
|
||||||
|
expect(dux.schema.default).toEqual(8);
|
||||||
|
});
|
||||||
|
test('validate', () => {
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: 12,
|
||||||
|
actions: {
|
||||||
|
doItWrong: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
dux.addMutation('doItWrong', () => () => 'potato');
|
||||||
|
const store = dux.createStore({ validate: true });
|
||||||
|
expect(() => store.dispatch.doItWrong()).toThrow();
|
||||||
|
});
|
1
dist/schemas.js
vendored
Normal file
1
dist/schemas.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
15
dist/schemas.test.js
vendored
Normal file
15
dist/schemas.test.js
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { test } from 'vitest';
|
||||||
|
import { expectTypeOf } from 'expect-type';
|
||||||
|
test('from simple schema', () => {
|
||||||
|
const x = {
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['x'],
|
||||||
|
properties: {
|
||||||
|
x: { type: 'number' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let y;
|
||||||
|
expectTypeOf(y).toMatchTypeOf();
|
||||||
|
});
|
1
dist/selectors.d.ts
vendored
Normal file
1
dist/selectors.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export declare function buildSelectors(localSelectors?: {}, subduxes?: {}): any;
|
5
dist/selectors.js
vendored
Normal file
5
dist/selectors.js
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { D } from '@mobily/ts-belt';
|
||||||
|
export function buildSelectors(localSelectors = {}, subduxes = {}) {
|
||||||
|
const subSelectors = Object.entries(subduxes).map(([slice, { selectors }]) => D.map(selectors, (subSelect) => (state) => subSelect(state[slice])));
|
||||||
|
return [localSelectors, ...subSelectors].reduce(D.merge);
|
||||||
|
}
|
1
dist/selectors.test.d.ts
vendored
Normal file
1
dist/selectors.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
47
dist/selectors.test.js
vendored
Normal file
47
dist/selectors.test.js
vendored
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import Updux from './index.js';
|
||||||
|
describe('basic selectors', () => {
|
||||||
|
const config = {
|
||||||
|
initialState: {
|
||||||
|
x: 1,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
getX: ({ x }) => x,
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
bar: new Updux({
|
||||||
|
initialState: { y: 2 },
|
||||||
|
selectors: {
|
||||||
|
getY: ({ y }) => y,
|
||||||
|
getYPlus: ({ y }) => (incr) => (y + incr),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// cause the type to fail
|
||||||
|
baz: new Updux({
|
||||||
|
selectors: {
|
||||||
|
getFromBaz: () => 'potato',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const foo = new Updux(config);
|
||||||
|
const sample = {
|
||||||
|
x: 4,
|
||||||
|
bar: { y: 3 },
|
||||||
|
};
|
||||||
|
test('updux selectors', () => {
|
||||||
|
expect(foo.selectors.getX(sample)).toBe(4);
|
||||||
|
expect(foo.selectors.getY(sample)).toBe(3);
|
||||||
|
expect(foo.selectors.getYPlus(sample)(3)).toBe(6);
|
||||||
|
});
|
||||||
|
test('store selectors', () => {
|
||||||
|
const store = foo.createStore();
|
||||||
|
expect(store.getState.getY()).toBe(2);
|
||||||
|
expect(store.getState.getYPlus(3)).toBe(5);
|
||||||
|
});
|
||||||
|
test('addSelector', () => {
|
||||||
|
const dux = new Updux({ initialState: 13 }).addSelector('plusHundred', (state) => state + 100);
|
||||||
|
expectTypeOf(dux.selectors.plusHundred).toMatchTypeOf();
|
||||||
|
expect(dux.selectors.plusHundred(7)).toBe(107);
|
||||||
|
});
|
||||||
|
});
|
1
dist/subduxes.test.d.ts
vendored
Normal file
1
dist/subduxes.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
21
dist/subduxes.test.js
vendored
Normal file
21
dist/subduxes.test.js
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import { expectTypeOf } from 'expect-type';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
const subA = new Updux({
|
||||||
|
initialState: true,
|
||||||
|
actions: {
|
||||||
|
action1: (x) => x,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
getAntiSub: (s) => !s,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const mainDux = new Updux({
|
||||||
|
subduxes: { subA },
|
||||||
|
});
|
||||||
|
test('subduxes resolves as objects', () => {
|
||||||
|
expectTypeOf(mainDux.initialState).toMatchTypeOf();
|
||||||
|
expectTypeOf(mainDux.actions.action1('foo')).toMatchTypeOf();
|
||||||
|
expectTypeOf(mainDux.selectors.getAntiSub({ subA: true })).toMatchTypeOf();
|
||||||
|
expect(mainDux.selectors.getAntiSub({ subA: true })).toEqual(false);
|
||||||
|
});
|
1
dist/tutorial.test.d.ts
vendored
Normal file
1
dist/tutorial.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
39
dist/tutorial.test.js
vendored
Normal file
39
dist/tutorial.test.js
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import Updux, { createAction, withPayload } from './index.js';
|
||||||
|
import { expectTypeOf } from 'expect-type';
|
||||||
|
test('initialState state', () => {
|
||||||
|
const initialState = {
|
||||||
|
next_id: 1,
|
||||||
|
todos: [],
|
||||||
|
};
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState,
|
||||||
|
});
|
||||||
|
expectTypeOf(dux.initialState).toMatchTypeOf();
|
||||||
|
expect(dux.initialState).toEqual(initialState);
|
||||||
|
const store = dux.createStore();
|
||||||
|
expect(store.getState()).toEqual(initialState);
|
||||||
|
});
|
||||||
|
test('actions', () => {
|
||||||
|
const addTodo = createAction('addTodo', withPayload());
|
||||||
|
const todoDone = createAction('todoDone');
|
||||||
|
const todosDux = new Updux({
|
||||||
|
actions: {
|
||||||
|
addTodo,
|
||||||
|
todoDone,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(todosDux.actions.addTodo('write tutorial')).toEqual({
|
||||||
|
type: 'addTodo',
|
||||||
|
payload: 'write tutorial',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
test('mutation', () => {
|
||||||
|
const addTodo = createAction('addTodo', withPayload());
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: { nextId: 0, todos: [] },
|
||||||
|
});
|
||||||
|
dux.addMutation(addTodo, (description) => (state) => {
|
||||||
|
state.todos.unshift({ description, id: state.nextId, done: false });
|
||||||
|
state.nextId++;
|
||||||
|
});
|
||||||
|
});
|
1
dist/tutorial/actions.test.d.ts
vendored
Normal file
1
dist/tutorial/actions.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
70
dist/tutorial/actions.test.js
vendored
Normal file
70
dist/tutorial/actions.test.js
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
/// --8<-- [start:actions1]
|
||||||
|
import Updux, { createAction, withPayload } from 'updux';
|
||||||
|
const addTodo = createAction('addTodo', withPayload());
|
||||||
|
const todoDone = createAction('todoDone', withPayload());
|
||||||
|
/// --8<-- [end:actions1]
|
||||||
|
test('createAction', () => {
|
||||||
|
expect(addTodo).toBeTypeOf('function');
|
||||||
|
expect(todoDone).toBeTypeOf('function');
|
||||||
|
});
|
||||||
|
const todosDux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
nextId: 1,
|
||||||
|
todos: [],
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addTodo,
|
||||||
|
todoDone,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
/// --8<-- [start:actions2]
|
||||||
|
todosDux.actions.addTodo('write tutorial');
|
||||||
|
// { type: 'addTodo', payload: 'write tutorial' }
|
||||||
|
/// --8<-- [end:actions2]
|
||||||
|
test('basic', () => {
|
||||||
|
expect(todosDux.actions.addTodo('write tutorial')).toEqual({
|
||||||
|
type: 'addTodo',
|
||||||
|
payload: 'write tutorial',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
/// --8<-- [start:addMutation-1]
|
||||||
|
todosDux.addMutation(addTodo, (description) => ({ todos, nextId }) => ({
|
||||||
|
nextId: 1 + nextId,
|
||||||
|
todos: todos.concat({ description, id: nextId, done: false }),
|
||||||
|
}));
|
||||||
|
todosDux.addMutation(todoDone, (id) => ({ todos, nextId }) => ({
|
||||||
|
nextId: 1 + nextId,
|
||||||
|
todos: todos.map((todo) => {
|
||||||
|
if (todo.id !== id)
|
||||||
|
return todo;
|
||||||
|
return Object.assign(Object.assign({}, todo), { done: true });
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
/// --8<-- [end:addMutation-1]
|
||||||
|
const store = todosDux.createStore();
|
||||||
|
store.dispatch.addTodo('write tutorial');
|
||||||
|
const state = store.getState();
|
||||||
|
// {
|
||||||
|
// nextId: 2,
|
||||||
|
// todos: [
|
||||||
|
// {
|
||||||
|
// description: 'write tutorial',
|
||||||
|
// done: false,
|
||||||
|
// id: 1,
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
/// --8<-- [end:addMutation]
|
||||||
|
test('addMutation', () => {
|
||||||
|
expect(state).toEqual({
|
||||||
|
nextId: 2,
|
||||||
|
todos: [
|
||||||
|
{
|
||||||
|
description: 'write tutorial',
|
||||||
|
done: false,
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
1
dist/tutorial/effects.test.d.ts
vendored
Normal file
1
dist/tutorial/effects.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
34
dist/tutorial/effects.test.js
vendored
Normal file
34
dist/tutorial/effects.test.js
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
/// --8<-- [start:effects-1]
|
||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import * as R from 'remeda';
|
||||||
|
import Updux, { createAction, withPayload } from 'updux';
|
||||||
|
const addTodoWithId = createAction('addTodoWithId', withPayload());
|
||||||
|
const incNextId = createAction('incNextId');
|
||||||
|
const addTodo = createAction('addTodo', withPayload());
|
||||||
|
const todosDux = new Updux({
|
||||||
|
initialState: { nextId: 1, todos: [] },
|
||||||
|
actions: { addTodo, incNextId, addTodoWithId },
|
||||||
|
selectors: {
|
||||||
|
nextId: ({ nextId }) => nextId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
todosDux.addMutation(addTodoWithId, (todo) => u({
|
||||||
|
todos: R.concat([u(todo, { done: false })]),
|
||||||
|
}));
|
||||||
|
todosDux.addMutation(incNextId, () => u({ nextId: (id) => id + 1 }));
|
||||||
|
todosDux.addEffect('addTodo', ({ getState, dispatch }) => (next) => (action) => {
|
||||||
|
const id = getState.nextId();
|
||||||
|
dispatch.incNextId();
|
||||||
|
next(action);
|
||||||
|
dispatch.addTodoWithId({ id, description: action.payload });
|
||||||
|
});
|
||||||
|
const store = todosDux.createStore();
|
||||||
|
store.dispatch.addTodo('write tutorial');
|
||||||
|
/// --8<-- [end:effects-1]
|
||||||
|
test('basic', () => {
|
||||||
|
expect(store.getState()).toMatchObject({
|
||||||
|
nextId: 2,
|
||||||
|
todos: [{ id: 1, description: 'write tutorial', done: false }],
|
||||||
|
});
|
||||||
|
});
|
1
dist/tutorial/final.test.d.ts
vendored
Normal file
1
dist/tutorial/final.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
30
dist/tutorial/final.test.js
vendored
Normal file
30
dist/tutorial/final.test.js
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import todoListDux from './todoList.js';
|
||||||
|
test('basic', () => {
|
||||||
|
const store = todoListDux.createStore();
|
||||||
|
store.dispatch.addTodo('write tutorial');
|
||||||
|
store.dispatch.addTodo('test code snippets');
|
||||||
|
store.dispatch.todoDone(2);
|
||||||
|
const s = store.getState();
|
||||||
|
expectTypeOf(s).toMatchTypeOf();
|
||||||
|
expect(store.getState()).toMatchObject({
|
||||||
|
todos: [
|
||||||
|
{ id: 1, done: false },
|
||||||
|
{ id: 2, done: true },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
// expect(todoListDux.schema).toMatchObject({
|
||||||
|
// type: 'object',
|
||||||
|
// properties: {
|
||||||
|
// nextId: { type: 'number', default: 1 },
|
||||||
|
// todos: {
|
||||||
|
// default: [],
|
||||||
|
// type: 'array',
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// default: {
|
||||||
|
// nextId: 1,
|
||||||
|
// todos: [],
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
});
|
1
dist/tutorial/initialState.test.d.ts
vendored
Normal file
1
dist/tutorial/initialState.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
22
dist/tutorial/initialState.test.js
vendored
Normal file
22
dist/tutorial/initialState.test.js
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { test, expect, expectTypeOf } from 'vitest';
|
||||||
|
/// --8<-- [start:tut1]
|
||||||
|
import Updux from 'updux';
|
||||||
|
const todosDux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
nextId: 1,
|
||||||
|
todos: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
/// ---8<-- [end:tut1]
|
||||||
|
test('basic', () => {
|
||||||
|
/// ---8<-- [start:tut2]
|
||||||
|
const store = todosDux.createStore();
|
||||||
|
const expected = {
|
||||||
|
nextId: 1,
|
||||||
|
todos: [],
|
||||||
|
};
|
||||||
|
expect(todosDux.initialState).toEqual(expected);
|
||||||
|
expect(store.getState()).toEqual(expected);
|
||||||
|
expectTypeOf(todosDux.initialState).toMatchTypeOf();
|
||||||
|
/// ---8<-- [end:tut2]
|
||||||
|
});
|
1
dist/tutorial/monolith.test.d.ts
vendored
Normal file
1
dist/tutorial/monolith.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
54
dist/tutorial/monolith.test.js
vendored
Normal file
54
dist/tutorial/monolith.test.js
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
/// --8<-- [start:mono]
|
||||||
|
import Updux from '../index.js';
|
||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
const todosDux = new Updux({
|
||||||
|
initialState: {
|
||||||
|
nextId: 1,
|
||||||
|
todos: [],
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addTodo: (description) => description,
|
||||||
|
addTodoWithId: (description, id) => ({
|
||||||
|
description,
|
||||||
|
id,
|
||||||
|
done: false,
|
||||||
|
}),
|
||||||
|
todoDone: (id) => id,
|
||||||
|
incNextId: () => { },
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
getTodoById: ({ todos }) => (id) => todos.find(u.matches({ id })),
|
||||||
|
getNextId: ({ nextId }) => nextId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addMutation('addTodoWithId', (todo) => u.updateIn('todos', (todos) => [...todos, todo]))
|
||||||
|
.addMutation('incNextId', () => u({ nextId: (x) => x + 1 }))
|
||||||
|
.addMutation('todoDone', (id) => u.updateIn('todos', u.map(u.if(u.matches({ id }), { done: true }))))
|
||||||
|
.addEffect('addTodo', ({ getState, dispatch }) => (next) => (action) => {
|
||||||
|
const id = getState.getNextId();
|
||||||
|
dispatch.incNextId();
|
||||||
|
next(action);
|
||||||
|
dispatch.addTodoWithId(action.payload, id);
|
||||||
|
});
|
||||||
|
/// --8<-- [end:mono]
|
||||||
|
test('basic', () => {
|
||||||
|
const store = todosDux.createStore();
|
||||||
|
store.dispatch.addTodo('write tutorial');
|
||||||
|
store.dispatch.addTodo('have fun');
|
||||||
|
expect(store.getState()).toMatchObject({
|
||||||
|
nextId: 3,
|
||||||
|
todos: [
|
||||||
|
{ id: 1, description: 'write tutorial', done: false },
|
||||||
|
{ id: 2, description: 'have fun', done: false },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
store.dispatch.todoDone(1);
|
||||||
|
expect(store.getState()).toMatchObject({
|
||||||
|
nextId: 3,
|
||||||
|
todos: [
|
||||||
|
{ id: 1, description: 'write tutorial', done: true },
|
||||||
|
{ id: 2, description: 'have fun', done: false },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
11
dist/tutorial/nextId.d.ts
vendored
Normal file
11
dist/tutorial/nextId.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import Updux from '../index.js';
|
||||||
|
declare const _default: Updux<{
|
||||||
|
initialState: number;
|
||||||
|
actions: {
|
||||||
|
incNextId: any;
|
||||||
|
};
|
||||||
|
selectors: {
|
||||||
|
getNextId: (state: any) => any;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
export default _default;
|
13
dist/tutorial/nextId.js
vendored
Normal file
13
dist/tutorial/nextId.js
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/// --8<-- [start:dux]
|
||||||
|
import Updux from '../index.js';
|
||||||
|
export default new Updux({
|
||||||
|
initialState: 1,
|
||||||
|
actions: {
|
||||||
|
incNextId: null,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
getNextId: (state) => state,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addMutation('incNextId', () => (id) => id + 1);
|
||||||
|
/// --8<-- [end:dux]
|
1
dist/tutorial/recipes.test.d.ts
vendored
Normal file
1
dist/tutorial/recipes.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
33
dist/tutorial/recipes.test.js
vendored
Normal file
33
dist/tutorial/recipes.test.js
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import * as R from 'remeda';
|
||||||
|
import Updux from '../index.js';
|
||||||
|
const done = (text) => text;
|
||||||
|
const todo = new Updux({
|
||||||
|
initial: { text: '', done: false },
|
||||||
|
actions: { done, doneAll: null }, // doneAll is a synonym for done for this dux
|
||||||
|
});
|
||||||
|
todo.addMutation('done', () => u({ done: true }));
|
||||||
|
todo.addMutation('doneAll', () => u({ done: true }));
|
||||||
|
const todos = new Updux({
|
||||||
|
initialState: [],
|
||||||
|
actions: Object.assign({ addTodo: null }, todo.actions),
|
||||||
|
})
|
||||||
|
.addMutation('addTodo', (text) => R.concat([{ text }]))
|
||||||
|
.addMutation('done', (text, action) => u.mapIf({ text }, todo.upreducer(action)));
|
||||||
|
todos.setDefaultMutation((_payload, action) => R.map(todo.upreducer(action)));
|
||||||
|
test('tutorial', async () => {
|
||||||
|
const store = todos.createStore();
|
||||||
|
store.dispatch.addTodo('one');
|
||||||
|
store.dispatch.addTodo('two');
|
||||||
|
store.dispatch.addTodo('three');
|
||||||
|
store.dispatch.done('two');
|
||||||
|
expect(store.getState()[1].done).toBeTruthy();
|
||||||
|
expect(store.getState()[2].done).toBeFalsy();
|
||||||
|
store.dispatch.doneAll();
|
||||||
|
expect(store.getState().map(({ done }) => done)).toEqual([
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
]);
|
||||||
|
});
|
1
dist/tutorial/selectors.test.d.ts
vendored
Normal file
1
dist/tutorial/selectors.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
33
dist/tutorial/selectors.test.js
vendored
Normal file
33
dist/tutorial/selectors.test.js
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { test, expect } from 'vitest';
|
||||||
|
/// --8<-- [start:sel1]
|
||||||
|
import Updux from 'updux';
|
||||||
|
const dux = new Updux({
|
||||||
|
initialState: [],
|
||||||
|
selectors: {
|
||||||
|
getDone: (state) => state.filter(({ done }) => done),
|
||||||
|
getById: (state) => (id) => state.find((todo) => todo.id === id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const state = [
|
||||||
|
{ id: 1, done: true },
|
||||||
|
{ id: 2, done: false },
|
||||||
|
];
|
||||||
|
dux.selectors.getDone(state); // = [ { id: 1, done: true } ]
|
||||||
|
dux.selectors.getById(state)(2); // = { id: 2, done: false }
|
||||||
|
const store = dux.createStore({ preloadedState: state });
|
||||||
|
store.selectors.getDone(state); // = [ { id: 1, done: true } ]
|
||||||
|
store.selectors.getById(state)(2); // = { id: 2, done: false }
|
||||||
|
store.getState.getDone(); // = [ { id: 1, done: true } ]
|
||||||
|
store.getState.getById(2); // = { id: 2, done: false }
|
||||||
|
/// --8<-- [end:sel1]
|
||||||
|
test('selectors', () => {
|
||||||
|
expect(dux.selectors.getDone(state)).toMatchObject([{ id: 1, done: true }]);
|
||||||
|
expect(dux.selectors.getById(state)(2)).toMatchObject({ id: 2 });
|
||||||
|
expect(store.selectors.getDone(state)).toMatchObject([
|
||||||
|
{ id: 1, done: true },
|
||||||
|
]);
|
||||||
|
expect(store.selectors.getById(state)(2)).toMatchObject({ id: 2 });
|
||||||
|
expect(store.getState()).toMatchObject([{ id: 1 }, { id: 2 }]);
|
||||||
|
expect(store.getState.getDone()).toMatchObject([{ id: 1, done: true }]);
|
||||||
|
expect(store.getState.getById(2)).toMatchObject({ id: 2 });
|
||||||
|
});
|
17
dist/tutorial/todo.d.ts
vendored
Normal file
17
dist/tutorial/todo.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import Updux from '../index.js';
|
||||||
|
declare const _default: Updux<{
|
||||||
|
initialState: {
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
done: boolean;
|
||||||
|
};
|
||||||
|
actions: {
|
||||||
|
todoDone: any;
|
||||||
|
};
|
||||||
|
selectors: {
|
||||||
|
desc: ({ description }: {
|
||||||
|
description: any;
|
||||||
|
}) => any;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
export default _default;
|
16
dist/tutorial/todo.js
vendored
Normal file
16
dist/tutorial/todo.js
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Updux from '../index.js';
|
||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
export default new Updux({
|
||||||
|
initialState: {
|
||||||
|
id: 0,
|
||||||
|
description: '',
|
||||||
|
done: false,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
todoDone: null,
|
||||||
|
},
|
||||||
|
selectors: {
|
||||||
|
desc: ({ description }) => description,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addMutation('todoDone', () => u({ done: true }));
|
35
dist/tutorial/todoList.d.ts
vendored
Normal file
35
dist/tutorial/todoList.d.ts
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import Updux from '../index.js';
|
||||||
|
declare const _default: Updux<{
|
||||||
|
subduxes: {
|
||||||
|
todos: Updux<{
|
||||||
|
initialState: {
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
done: boolean;
|
||||||
|
}[];
|
||||||
|
actions: {
|
||||||
|
addTodoWithId: (description: any, id: any) => {
|
||||||
|
description: any;
|
||||||
|
id: any;
|
||||||
|
};
|
||||||
|
todoDone: (id: number) => number;
|
||||||
|
};
|
||||||
|
findSelectors: {
|
||||||
|
getTodoById: (state: any) => (id: any) => any;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
nextId: Updux<{
|
||||||
|
initialState: number;
|
||||||
|
actions: {
|
||||||
|
incNextId: any;
|
||||||
|
};
|
||||||
|
selectors: {
|
||||||
|
getNextId: (state: any) => any;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
actions: {
|
||||||
|
addTodo: (description: string) => string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
export default _default;
|
31
dist/tutorial/todoList.js
vendored
Normal file
31
dist/tutorial/todoList.js
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import Updux from '../index.js';
|
||||||
|
import nextIdDux from './nextId.js';
|
||||||
|
import todosDux from './todos.js';
|
||||||
|
export default new Updux({
|
||||||
|
subduxes: {
|
||||||
|
todos: todosDux,
|
||||||
|
nextId: nextIdDux,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addTodo: (description) => description,
|
||||||
|
},
|
||||||
|
}).addEffect('addTodo', ({ getState, dispatch }) => (next) => (action) => {
|
||||||
|
const id = getState.getNextId();
|
||||||
|
dispatch.incNextId();
|
||||||
|
next(action);
|
||||||
|
dispatch.addTodoWithId(action.payload, id);
|
||||||
|
});
|
||||||
|
const x = new Updux({
|
||||||
|
subduxes: {
|
||||||
|
todos: todosDux,
|
||||||
|
nextId: nextIdDux,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
addTodo: (description) => description,
|
||||||
|
},
|
||||||
|
}).addEffect('addTodo', ({ getState, dispatch }) => (next) => (action) => {
|
||||||
|
const id = getState.getNextId();
|
||||||
|
dispatch.incNextId();
|
||||||
|
next(action);
|
||||||
|
dispatch.addTodoWithId(action.payload, id);
|
||||||
|
});
|
19
dist/tutorial/todos.d.ts
vendored
Normal file
19
dist/tutorial/todos.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Updux from '../index.js';
|
||||||
|
declare const _default: Updux<{
|
||||||
|
initialState: {
|
||||||
|
id: number;
|
||||||
|
description: string;
|
||||||
|
done: boolean;
|
||||||
|
}[];
|
||||||
|
actions: {
|
||||||
|
addTodoWithId: (description: any, id: any) => {
|
||||||
|
description: any;
|
||||||
|
id: any;
|
||||||
|
};
|
||||||
|
todoDone: (id: number) => number;
|
||||||
|
};
|
||||||
|
findSelectors: {
|
||||||
|
getTodoById: (state: any) => (id: any) => any;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
export default _default;
|
27
dist/tutorial/todos.js
vendored
Normal file
27
dist/tutorial/todos.js
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import Updux from '../index.js';
|
||||||
|
import u from '@yanick/updeep-remeda';
|
||||||
|
import todoDux from './todo.js';
|
||||||
|
export default new Updux({
|
||||||
|
initialState: [],
|
||||||
|
actions: {
|
||||||
|
addTodoWithId: (description, id) => ({ description, id }),
|
||||||
|
todoDone: (id) => id,
|
||||||
|
},
|
||||||
|
findSelectors: {
|
||||||
|
getTodoById: (state) => (id) => state.find(u.matches({ id })),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addMutation('addTodoWithId', (todo) => (todos) => todos.concat(Object.assign(Object.assign({}, todo), { done: false })))
|
||||||
|
.addMutation('todoDone', (id, action) => u.map(u.if(u.matches({ id }), todoDux.upreducer(action))));
|
||||||
|
const x = new Updux({
|
||||||
|
initialState: [],
|
||||||
|
actions: {
|
||||||
|
addTodoWithId: (description, id) => ({ description, id }),
|
||||||
|
todoDone: (id) => id,
|
||||||
|
},
|
||||||
|
findSelectors: {
|
||||||
|
getTodoById: (state) => (id) => state.find(u.matches({ id })),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.addMutation('addTodoWithId', (todo) => (todos) => todos.concat(Object.assign(Object.assign({}, todo), { done: false })))
|
||||||
|
.addMutation('todoDone', (id, action) => u.map(u.if(u.matches({ id }), todoDux.upreducer(action))));
|
83
dist/types.d.ts
vendored
Normal file
83
dist/types.d.ts
vendored
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { ActionCreator, ActionCreatorWithPreparedPayload, AnyAction, Dispatch, MiddlewareAPI } from '@reduxjs/toolkit';
|
||||||
|
import { EffectMiddleware } from './effects.js';
|
||||||
|
import Updux from './Updux.js';
|
||||||
|
export type DuxConfig = Partial<{
|
||||||
|
initialState: any;
|
||||||
|
subduxes: Record<string, DuxConfig>;
|
||||||
|
actions: Record<string, ActionCreator<string> | Function | null>;
|
||||||
|
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;
|
||||||
|
export type SubduxesState<D> = D extends {
|
||||||
|
subduxes: infer S;
|
||||||
|
} ? {
|
||||||
|
[key in keyof S]: DuxState<UpduxConfig<S[key]>>;
|
||||||
|
} : unknown;
|
||||||
|
type ForceResolveObject<O> = O extends {
|
||||||
|
[key: string]: any;
|
||||||
|
} ? {
|
||||||
|
[key in keyof O]: O[key];
|
||||||
|
} : O;
|
||||||
|
export type DuxState<D> = ForceResolveObject<(D extends {
|
||||||
|
initialState: any;
|
||||||
|
} ? D['initialState'] : {}) & (D extends {
|
||||||
|
subduxes: any;
|
||||||
|
} ? SubduxesState<D> : unknown)>;
|
||||||
|
type SubduxesActions<D> = D extends {
|
||||||
|
subduxes: infer S;
|
||||||
|
} ? UnionToIntersection<DuxActions<UpduxConfig<S[keyof S]>>> : unknown;
|
||||||
|
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
|
||||||
|
type IsAny<T> = IfAny<T, true, never>;
|
||||||
|
type ResolveAction<K extends string, A> = true extends IsAny<A> ? ActionCreator<A> : A extends Function & {
|
||||||
|
type: string;
|
||||||
|
} ? A : A extends (...args: infer PARAMS) => infer R ? ActionCreatorWithPreparedPayload<PARAMS, R, K, never, never> : ActionCreator<A>;
|
||||||
|
type ResolveActions<A> = A extends {
|
||||||
|
[key: string]: any;
|
||||||
|
} ? {
|
||||||
|
[key in keyof A]: key extends string ? ResolveAction<key, A[key]> : never;
|
||||||
|
} : A;
|
||||||
|
export type DuxActions<D> = ResolveActions<(D extends {
|
||||||
|
actions: any;
|
||||||
|
} ? D['actions'] : {}) & (D extends {
|
||||||
|
subduxes: any;
|
||||||
|
} ? SubduxesActions<D> : unknown)>;
|
||||||
|
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;
|
||||||
|
} ? P : undefined, action: A) => (state: S) => S | void;
|
||||||
|
type CurriedSelectors<S> = {
|
||||||
|
[key in keyof S]: CurriedSelector<S[key]>;
|
||||||
|
};
|
||||||
|
type XSel<R> = R extends Function ? R : () => R;
|
||||||
|
type CurriedSelector<S> = S extends (...args: any) => infer R ? XSel<R> : never;
|
||||||
|
export type AugmentedMiddlewareAPI<D> = MiddlewareAPI<Dispatch<AnyAction>, DuxState<D>> & {
|
||||||
|
dispatch: DuxActions<D>;
|
||||||
|
getState: CurriedSelectors<DuxSelectors<D>>;
|
||||||
|
actions: DuxActions<D>;
|
||||||
|
selectors: DuxSelectors<D>;
|
||||||
|
};
|
||||||
|
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) extends (k: infer I) => void ? I : never;
|
||||||
|
type RebaseSelectors<SLICE, DUX> = DUX extends {
|
||||||
|
selectors: infer S;
|
||||||
|
} ? {
|
||||||
|
[key in keyof S]: RebaseSelector<SLICE, S[key]>;
|
||||||
|
} : never;
|
||||||
|
type RebaseSelector<SLICE, S> = SLICE extends string ? S extends (state: infer STATE) => infer R ? (state: Record<SLICE, STATE>) => R : never : 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 {};
|
1
dist/types.js
vendored
Normal file
1
dist/types.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
1
dist/types.test.d.ts
vendored
Normal file
1
dist/types.test.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
export {};
|
8
dist/types.test.js
vendored
Normal file
8
dist/types.test.js
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { test } from 'vitest';
|
||||||
|
import { expectTypeOf } from 'expect-type';
|
||||||
|
test('duxstate basic', () => {
|
||||||
|
const x = {
|
||||||
|
initialState: 'potato',
|
||||||
|
};
|
||||||
|
expectTypeOf(true).toEqualTypeOf();
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user