typescript
Yanick Champoux 2022-08-24 21:19:27 -04:00
parent bb5218d2f6
commit 3aac2e092e
20 changed files with 132 additions and 63 deletions

View File

@ -63,7 +63,7 @@ import Updux from 'updux';
const updux = new Updux({
initial: { counter: 0 },
mutations: {
add: (inc=1) => state => { counter: counter + inc }
add: (inc=1) => state => ({ counter: state.counter + inc })
}
});
@ -74,7 +74,7 @@ Converting it to Immer would look like:
```
import Updux from 'updux';
import { produce } from 'Immer';
import { produce } from 'immer';
const updux = new Updux({
initial: { counter: 0 },
@ -91,7 +91,7 @@ can be used to wrap all mutations with it:
```
import Updux from 'updux';
import { produce } from 'Immer';
import { produce } from 'immer';
const updux = new Updux({
initial: { counter: 0 },

View File

@ -1,7 +1,9 @@
{
"type": "module",
"types": "./dist",
"dependencies": {
"@yanick/updeep": "link:../updeep",
"lodash": "^4.17.15",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"moize": "^6.1.0",
"redux": "^4.0.5",
@ -39,10 +41,10 @@
"ts-node": "^8.10.2",
"tsd": "^0.17.0",
"typedoc": "0.22.5",
"typescript": "^4.4.3"
"typescript": "^4.4.4"
},
"license": "MIT",
"main": "src/index.js",
"main": "dist/index.js",
"name": "updux",
"description": "Updeep-friendly Redux helper framework",
"scripts": {
@ -66,7 +68,6 @@
"url": "https://github.com/yanick/updux/issues"
},
"homepage": "https://github.com/yanick/updux#readme",
"types": "./dist/index.d.ts",
"prettier": {
"tabWidth": 4,
"singleQuote": true,

View File

@ -1,5 +1,5 @@
import { Updux } from './Updux';
import { action } from './actions';
import { Updux } from './Updux.js';
import { action } from './actions.js';
test('basic state', () => {
const alpha = new Updux({
@ -96,8 +96,8 @@ test('basic selectors', () => {
test('mutations', () => {
const alpha = new Updux({
initial: { quux: 3 },
actions: { add: (x:number)=>x },
});
alpha.setAction('add');
alpha.setMutation('add', (toAdd) => (state) => ({
quux: state.quux + toAdd,
}));
@ -108,8 +108,10 @@ test('mutations', () => {
bar: 4,
},
subduxes: { alpha },
actions: {
subtract: ()=>{},
}
});
dux.setAction('subtract');
dux.setMutation('add', (toAdd) => (state) => ({
...state,
foo: state.foo + toAdd,

View File

@ -1,15 +1,19 @@
/* TODO change * for leftovers to +, change subscriptions to reactions */
import moize from 'moize';
import u from '@yanick/updeep';
import u from 'updeep';
import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
import { get, map, mapValues, merge, difference } from 'lodash';
import { get, map, mapValues, merge, difference } from 'lodash-es';
import { buildInitial } from './buildInitial';
import { buildActions } from './buildActions';
import { buildSelectors } from './buildSelectors';
import { action } from './actions';
import { buildUpreducer } from './buildUpreducer';
import { buildMiddleware, augmentMiddlewareApi } from './buildMiddleware';
import { buildInitial } from './buildInitial/index.js';
import { buildActions } from './buildActions/index.js';
import { buildSelectors } from './buildSelectors/index.js';
import { action } from './actions.js';
import { buildUpreducer } from './buildUpreducer.js';
import {
buildMiddleware,
augmentMiddlewareApi,
effectToMiddleware,
} from './buildMiddleware/index.js';
import {
AggregateDuxActions,
@ -18,7 +22,9 @@ import {
ItemsOf,
Reducer,
Upreducer,
} from './types';
} from './types.js';
type Mutation<TState,TAction extends { payload?: any }> = (payload:TAction['payload'], action:TAction) => (state: TState) => TState;
/**
* Configuration object typically passed to the constructor of the class Updux.
@ -99,6 +105,8 @@ export interface UpduxConfig<
AggregateDuxState<TState, TSubduxes>,
ItemsOf<AggregateDuxActions<TActions, TSubduxes>>
>;
middlewareWrapper?: Function;
}
export class Updux<
@ -121,7 +129,11 @@ export class Updux<
#mappedReaction = undefined;
#upreducerWrapper = undefined;
constructor(config: UpduxConfig<TState, TActions, TSelectors, TSubduxes>) {
#middlewareWrapper = undefined;
constructor(
config: UpduxConfig<TState, TActions, TSelectors, TSubduxes>
) {
this.#initial = config.initial ?? {};
this.#subduxes = config.subduxes ?? {};
@ -165,6 +177,8 @@ export class Updux<
this.#mappedReaction = config.mappedReaction;
this.#upreducerWrapper = config.upreducerWrapper;
this.#middlewareWrapper = config.middlewareWrapper;
}
#memoInitial = moize(buildInitial);
@ -185,10 +199,16 @@ export class Updux<
this.#effects,
this.actions,
this.selectors,
this.#subduxes
this.#subduxes,
this.#middlewareWrapper,
this
);
}
setMiddlewareWrapper(wrapper: Function) {
this.#middlewareWrapper = wrapper;
}
/** @member { unknown } */
get initial(): AggregateDuxState<TState, TSubduxes> {
return this.#memoInitial(this.#initial, this.#subduxes);
@ -206,6 +226,8 @@ export class Updux<
);
}
get subduxes() { return this.#subduxes }
get upreducer(): Upreducer<
AggregateDuxState<TState, TSubduxes>,
ItemsOf<AggregateDuxActions<TActions, TSubduxes>>
@ -229,6 +251,11 @@ export class Updux<
this.#reactions = [...this.#reactions, subscription];
}
addReaction(reaction) {
this.#reactions = [...this.#reactions, reaction];
}
setAction(type, payloadFunc?: (...args: any) => any) {
const theAction = action(type, payloadFunc);
@ -246,7 +273,8 @@ export class Updux<
return func;
}
setMutation(name, mutation) {
setMutation<TAction extends keyof AggregateDuxActions<TActions,TSubduxes>>(name: TAction, mutation: Mutation<AggregateDuxState<TState, TSubduxes>,
ReturnType<AggregateDuxActions<TActions,TSubduxes>[TAction]>>) {
if (typeof name === 'function') name = name.type;
this.#mutations = {
@ -257,11 +285,15 @@ export class Updux<
return mutation;
}
addEffect(action, effect) {
addEffect<TType, E>(action: TType, effect: E): E {
this.#effects = [...this.#effects, [action, effect]];
return effect;
}
augmentMiddlewareApi(api) {
return augmentMiddlewareApi(api, this.actions, this.selectors);
}
splatSubscriber(store, inner, splatReaction) {
const cache = {};
@ -366,9 +398,12 @@ export class Updux<
return {
unsub: () => results.forEach(({ unsub }) => unsub()),
subscriber: () => results.forEach(({ subscriber }) => subscriber()),
subscriber: () =>
results.forEach(({ subscriber }) => subscriber()),
subscriberRaw: (...args) =>
results.forEach(({ subscriberRaw }) => subscriberRaw(...args)),
results.forEach(({ subscriberRaw }) =>
subscriberRaw(...args)
),
};
}
@ -415,4 +450,8 @@ export class Updux<
return store;
}
effectToMiddleware(effect) {
return effectToMiddleware(effect, this.actions, this.selectors);
}
}

View File

@ -1,13 +0,0 @@
import { action } from './actions';
test('action generators', () => {
const foo = action('foo');
expect(foo.type).toEqual('foo');
expect(foo()).toMatchObject({ type: 'foo' });
const bar = action('bar');
expect(bar.type).toEqual('bar');
expect(bar()).toMatchObject({ type: 'bar' });
});

34
src/actions.test.ts Normal file
View File

@ -0,0 +1,34 @@
import { action } from './actions.js';
import u from 'updeep';
test('action generators', () => {
const foo = action('foo');
expect(foo.type).toEqual('foo');
expect(foo()).toMatchObject({ type: 'foo' });
const bar = action('bar');
expect(bar.type).toEqual('bar');
expect(bar()).toMatchObject({ type: 'bar' });
const action3 = action('a3', (x:number) => x);
expect(action3(12)).toMatchObject({
type: 'a3',
payload: 12,
});
const action4 = action('a4', undefined, u.updateIn('meta.x','yay'));
expect(action4(13)).toMatchObject({
type: 'a4',
payload: 13,
meta: {x:'yay'},
});
expect(action4.type).toEqual('a4');
});

View File

@ -37,7 +37,7 @@ export function action<
payloadFunction?: TPayload,
transformer?: Function
): ActionGenerator<TType, TPayload> {
const generator = function (...payloadArg) {
let generator : any= function (...payloadArg) {
const result: Action = { type };
if (payloadFunction) {
@ -49,11 +49,12 @@ export function action<
return result;
};
if(transformer) {
const orig = generator;
generator = (...args: any) => transformer(orig(...args), args);
}
generator.type = type;
return (
transformer
? (...args: any) => transformer(generator(...args), args)
: generator
) as any;
return generator;
}

View File

@ -1,4 +1,5 @@
import { isPlainObject, mapValues } from 'lodash';
import { mapValues } from 'lodash-es';
import isPlainObject from 'lodash/isPlainObject.js';
import u from 'updeep';
export function buildInitial(initial, subduxes = {}) {

View File

@ -1,4 +1,4 @@
import { buildInitial } from '.';
import { buildInitial } from './index.js';
test('basic', () => {
expect(buildInitial({ a: 1 }, { b: { initial: { c: 2 } } })).toMatchObject({

View File

@ -1,6 +1,5 @@
import u from 'updeep';
import { mapValues, map, get } from 'lodash';
import { Updux } from '../Updux.js';
import { mapValues, map, get } from 'lodash-es';
const middlewareFor = (type, middleware) => (api) => (next) => (action) => {
if (type !== '*' && action.type !== type) return next(action);
@ -66,7 +65,9 @@ export function buildMiddleware(
effects = [],
actions = {},
selectors = {},
sub = {}
sub = {},
wrapper = undefined,
dux = undefined,
) {
let inner = map(sub, ({ middleware }, slice) =>
slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined
@ -76,7 +77,9 @@ export function buildMiddleware(
effectToMiddleware(effect, actions, selectors)
);
const mws = [...local, ...inner];
let mws = [...local, ...inner];
if( wrapper ) mws = wrapper(mws,dux);
return composeMw(mws);
}

View File

@ -1,4 +1,4 @@
import { map, mapValues, merge } from 'lodash';
import { map, mapValues, merge } from 'lodash-es';
export function buildSelectors(
localSelectors,

View File

@ -1,5 +1,5 @@
import u from 'updeep';
import { mapValues } from 'lodash';
import { mapValues } from 'lodash-es';
export function buildUpreducer(
initial,

View File

@ -1,7 +1,7 @@
import u from 'updeep';
import add from 'lodash/fp/add.js';
import { Updux } from '.';
import { Updux } from './index.js';
test('README.md', () => {
const otherDux = new Updux({});

View File

@ -1,2 +0,0 @@
export { Updux } from './Updux';
export { action } from './actions';

3
src/index.ts Normal file
View File

@ -0,0 +1,3 @@
export { Updux, UpduxConfig } from './Updux.js';
export { action } from './actions.js';
export { AggregateDuxActions, AggregateDuxState } from './types.js';

View File

@ -1,4 +1,4 @@
import { Updux } from './Updux';
import { Updux } from './Updux.js';
test('initial', () => {
const foo = new Updux({

View File

@ -1,4 +1,4 @@
import { difference, omit } from 'lodash';
import { difference, omit } from 'lodash-es';
import { Updux } from './Updux';

View File

@ -1,5 +1,5 @@
import { Updux } from './Updux';
import { Action, action, ActionGenerator } from './actions';
import { Updux } from './Updux.js';
import { Action, action, ActionGenerator } from './actions.js';
import { expectAssignable, expectType, expectNotType } from 'tsd';
type SimplePayload = (x: number) => number;

View File

@ -1,4 +1,4 @@
import { Action, ActionGenerator } from './actions';
import { Action, ActionGenerator } from './actions.js';
export type Dict<T> = Record<string, T>;

View File

@ -4,9 +4,9 @@
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"target": "es2020",
"target": "es2015",
"lib": ["es2020"],
"module": "ES2020",
"module": "esnext",
"moduleResolution": "Node",
"strict": false,
"sourceMap": true,