tests are passing

This commit is contained in:
Yanick Champoux 2019-10-21 17:17:56 -04:00
parent aa130e33e5
commit 6168720c47
9 changed files with 236 additions and 135 deletions

30
src/buildActions/index.ts Normal file
View File

@ -0,0 +1,30 @@
import fp from 'lodash/fp';
function actionFor(type) {
return (payload = null, meta = null) => {
return fp.pickBy(v => v !== null)({type, payload, meta});
};
}
export default function buildActions(
mutations = {},
effects = {},
subActions = {},
) {
let actions = { ...subActions };
Object.keys(mutations).forEach(type => {
if (!actions[type]) {
actions[type] = actionFor(type);
}
});
Object.keys(effects).forEach(type => {
if (!actions[type]) {
actions[type] = actionFor(type);
}
});
return actions;
}

17
src/buildCreateStore.ts Normal file
View File

@ -0,0 +1,17 @@
import { createStore as reduxCreateStore, applyMiddleware } from 'redux';
export default function buildCreateStore( reducer, initial, middleware,
actions ) {
return () => {
const store = reduxCreateStore( reducer, initial,
applyMiddleware( middleware)
);
for ( let a in actions ) {
store.dispatch[a] = (...args) => {
store.dispatch(actions[a](...args))
};
}
return store as any;
}
};

11
src/buildInitial/index.ts Normal file
View File

@ -0,0 +1,11 @@
import fp from 'lodash/fp';
export default function buildInitial(initial : any = {}, subduxes = {}) {
let state = initial;
if (fp.isPlainObject(initial)) {
initial = fp.mergeAll([ subduxes, initial, ]);
}
return initial;
}

View File

@ -0,0 +1,55 @@
import fp from 'lodash/fp';
import u from 'updeep';
import { Mutation, Mutations } from '../types';
const composeMutations = (mutations:Mutation[]) =>
mutations.reduce( (m1,m2) =>
(payload=null,action={}) => state => m2(payload,action)(
m1(payload,action)(state) ));
export default function buildMutations(mutations = {}, subduxes= {}) :Mutations{
// we have to differentiate the subduxes with '*' than those
// without, as the root '*' is not the same as any sub-'*'
const actions = fp.uniq( Object.keys(mutations).concat(
...Object.values( subduxes ).map( ({mutations = {}}:any) => Object.keys(mutations) )
) );
let mergedMutations = {};
let [ globby, nonGlobby ] = fp.partition(
([_,{mutations={}}]:any) => mutations['*'],
Object.entries(subduxes)
);
globby =
fp.flow([
fp.fromPairs,
fp.mapValues(
({reducer}) => (_,action={}) => state =>
reducer(state,action) ),
])(globby);
const globbyMutation = (payload,action) => u(
fp.mapValues( (mut:any) => mut(payload,action) )(globby)
);
actions.forEach( action => {
mergedMutations[action] = [ globbyMutation ]
});
nonGlobby.forEach( ([slice, {mutations={},reducer={}}]:any) => {
Object.entries(mutations).forEach(([type,mutation]) => {
const localized = (payload=null,action={}) => u.updateIn( slice )( (mutation as any)(payload,action) );
mergedMutations[type].push(localized);
})
});
Object.entries(mutations).forEach(([type,mutation]) => {
mergedMutations[type].push(mutation);
});
return fp.mapValues( composeMutations )(mergedMutations) as Mutations;
}

View File

@ -0,0 +1,18 @@
import fp from 'lodash/fp';
import {Mutations} from '../types';
type Upreducer = <S>(action:any) => (state:S) => S;
export default function buildUpreducer<S>(initial: S, mutations: Mutations): Upreducer {
return (action = {}) => (state:any) => {
if (state === null) state = initial;
const a =
mutations[(action as any).type] ||
mutations['*'];
if(!a) return state;
return a((action as any).payload, action)(state) as S;
};
}

View File

@ -1,149 +1,34 @@
import fp from 'lodash/fp'; import fp from 'lodash/fp';
import u from 'updeep'; import u from 'updeep';
import { createStore as reduxCreateStore, applyMiddleware } from 'redux'; import Updux from './updux';
import buildMiddleware from './buildMiddleware';
function actionFor(type) {
return (payload = null, meta = null) => {
return fp.pickBy(v => v !== null)({type, payload, meta});
};
}
function buildInitial({initial = {}, subduxes = {}}) {
let state = initial;
if (fp.isPlainObject(initial)) {
initial = fp.mergeAll([
fp.mapValues(fp.getOr({}, 'initial'), subduxes),
initial,
]);
}
return initial;
}
function buildActions({mutations = {}, effects = {}, subduxes = {}}) {
let actions = fp.mergeAll(fp.map(fp.getOr({}, 'actions'), subduxes)) || {};
Object.keys(mutations).forEach(type => {
if (!actions[type]) {
actions[type] = actionFor(type);
}
});
Object.keys(effects).forEach(type => {
if (!actions[type]) {
actions[type] = actionFor(type);
}
});
return actions;
}
const composeMutations = mutations =>
mutations.reduce( (m1,m2) =>
(payload=null,action={}) => state => m2(payload,action)(
m1(payload,action)(state) ));
function buildMutations({mutations = {}, subduxes= {}}: any) {
// we have to differentiate the subduxes with '*' than those
// without, as the root '*' is not the same as any sub-'*'
const actions = fp.uniq( Object.keys(mutations).concat(
...Object.values( subduxes ).map( ({mutations = {}}:any) => Object.keys(mutations) )
) );
let mergedMutations = {};
let [ globby, nonGlobby ] = fp.partition(
([_,{mutations={}}]:any) => mutations['*'],
Object.entries(subduxes)
);
globby =
fp.flow([
fp.fromPairs,
fp.mapValues(
({reducer}) => (_,action={}) => state =>
reducer(state,action) ),
])(globby);
const globbyMutation = (payload,action) => u(
fp.mapValues( (mut:any) => mut(payload,action) )(globby)
);
actions.forEach( action => {
mergedMutations[action] = [ globbyMutation ]
});
nonGlobby.forEach( ([slice, {mutations={},reducer={}}]:any) => {
Object.entries(mutations).forEach(([type,mutation]) => {
const localized = (payload=null,action={}) => u.updateIn( slice )( (mutation as any)(payload,action) );
mergedMutations[type].push(localized);
})
});
Object.entries(mutations).forEach(([type,mutation]) => {
mergedMutations[type].push(mutation);
});
return fp.mapValues( composeMutations )(mergedMutations);
}
function updux(config) { function updux(config) {
const actions = buildActions(config); return new Updux(config);
// const actions = buildActions(
// config.mutations,
// config.effects,
// fp.flatten( ( config.subduxes||{}).map( ({ actions }) => actions ) )
// );
const initial = buildInitial(config); // const initial = buildInitial(config);
const mutations = buildMutations(config); // const mutations = buildMutations(config.mutations,config.subduxes);
const upreducer = (action={}) => state => {
if (state === null) state = initial;
const a =
mutations[(action as any).type] ||
mutations['*'] ||
(() => state => state);
return a((action as any).payload, action)(state); // return {
}; // reducer,
// upreducer,
const reducer = (state, action) => { // middleware,
return upreducer(action)(state); // createStore,
}; // actions: ( actions as any ),
// mutations,
const middleware = buildMiddleware( // initial,
config.effects, // };
actions,
config.subduxes,
);
const createStore = () => {
const store = reduxCreateStore( reducer, initial,
applyMiddleware( middleware)
);
for ( let a in actions ) {
store.dispatch[a] = (...args) => {
store.dispatch(actions[a](...args))
};
}
return store;
}
return {
reducer,
upreducer,
middleware,
createStore,
actions,
mutations,
initial,
};
} }
export default updux; export default updux;

7
src/types.ts Normal file
View File

@ -0,0 +1,7 @@
export type Dictionary<T> = { [key: string]: T };
export type Mutation = (payload: any, action: any) => (state:any) => any;
export type Mutations = Dictionary<Mutation>;

78
src/updux.ts Normal file
View File

@ -0,0 +1,78 @@
import fp from 'lodash/fp';
import buildActions from './buildActions';
import buildInitial from './buildInitial';
import buildMutations from './buildMutations';
import { Dictionary, Mutation } from './types';
import buildCreateStore from './buildCreateStore';
import buildMiddleware from './buildMiddleware';
import buildUpreducer from './buildUpreducer';
type UpduxConfig = {
initial?: any,
mutations?: any,
effects?: any,
subduxes?: {
[ slice: string ]: UpduxConfig | Updux
}
};
export class Updux {
actions: any;
subduxes: Dictionary<Updux>;
initial: any;
mutations: Dictionary<Mutation>;
createStore: Function;
upreducer: (action:any)=>(state:any)=>any;
reducer: <S>(state:S,action:any) => S;
middleware: (api:any) => (next: Function) => (action: any) => any;
constructor(config: UpduxConfig) {
this.subduxes = fp.mapValues(
value => fp.isPlainObject(value) ? new Updux(value as UpduxConfig) : value )(fp.getOr({},'subduxes',config)
) as Dictionary<Updux>;
this.actions = buildActions(
config.mutations,
config.effects,
fp.mergeAll( Object.values( this.subduxes ).map( ({ actions }) =>
actions ) )
)
this.initial = buildInitial(
config.initial, fp.mapValues( ({initial}) => initial )(this.subduxes)
);
this.mutations = buildMutations(
config.mutations, this.subduxes
);
this.upreducer = buildUpreducer(
this.initial, this.mutations
);
this.reducer = (state,action) => {
return this.upreducer(action)(state);
}
this.middleware = buildMiddleware(
config.effects,
this.actions,
config.subduxes,
);
this.createStore = buildCreateStore(this.reducer,this.initial,this.middleware,this.actions);
}
}
export default Updux;

View File

@ -4,7 +4,7 @@
"incremental": true, /* Enable incremental compilation */ "incremental": true, /* Enable incremental compilation */
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"lib": [ "dom", "es2016" ], /* Specify library files to be included in the compilation. */ "lib": [ "dom", "es2017" ], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */ // "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */ // "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
@ -12,7 +12,7 @@
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */ // "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */ "outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */ // "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */