Merge branch 'mutation-maps'
This commit is contained in:
commit
73c2776826
2
.prettierignore
Normal file
2
.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
Changes
|
||||
.prettierignore
|
3
Changes
3
Changes
@ -1,5 +1,8 @@
|
||||
# Revision history for Updux
|
||||
|
||||
NEXT
|
||||
- Mutations passed to the constructor can be arrays of arrays.
|
||||
|
||||
1.2.0 2019-11-06
|
||||
- The middleware's 'getState' returns the local state of its updux,
|
||||
instead of the root state. Plus we add `getRootState` to get
|
||||
|
@ -1,21 +1,18 @@
|
||||
import Updux, { actionCreator } from './updux';
|
||||
import Updux, { actionCreator } from "./updux";
|
||||
|
||||
type MyState = {
|
||||
sum: number
|
||||
}
|
||||
sum: number;
|
||||
};
|
||||
|
||||
test( 'added mutation is present', () => {
|
||||
test("added mutation is present", () => {
|
||||
const updux = new Updux<MyState>({
|
||||
initial: { sum: 0 },
|
||||
initial: { sum: 0 }
|
||||
});
|
||||
|
||||
const add = actionCreator('add', (n : number) => ({n}) )
|
||||
const add = actionCreator("add", (n: number) => ({ n }));
|
||||
|
||||
updux.addMutation(add, ({ n }, action) => ({ sum }) => ({ sum: sum + n }));
|
||||
|
||||
// must add 'add' in the actions 9.9
|
||||
updux.addMutation(
|
||||
add, ({n},action) => ({sum}) => ({sum: sum + n})
|
||||
);
|
||||
updux.mutations;
|
||||
const store = updux.createStore();
|
||||
store.dispatch.add(3);
|
||||
|
||||
|
@ -1,28 +1,40 @@
|
||||
import fp from 'lodash/fp';
|
||||
import { Action, ActionCreator, ActionPayloadGenerator, Dictionary } from '../types';
|
||||
import fp from "lodash/fp";
|
||||
import {
|
||||
Action,
|
||||
ActionCreator,
|
||||
ActionPayloadGenerator,
|
||||
Dictionary
|
||||
} from "../types";
|
||||
|
||||
export function actionCreator<T extends string,P extends any>( type: T, transform: (...args: any[]) => P ): ActionCreator<T,P>
|
||||
export function actionCreator<T extends string>( type: T, transform: never ): ActionCreator<T,undefined>
|
||||
export function actionCreator<T extends string>( type: T, transform: null ): ActionCreator<T,null>
|
||||
export function actionCreator(type:any, transform:any ) {
|
||||
|
||||
if( transform ) {
|
||||
export function actionCreator<T extends string, P extends any>(
|
||||
type: T,
|
||||
transform: (...args: any[]) => P
|
||||
): ActionCreator<T, P>;
|
||||
export function actionCreator<T extends string>(
|
||||
type: T,
|
||||
transform: null
|
||||
): ActionCreator<T, null>;
|
||||
export function actionCreator<T extends string>(
|
||||
type: T
|
||||
): ActionCreator<T, undefined>;
|
||||
export function actionCreator(type: any, transform?: any) {
|
||||
if (transform) {
|
||||
return Object.assign(
|
||||
(...args: any[]) => ({ type, payload: transform(...args) }),
|
||||
{ type } )
|
||||
{ type }
|
||||
);
|
||||
}
|
||||
|
||||
if( transform === null ) {
|
||||
return Object.assign( () => ({ type }), { type } )
|
||||
if (transform === null) {
|
||||
return Object.assign(() => ({ type }), { type });
|
||||
}
|
||||
|
||||
return Object.assign( (payload: unknown) => ({type, payload}) );
|
||||
return Object.assign((payload: unknown) => ({ type, payload }), { type });
|
||||
}
|
||||
|
||||
function actionFor(type:string): ActionCreator {
|
||||
const f = ( (payload = undefined, meta = undefined) =>
|
||||
fp.pickBy(v => v !== undefined)({type, payload, meta}) as Action
|
||||
);
|
||||
export function actionFor(type: string): ActionCreator {
|
||||
const f = (payload = undefined, meta = undefined) =>
|
||||
fp.pickBy(v => v !== undefined)({ type, payload, meta }) as Action;
|
||||
|
||||
return Object.assign(f, {
|
||||
_genericAction: true,
|
||||
@ -30,27 +42,31 @@ function actionFor(type:string): ActionCreator {
|
||||
});
|
||||
}
|
||||
|
||||
type ActionPair = [ string, ActionCreator ];
|
||||
type ActionPair = [string, ActionCreator];
|
||||
|
||||
function buildActions(
|
||||
generators : Dictionary<ActionPayloadGenerator> = {},
|
||||
generators: Dictionary<ActionPayloadGenerator> = {},
|
||||
actionNames: string[] = [],
|
||||
subActions : ActionPair[] = [],
|
||||
):Dictionary<ActionCreator> {
|
||||
|
||||
subActions: ActionPair[] = []
|
||||
): Dictionary<ActionCreator> {
|
||||
// priority => generics => generic subs => craft subs => creators
|
||||
|
||||
const [ crafted, generic ] = fp.partition(
|
||||
([type,f]) => !f._genericAction
|
||||
)( subActions );
|
||||
const [crafted, generic] = fp.partition(([type, f]) => !f._genericAction)(
|
||||
subActions
|
||||
);
|
||||
|
||||
const actions : any = [
|
||||
...(actionNames.map( type => [ type, actionFor(type) ] )),
|
||||
const actions: any = [
|
||||
...actionNames.map(type => [type, actionFor(type)]),
|
||||
...generic,
|
||||
...crafted,
|
||||
...Object.entries(generators).map(
|
||||
([type, payload]: [ string, Function ]): any => [type, (payload as any).type ? payload : (...args: any) => ({ type, payload: payload(...args) })]
|
||||
),
|
||||
...Object.entries(
|
||||
generators
|
||||
).map(([type, payload]: [string, Function]): any => [
|
||||
type,
|
||||
(payload as any).type
|
||||
? payload
|
||||
: (...args: any) => ({ type, payload: payload(...args) })
|
||||
])
|
||||
];
|
||||
|
||||
return fp.fromPairs(actions);
|
||||
|
@ -1,18 +1,18 @@
|
||||
import fp from 'lodash/fp';
|
||||
import u from 'updeep';
|
||||
import {Mutation, Action, Dictionary} from '../types';
|
||||
import fp from "lodash/fp";
|
||||
import u from "updeep";
|
||||
import { Mutation, Action, Dictionary, MutationEntry } from "../types";
|
||||
|
||||
const composeMutations = (mutations: Mutation[]) =>
|
||||
mutations.reduce((m1, m2) => (payload: any = null, action: Action) => state =>
|
||||
m2(payload, action)(m1(payload, action)(state)),
|
||||
m2(payload, action)(m1(payload, action)(state))
|
||||
);
|
||||
|
||||
type SubMutations = {
|
||||
[ slice: string ]: Dictionary<Mutation>
|
||||
}
|
||||
[slice: string]: Dictionary<Mutation>;
|
||||
};
|
||||
|
||||
function buildMutations(
|
||||
mutations :Dictionary<Mutation|([Mutation,boolean|undefined])> = {},
|
||||
mutations: Dictionary<Mutation | [Mutation, boolean | undefined]> = {},
|
||||
subduxes = {}
|
||||
) {
|
||||
// we have to differentiate the subduxes with '*' than those
|
||||
@ -20,53 +20,49 @@ function buildMutations(
|
||||
|
||||
const actions = fp.uniq(
|
||||
Object.keys(mutations).concat(
|
||||
...Object.values(subduxes).map(({mutations = {}}:any) =>
|
||||
Object.keys(mutations),
|
||||
),
|
||||
),
|
||||
...Object.values(subduxes).map(({ mutations = {} }: any) =>
|
||||
Object.keys(mutations)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
let mergedMutations :Dictionary<Mutation[]> = {};
|
||||
let mergedMutations: Dictionary<Mutation[]> = {};
|
||||
|
||||
let [globby, nonGlobby] = fp.partition(
|
||||
([_, {mutations = {}}]:any) => mutations['*'],
|
||||
Object.entries(subduxes),
|
||||
([_, { mutations = {} }]: any) => mutations["*"],
|
||||
Object.entries(subduxes)
|
||||
);
|
||||
|
||||
globby = fp.flow([
|
||||
fp.fromPairs,
|
||||
fp.mapValues(({reducer}) => (_:any, action :Action) => ( state: any ) =>
|
||||
reducer(state, action),
|
||||
),
|
||||
fp.mapValues(({ reducer }) => (_: any, action: Action) => (state: any) =>
|
||||
reducer(state, action)
|
||||
)
|
||||
])(globby);
|
||||
|
||||
const globbyMutation = (payload:any, action:Action) =>
|
||||
u(fp.mapValues((mut:any) => mut(payload, action))(globby));
|
||||
const globbyMutation = (payload: any, action: Action) =>
|
||||
u(fp.mapValues((mut: any) => mut(payload, action))(globby));
|
||||
|
||||
actions.forEach(action => {
|
||||
mergedMutations[action] = [globbyMutation];
|
||||
});
|
||||
|
||||
nonGlobby.forEach(([slice, {mutations = {}, reducer = {}}]:any[]) => {
|
||||
nonGlobby.forEach(([slice, { mutations = {}, reducer = {} }]: any[]) => {
|
||||
Object.entries(mutations).forEach(([type, mutation]) => {
|
||||
const localized = (payload = null, action :Action) => {
|
||||
const localized = (payload = null, action: Action) => {
|
||||
return u.updateIn(slice)((mutation as Mutation)(payload, action));
|
||||
}
|
||||
};
|
||||
|
||||
mergedMutations[type].push(localized);
|
||||
});
|
||||
});
|
||||
|
||||
Object.entries(mutations).forEach(([type, mutation]) => {
|
||||
if ( Array.isArray(mutation) ) {
|
||||
if( mutation[1] ) {
|
||||
mergedMutations[type] = [
|
||||
mutation[0]
|
||||
]
|
||||
}
|
||||
else mergedMutations[type].push( mutation[0] );
|
||||
}
|
||||
else mergedMutations[type].push(mutation);
|
||||
if (Array.isArray(mutation)) {
|
||||
if (mutation[1]) {
|
||||
mergedMutations[type] = [mutation[0]];
|
||||
} else mergedMutations[type].push(mutation[0]);
|
||||
} else mergedMutations[type].push(mutation);
|
||||
});
|
||||
|
||||
return fp.mapValues(composeMutations)(mergedMutations);
|
||||
|
10
src/index.ts
10
src/index.ts
@ -1,8 +1,8 @@
|
||||
import Updux from './updux';
|
||||
import Updux from "./updux";
|
||||
|
||||
export { default as Updux } from './updux';
|
||||
export {
|
||||
UpduxConfig
|
||||
} from './types';
|
||||
export { default as Updux } from "./updux";
|
||||
export { UpduxConfig } from "./types";
|
||||
|
||||
export { actionCreator } from "./buildActions";
|
||||
|
||||
export default Updux;
|
||||
|
25
src/mutations.test.ts
Normal file
25
src/mutations.test.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import Updux, { actionCreator } from "./updux";
|
||||
|
||||
describe("as array of arrays", () => {
|
||||
const doIt = actionCreator("doIt");
|
||||
|
||||
const updux = new Updux({
|
||||
initial: "",
|
||||
mutations: [
|
||||
[doIt, () => () => "bingo"],
|
||||
["thisToo", () => () => "straight type"]
|
||||
]
|
||||
});
|
||||
|
||||
const store = updux.createStore();
|
||||
|
||||
test("doIt", () => {
|
||||
store.dispatch.doIt();
|
||||
expect(store.getState()).toEqual("bingo");
|
||||
});
|
||||
|
||||
test("straight type", () => {
|
||||
store.dispatch.thisToo();
|
||||
expect(store.getState()).toEqual("straight type");
|
||||
});
|
||||
});
|
33
src/types.ts
33
src/types.ts
@ -1,24 +1,30 @@
|
||||
import {Dispatch, Middleware} from 'redux';
|
||||
import { Dispatch, Middleware } from "redux";
|
||||
|
||||
type MaybePayload<P> = P extends object | string | boolean | number
|
||||
? {
|
||||
payload: P;
|
||||
}
|
||||
: {payload?: P};
|
||||
: { payload?: P };
|
||||
|
||||
export type Action<T extends string = string, P = any> = {
|
||||
type: T;
|
||||
} & MaybePayload<P>;
|
||||
|
||||
export type Dictionary<T> = {[key: string]: T};
|
||||
export type Dictionary<T> = { [key: string]: T };
|
||||
|
||||
export type Mutation<S = any, A extends Action = Action> = (
|
||||
payload: A['payload'],
|
||||
action: A,
|
||||
payload: A["payload"],
|
||||
action: A
|
||||
) => (state: S) => S;
|
||||
|
||||
export type ActionPayloadGenerator = (...args: any[]) => any;
|
||||
|
||||
export type MutationEntry = [
|
||||
ActionCreator | string,
|
||||
Mutation<any, Action<string, any>>,
|
||||
boolean?
|
||||
];
|
||||
|
||||
export type ActionCreator<T extends string = string, P = any> = {
|
||||
type: T;
|
||||
_genericAction?: boolean;
|
||||
@ -26,12 +32,11 @@ export type ActionCreator<T extends string = string, P = any> = {
|
||||
|
||||
export type UpduxDispatch = Dispatch & Dictionary<Function>;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration object given to Updux's constructor.
|
||||
* @typeparam S Type of the Updux's state. Defaults to `any`.
|
||||
*/
|
||||
export type UpduxConfig<S=any> = {
|
||||
export type UpduxConfig<S = any> = {
|
||||
/**
|
||||
* The default initial state of the reducer. Can be anything your
|
||||
* heart desires.
|
||||
@ -155,7 +160,7 @@ export type UpduxConfig<S=any> = {
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
mutations?: any;
|
||||
mutations?: { [actionType: string]: Mutation<S> } | MutationEntry[];
|
||||
|
||||
groomMutations?: (m: Mutation<S>) => Mutation<S>;
|
||||
|
||||
@ -184,10 +189,10 @@ export type UpduxConfig<S=any> = {
|
||||
export type Upreducer<S = any> = (action: Action) => (state: S) => S;
|
||||
|
||||
export interface UpduxMiddlewareAPI<S> {
|
||||
dispatch: UpduxDispatch,
|
||||
getState(): any,
|
||||
getRootState(): S
|
||||
|
||||
dispatch: UpduxDispatch;
|
||||
getState(): any;
|
||||
getRootState(): S;
|
||||
}
|
||||
export type UpduxMiddleware<S=any> = (api: UpduxMiddlewareAPI<S> ) => ( next: UpduxDispatch ) => ( action: Action ) => any;
|
||||
|
||||
export type UpduxMiddleware<S = any> = (
|
||||
api: UpduxMiddlewareAPI<S>
|
||||
) => (next: UpduxDispatch) => (action: Action) => any;
|
||||
|
101
src/updux.ts
101
src/updux.ts
@ -1,14 +1,14 @@
|
||||
import fp from 'lodash/fp';
|
||||
import u from 'updeep';
|
||||
import {observable, computed, toJS} from 'mobx';
|
||||
import fp from "lodash/fp";
|
||||
import u from "updeep";
|
||||
import { observable, computed, toJS } from "mobx";
|
||||
|
||||
import buildActions from './buildActions';
|
||||
import buildInitial from './buildInitial';
|
||||
import buildMutations from './buildMutations';
|
||||
import buildActions, { actionFor } from "./buildActions";
|
||||
import buildInitial from "./buildInitial";
|
||||
import buildMutations from "./buildMutations";
|
||||
|
||||
import buildCreateStore from './buildCreateStore';
|
||||
import buildMiddleware from './buildMiddleware';
|
||||
import buildUpreducer from './buildUpreducer';
|
||||
import buildCreateStore from "./buildCreateStore";
|
||||
import buildMiddleware from "./buildMiddleware";
|
||||
import buildUpreducer from "./buildUpreducer";
|
||||
import {
|
||||
UpduxConfig,
|
||||
Dictionary,
|
||||
@ -17,29 +17,30 @@ import {
|
||||
Mutation,
|
||||
Upreducer,
|
||||
UpduxDispatch,
|
||||
UpduxMiddleware
|
||||
} from './types';
|
||||
UpduxMiddleware,
|
||||
MutationEntry
|
||||
} from "./types";
|
||||
|
||||
import {Middleware, Store} from 'redux';
|
||||
export {actionCreator} from './buildActions';
|
||||
import { Middleware, Store } from "redux";
|
||||
export { actionCreator } from "./buildActions";
|
||||
|
||||
type StoreWithDispatchActions<
|
||||
S = any,
|
||||
Actions = {[action: string]: (...args: any) => Action}
|
||||
Actions = { [action: string]: (...args: any) => Action }
|
||||
> = Store<S> & {
|
||||
dispatch: {[type in keyof Actions]: (...args: any) => void};
|
||||
dispatch: { [type in keyof Actions]: (...args: any) => void };
|
||||
};
|
||||
|
||||
export type Dux<S> = Pick<
|
||||
Updux<S>,
|
||||
| 'subduxes'
|
||||
| 'actions'
|
||||
| 'initial'
|
||||
| 'mutations'
|
||||
| 'reducer'
|
||||
| 'middleware'
|
||||
| 'createStore'
|
||||
| 'upreducer'
|
||||
| "subduxes"
|
||||
| "actions"
|
||||
| "initial"
|
||||
| "mutations"
|
||||
| "reducer"
|
||||
| "middleware"
|
||||
| "createStore"
|
||||
| "upreducer"
|
||||
>;
|
||||
|
||||
/**
|
||||
@ -98,35 +99,36 @@ export class Updux<S = any> {
|
||||
*/
|
||||
groomMutations: (mutation: Mutation<S>) => Mutation<S>;
|
||||
|
||||
@observable private localEffects: Dictionary<
|
||||
UpduxMiddleware<S>
|
||||
>;
|
||||
@observable private localEffects: Dictionary<UpduxMiddleware<S>>;
|
||||
|
||||
@observable private localActions: Dictionary<ActionCreator>;
|
||||
|
||||
@observable private localMutations: Dictionary<
|
||||
Mutation<S> | [Mutation<S>, boolean | undefined]
|
||||
>;
|
||||
> = {};
|
||||
|
||||
constructor(config: UpduxConfig = {}) {
|
||||
this.groomMutations = config.groomMutations || ((x: Mutation<S>) => x);
|
||||
|
||||
this.subduxes = fp.mapValues((value: UpduxConfig | Updux) =>
|
||||
fp.isPlainObject(value) ? new Updux(value) : value,
|
||||
)(fp.getOr({}, 'subduxes', config)) as Dictionary<Updux>;
|
||||
fp.isPlainObject(value) ? new Updux(value) : value
|
||||
)(fp.getOr({}, "subduxes", config)) as Dictionary<Updux>;
|
||||
|
||||
this.localActions = fp.getOr({}, 'actions', config);
|
||||
this.localActions = fp.getOr({}, "actions", config);
|
||||
|
||||
this.localEffects = fp.getOr({}, 'effects', config);
|
||||
this.localEffects = fp.getOr({}, "effects", config);
|
||||
|
||||
this.initial = buildInitial<any>(
|
||||
config.initial,
|
||||
fp.mapValues(({initial}) => initial)(this.subduxes),
|
||||
fp.mapValues(({ initial }) => initial)(this.subduxes)
|
||||
);
|
||||
|
||||
this.localMutations = fp.mapValues((m: Mutation<S>) =>
|
||||
this.groomMutations(m),
|
||||
)(fp.getOr({}, 'mutations', config));
|
||||
let mutations = fp.getOr([], "mutations", config);
|
||||
if (!Array.isArray(mutations)) {
|
||||
mutations = fp.toPairs(mutations);
|
||||
}
|
||||
|
||||
mutations.forEach(args => (this.addMutation as any)(...args));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,11 +140,7 @@ export class Updux<S = any> {
|
||||
* alongside `getState` to get the root state.
|
||||
*/
|
||||
@computed get middleware(): UpduxMiddleware<S> {
|
||||
return buildMiddleware(
|
||||
this.localEffects,
|
||||
this.actions,
|
||||
this.subduxes,
|
||||
);
|
||||
return buildMiddleware(this.localEffects, this.actions, this.subduxes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -164,10 +162,10 @@ export class Updux<S = any> {
|
||||
this.localActions,
|
||||
[...Object.keys(this.localMutations), ...Object.keys(this.localEffects)],
|
||||
fp.flatten(
|
||||
Object.values(this.subduxes).map(({actions}: Updux) =>
|
||||
Object.entries(actions),
|
||||
),
|
||||
),
|
||||
Object.values(this.subduxes).map(({ actions }: Updux) =>
|
||||
Object.entries(actions)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -245,7 +243,7 @@ export class Updux<S = any> {
|
||||
this.reducer,
|
||||
this.initial,
|
||||
this.middleware as Middleware,
|
||||
this.actions,
|
||||
this.actions
|
||||
) as () => StoreWithDispatchActions<S, typeof actions>;
|
||||
}
|
||||
|
||||
@ -264,7 +262,7 @@ export class Updux<S = any> {
|
||||
actions: this.actions,
|
||||
reducer: this.reducer,
|
||||
mutations: this.mutations,
|
||||
initial: this.initial,
|
||||
initial: this.initial
|
||||
};
|
||||
}
|
||||
|
||||
@ -283,12 +281,15 @@ export class Updux<S = any> {
|
||||
addMutation<A extends ActionCreator>(
|
||||
creator: A,
|
||||
mutation: Mutation<S, A extends (...args: any[]) => infer R ? R : never>,
|
||||
isSink?: boolean,
|
||||
isSink?: boolean
|
||||
) {
|
||||
this.localActions[creator.type] = creator;
|
||||
this.localMutations[creator.type] = [
|
||||
let c = fp.isFunction(creator) ? creator : actionFor(creator);
|
||||
|
||||
this.localActions[c.type] = c;
|
||||
|
||||
this.localMutations[c.type] = [
|
||||
this.groomMutations(mutation as any) as Mutation<S>,
|
||||
isSink,
|
||||
isSink
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user