Merge branch 'subdux-selectors'

This commit is contained in:
Yanick Champoux 2023-09-20 10:49:05 -04:00
commit 7280381ca8
6 changed files with 105 additions and 89 deletions

View File

@ -1,58 +1,59 @@
{
"type": "module",
"dependencies": {
"@yanick/updeep-remeda": "^2.2.0",
"ajv": "^8.12.0",
"expect-type": "^0.16.0",
"immer": "^9.0.15",
"json-schema-shorthand": "^2.0.0",
"json-schema-to-ts": "^2.9.2",
"memoize-one": "^6.0.0",
"moize": "^6.1.6",
"redux": "^4.2.0",
"remeda": "^1.0.1",
"updeep": "^1.2.1"
},
"license": "MIT",
"module": "dist/index.js",
"exports": {
".": {
"import": "./dist/index.js"
}
},
"name": "updux",
"description": "Updeep-friendly Redux helper framework",
"scripts": {
"docsify:serve": "docsify serve docs"
},
"version": "5.1.0",
"repository": {
"type": "git",
"url": "git+https://github.com/yanick/updux.git"
},
"keywords": [
"redux",
"updeep"
],
"author": "Yanick Champoux <yanick@babyl.ca> (http://techblog.babyl.ca)",
"bugs": {
"url": "https://github.com/yanick/updux/issues"
},
"homepage": "https://github.com/yanick/updux#readme",
"devDependencies": {
"@reduxjs/toolkit": "==2.0.0-alpha.5 ",
"@vitest/browser": "^0.23.1",
"@vitest/ui": "^0.23.1",
"docsify": "^4.13.1",
"eslint": "^8.22.0",
"eslint-plugin-no-only-tests": "^3.0.0",
"eslint-plugin-todo-plz": "^1.2.1",
"jsdoc-to-markdown": "^7.1.1",
"prettier": "^2.7.1",
"redux-toolkit": "^1.1.2",
"tsdoc-markdown": "^0.0.4",
"typescript": "^4.9.5",
"vite": "^4.2.1",
"vitest": "0.23.1"
"type": "module",
"dependencies": {
"@yanick/updeep-remeda": "^2.2.0",
"ajv": "^8.12.0",
"expect-type": "^0.16.0",
"immer": "^9.0.15",
"json-schema-shorthand": "^2.0.0",
"json-schema-to-ts": "^2.9.2",
"memoize-one": "^6.0.0",
"moize": "^6.1.6",
"redux": "^4.2.0",
"remeda": "^1.0.1",
"updeep": "^1.2.1"
},
"license": "MIT",
"module": "dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js"
}
},
"name": "updux",
"description": "Updeep-friendly Redux helper framework",
"scripts": {
"docsify:serve": "docsify serve docs"
},
"version": "5.1.0",
"repository": {
"type": "git",
"url": "git+https://github.com/yanick/updux.git"
},
"keywords": [
"redux",
"updeep"
],
"author": "Yanick Champoux <yanick@babyl.ca> (http://techblog.babyl.ca)",
"bugs": {
"url": "https://github.com/yanick/updux/issues"
},
"homepage": "https://github.com/yanick/updux#readme",
"devDependencies": {
"@reduxjs/toolkit": "==2.0.0-alpha.5 ",
"@vitest/browser": "^0.23.1",
"@vitest/ui": "^0.23.1",
"docsify": "^4.13.1",
"eslint": "^8.22.0",
"eslint-plugin-no-only-tests": "^3.0.0",
"eslint-plugin-todo-plz": "^1.2.1",
"jsdoc-to-markdown": "^7.1.1",
"prettier": "^2.7.1",
"redux-toolkit": "^1.1.2",
"tsdoc-markdown": "^0.0.4",
"typescript": "^4.9.5",
"vite": "^4.2.1",
"vitest": "0.23.1"
}
}

View File

@ -122,17 +122,17 @@ export default class Updux<D extends DuxConfig> {
matcher: A,
mutation: Mutation<DuxActions<D>[A] extends (...args: any) => infer P ? P : never, DuxState<D>>,
terminal?: boolean,
);
): Updux<D>;
addMutation<A extends Action<any>>(
matcher: (action: A) => boolean,
mutation: Mutation<A, DuxState<D>>,
terminal?: boolean,
);
): Updux<D>;
addMutation<A extends ActionCreator<any>>(
actionCreator: A,
mutation: Mutation<ReturnType<A>, DuxState<D>>,
terminal?: boolean,
);
): Updux<D>;
addMutation(matcher, mutation, terminal = false) {
if (typeof matcher === 'string') {
@ -157,6 +157,8 @@ export default class Updux<D extends DuxConfig> {
mutation,
},
];
return this;
}
#defaultMutation;
@ -189,6 +191,7 @@ export default class Updux<D extends DuxConfig> {
options: Partial<{
preloadedState: DuxState<D>;
validate: boolean;
buildMiddleware: (middleware: any[]) => any
}> = {},
): EnhancedStore<DuxState<D>> & AugmentedMiddlewareAPI<D> {
const preloadedState = options.preloadedState;
@ -199,7 +202,7 @@ export default class Updux<D extends DuxConfig> {
this.selectors,
);
const middleware = [effects];
let middleware = [effects];
if (options.validate) {
middleware.unshift(
@ -207,6 +210,9 @@ export default class Updux<D extends DuxConfig> {
)
}
if (options.buildMiddleware)
middleware = options.buildMiddleware(middleware);
const store = configureStore({
reducer: this.reducer,
preloadedState,
@ -253,16 +259,16 @@ export default class Updux<D extends DuxConfig> {
addEffect(
actionType: keyof DuxActions<D>,
effect: EffectMiddleware<D>,
): EffectMiddleware<D>;
): Updux<D>;
addEffect(
actionCreator: { match: (action: any) => boolean },
effect: EffectMiddleware<D>,
): EffectMiddleware<D>;
): Updux<D>;
addEffect(
guardFunc: (action: AnyAction) => boolean,
effect: EffectMiddleware<D>,
): EffectMiddleware<D>;
addEffect(effect: EffectMiddleware<D>): EffectMiddleware<D>;
): Updux<D>;
addEffect(effect: EffectMiddleware<D>): Updux<D>;
addEffect(...args) {
let effect;
if (args.length === 1) {
@ -294,10 +300,10 @@ export default class Updux<D extends DuxConfig> {
this.#effects = [...this.#effects, effect];
return effect;
return this;
}
get effects() {
get effects(): any {
return this.memoBuildEffects(this.#effects, this.duxConfig.subduxes);
}

View File

@ -7,7 +7,7 @@ import { createAction } from './index.js';
test('addEffect', () => {
const dux = new Updux({});
dux.addEffect((api) => (next) => (action) => { });
dux.addEffect((api) => (next) => (action) => {});
});
test('buildEffectsMiddleware', () => {
@ -48,7 +48,7 @@ test('buildEffectsMiddleware', () => {
expect(seen).toEqual(0);
const dispatch = vi.fn();
mw({ getState: () => 'the state', dispatch })(() => { })({
mw({ getState: () => 'the state', dispatch })(() => {})({
type: 'noop',
});
expect(seen).toEqual(1);
@ -126,10 +126,10 @@ test('addEffect with actionCreator', () => {
const next = vi.fn();
const spy = vi.fn();
const mw = dux.addEffect(
const [mw] = dux.addEffect(
dux.actions.foo,
(api) => (next) => (action) => next(spy(action)),
);
).effects;
mw({} as any)(next)(dux.actions.bar());
expect(next).toHaveBeenCalled();
@ -144,18 +144,18 @@ test('addEffect with actionCreator', () => {
test('addEffect with function', () => {
const dux = new Updux({
actions: {
foo: () => { },
bar: () => { },
foo: () => {},
bar: () => {},
},
});
const next = vi.fn();
const spy = vi.fn();
const mw = dux.addEffect(
const [mw] = dux.addEffect(
(action) => action.type[0] === 'f',
(api) => (next) => (action) => next(spy(action)),
);
).effects;
mw({} as any)(next)(dux.actions.bar());
expect(next).toHaveBeenCalled();
@ -170,13 +170,13 @@ test('addEffect with function', () => {
test('catchall addEffect', () => {
const dux = new Updux({
initialState: {
a: 1
}
a: 1,
},
});
const spy = vi.fn();
dux.addEffect((api) => next => action => {
dux.addEffect((api) => (next) => (action) => {
expectTypeOf(api.getState()).toMatchTypeOf<{
a: number;
}>();
@ -191,7 +191,6 @@ test('catchall addEffect', () => {
store.dispatch({ type: 'noop' });
expect(spy).toHaveBeenCalled();
});
// TODO subdux effects

View File

@ -5,7 +5,7 @@ import Updux, { createAction } from './index.js';
describe('basic selectors', () => {
type State = { x: number };
const foo = new Updux({
const config = {
initialState: {
x: 1,
},
@ -19,12 +19,20 @@ describe('basic selectors', () => {
getY: ({ y }: { y: number }) => y,
getYPlus:
({ y }) =>
(incr: number) =>
(y + incr) as number,
(incr: number) =>
(y + incr) as number,
},
}),
// cause the type to fail
baz: new Updux({
selectors: {
getFromBaz: () => 'potato',
},
}),
},
});
};
const foo = new Updux(config);
const sample = {
x: 4,

View File

@ -3,23 +3,25 @@ import { DuxState, UnionToIntersection } from './types.js';
type RebaseSelectors<SLICE, DUX> = DUX extends { selectors: infer S }
? {
[key in keyof S]: RebaseSelector<SLICE, S[key]>;
}
[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
? (state: Record<SLICE, STATE>) => R
: never
: never;
type Values<X> = X[keyof X];
export type DuxSelectors<D> = (D extends { selectors: infer S } ? S : {}) &
(D extends { subduxes: infer SUB }
? Values<{
[key in keyof SUB]: RebaseSelectors<key, SUB[key]>;
}>
? UnionToIntersection<
Values<{
[key in keyof SUB]: RebaseSelectors<key, SUB[key]>;
}>
>
: {});
export function buildSelectors(localSelectors = {}, subduxes = {}) {

View File

@ -48,7 +48,7 @@
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
"declaration": true /* Generate .d.ts files from TypeScript and JavaScript files in your project. */,
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */