middleware work

This commit is contained in:
Yanick Champoux 2019-10-20 11:30:36 -04:00
parent 64e89dc046
commit 332a1cdec7
4 changed files with 176 additions and 25 deletions

24
src/actions.test.js Normal file
View File

@ -0,0 +1,24 @@
import updux from '.';
import u from 'updeep';
test( 'actions defined in effects and mutations, multi-level', () => {
const { actions } = updux({
effects: {
foo: api => next => action => { },
},
mutations: { bar: () => () => null },
subduxes: {
mysub: updux({
effects: { baz: api => next => action => { }, },
mutations: { quux: () => () => null },
})
},
});
const types = Object.keys(actions);
types.sort();
expect( types).toEqual([ 'bar', 'baz', 'foo', 'quux', ]);
});

29
src/buildMiddleware.js Normal file
View File

@ -0,0 +1,29 @@
import fp from 'lodash/fp';
const MiddlewareFor = (type,mw) => api => next => action => {
if (type !== '*' && action.type !== type) return next(action);
return mw(api)(next)(action);
};
export default function buildMiddleware(
{effects = {}, subduxes = {}},
{actions},
) {
return api => {
for (let type in actions) {
api.dispatch[type] = (...args) => api.dispatch(actions[type](...args));
}
return original_next => {
return [
...fp.toPairs(effects).map(([type, effect]) =>
MiddlewareFor(type,effect)
),
...fp.map('middleware', subduxes),
]
.filter(x => x)
.reduceRight((next, mw) => mw(api)(next), original_next);
};
};
}

View File

@ -3,6 +3,8 @@ import u from 'updeep';
import { createStore, applyMiddleware } from 'redux'; import { createStore, applyMiddleware } from 'redux';
import buildMiddleware from './buildMiddleware';
function actionFor(type) { function actionFor(type) {
return (payload = null, meta = null) => { return (payload = null, meta = null) => {
return fp.pickBy(v => v !== null)({type, payload, meta}); return fp.pickBy(v => v !== null)({type, payload, meta});
@ -22,7 +24,7 @@ function buildInitial({initial = {}, subduxes = {}}) {
return initial; return initial;
} }
function buildActions({mutations = {}, subduxes = {}}) { function buildActions({mutations = {}, effects = {}, subduxes = {}}) {
let actions = fp.mergeAll(fp.map(fp.getOr({}, 'actions'), subduxes)) || {}; let actions = fp.mergeAll(fp.map(fp.getOr({}, 'actions'), subduxes)) || {};
Object.keys(mutations).forEach(type => { Object.keys(mutations).forEach(type => {
@ -31,6 +33,12 @@ function buildActions({mutations = {}, subduxes = {}}) {
} }
}); });
Object.keys(effects).forEach(type => {
if (!actions[type]) {
actions[type] = actionFor(type);
}
});
return actions; return actions;
} }
@ -44,13 +52,13 @@ function buildMutations({mutations = {}, subduxes = {}}) {
// without, as the root '*' is not the same as any sub-'*' // without, as the root '*' is not the same as any sub-'*'
const actions = fp.uniq( Object.keys(mutations).concat( const actions = fp.uniq( Object.keys(mutations).concat(
...Object.values( subduxes ).map( ({mutations}) => Object.keys(mutations) ) ...Object.values( subduxes ).map( ({mutations = {}}) => Object.keys(mutations) )
) ); ) );
let mergedMutations = {}; let mergedMutations = {};
let [ globby, nonGlobby ] = fp.partition( let [ globby, nonGlobby ] = fp.partition(
([_,{mutations}]) => mutations['*'], ([_,{mutations={}}]) => mutations['*'],
Object.entries(subduxes) Object.entries(subduxes)
); );
@ -66,7 +74,7 @@ function buildMutations({mutations = {}, subduxes = {}}) {
mergedMutations[action] = [ globbyMutation ] mergedMutations[action] = [ globbyMutation ]
}); });
nonGlobby.forEach( ([slice, {mutations,reducer}]) => { nonGlobby.forEach( ([slice, {mutations={},reducer={}}]) => {
Object.entries(mutations).forEach(([type,mutation]) => { Object.entries(mutations).forEach(([type,mutation]) => {
const localized = (payload=null,action={}) => u.updateIn( slice, mutation(payload,action) ); const localized = (payload=null,action={}) => u.updateIn( slice, mutation(payload,action) );
@ -82,27 +90,6 @@ function buildMutations({mutations = {}, subduxes = {}}) {
} }
function buildMiddleware({effects={},subduxes={}},{actions}) {
return api => {
for ( let type in actions ) {
api.dispatch[type] = (...args) => api.dispatch( actions[type](...args) );
}
return original_next => {
return [
...fp.toPairs(effects).map(([type,effect])=> {
return api => next => action => {
if( action.type !== type ) return next(action);
return effect(api)(next)(action);
};
}),
...fp.map( 'middleware', subduxes )
].filter(x=>x).reduceRight( (next,mw) => mw(api)(next), original_next )
}}
}
function updux(config) { function updux(config) {
const dux = {}; const dux = {};

111
src/middleware.test.js Normal file
View File

@ -0,0 +1,111 @@
import updux from '.';
import u from 'updeep';
test( 'simple effect', () => {
const tracer = jest.fn();
const store = updux({
effects: {
foo: api => next => action => {
tracer();
next(action);
},
},
}).createStore();
expect(tracer).not.toHaveBeenCalled();
store.dispatch({ type: 'bar' });
expect(tracer).not.toHaveBeenCalled();
store.dispatch.foo();
expect(tracer).toHaveBeenCalled();
});
test( 'effect and sub-effect', () => {
const tracer = jest.fn();
const tracerEffect = signature => api => next => action => {
tracer(signature);
next(action);
};
const store = updux({
effects: {
foo: tracerEffect('root'),
},
subduxes: {
zzz: updux({effects: {
foo: tracerEffect('child'),
}})
},
}).createStore();
expect(tracer).not.toHaveBeenCalled();
store.dispatch({ type: 'bar' });
expect(tracer).not.toHaveBeenCalled();
store.dispatch.foo();
expect(tracer).toHaveBeenNthCalledWith(1,'root');
expect(tracer).toHaveBeenNthCalledWith(2,'child');
});
test( '"*" effect', () => {
const tracer = jest.fn();
const store = updux({
effects: {
'*': api => next => action => {
tracer();
next(action);
},
},
}).createStore();
expect(tracer).not.toHaveBeenCalled();
store.dispatch({ type: 'bar' });
expect(tracer).toHaveBeenCalled();
});
test( 'async effect', async () => {
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const tracer = jest.fn();
const store = updux({
effects: {
foo: api => next => async action => {
next(action);
await timeout(1000);
tracer();
},
},
}).createStore();
expect(tracer).not.toHaveBeenCalled();
store.dispatch.foo();
expect(tracer).not.toHaveBeenCalled();
await timeout(1000);
expect(tracer).toHaveBeenCalled();
});