tests are passing
This commit is contained in:
parent
aa130e33e5
commit
6168720c47
30
src/buildActions/index.ts
Normal file
30
src/buildActions/index.ts
Normal 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
17
src/buildCreateStore.ts
Normal 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
11
src/buildInitial/index.ts
Normal 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;
|
||||||
|
}
|
55
src/buildMutations/index.ts
Normal file
55
src/buildMutations/index.ts
Normal 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;
|
||||||
|
}
|
18
src/buildUpreducer/index.ts
Normal file
18
src/buildUpreducer/index.ts
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
151
src/index.ts
151
src/index.ts
@ -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
7
src/types.ts
Normal 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
78
src/updux.ts
Normal 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;
|
@ -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 */
|
||||||
|
Loading…
Reference in New Issue
Block a user