add the dist directory

This commit is contained in:
Yanick Champoux 2025-01-31 13:16:41 -05:00
parent b20334c017
commit ad72570f49
87 changed files with 2267 additions and 1 deletions

1
.gitignore vendored
View File

@ -1,7 +1,6 @@
node_modules/
tsconfig.tsbuildinfo
**/*.orig
dist
package-lock.json
yarn.lock
.nyc_output/

82
dist/Updux.d.ts vendored Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
export {};

74
dist/actions.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

24
dist/asDux.test.js vendored Normal file
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
export declare function buildInitialState(localInitialState: any, subduxes: any): any;

12
dist/buildInitialState.js vendored Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
export {};

25
dist/createStore.test.js vendored Normal file
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
export {};

195
dist/effects.test.js vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
export declare function buildInitialState(localInitialState: any, subduxes: any): any;

12
dist/initialState.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

86
dist/initialState.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

96
dist/mutations.test.js vendored Normal file
View 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
View File

@ -0,0 +1,2 @@
(DuxState) = D['initialState'];
export {};

1
dist/reactions.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export declare function buildReactions(reactions: any, subduxes: any): any[];

6
dist/reactions.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

72
dist/reactions.test.js vendored Normal file
View 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
View 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
View 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
View File

@ -0,0 +1 @@
export {};

79
dist/reducer.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export default function buildSchema(schema?: any, initialState?: any, subduxes?: {}): any;

27
dist/schema.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

34
dist/schema.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

15
dist/schemas.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export declare function buildSelectors(localSelectors?: {}, subduxes?: {}): any;

5
dist/selectors.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

47
dist/selectors.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

21
dist/subduxes.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

39
dist/tutorial.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

70
dist/tutorial/actions.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

34
dist/tutorial/effects.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

30
dist/tutorial/final.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

22
dist/tutorial/initialState.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

54
dist/tutorial/monolith.test.js vendored Normal file
View 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
View 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
View 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
View File

@ -0,0 +1 @@
export {};

33
dist/tutorial/recipes.test.js vendored Normal file
View 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
View File

@ -0,0 +1 @@
export {};

33
dist/tutorial/selectors.test.js vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1 @@
export {};

1
dist/types.test.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export {};

8
dist/types.test.js vendored Normal file
View File

@ -0,0 +1,8 @@
import { test } from 'vitest';
import { expectTypeOf } from 'expect-type';
test('duxstate basic', () => {
const x = {
initialState: 'potato',
};
expectTypeOf(true).toEqualTypeOf();
});