feat!: middleware support refined
This commit is contained in:
parent
73c2776826
commit
d90d72148c
@ -1,12 +1,12 @@
|
||||
import Updux from '.';
|
||||
import u from 'updeep';
|
||||
import Updux from ".";
|
||||
import u from "updeep";
|
||||
|
||||
const noopEffect = () => () => () => {};
|
||||
|
||||
test('actions defined in effects and mutations, multi-level', () => {
|
||||
test.only("actions defined in effects and mutations, multi-level", () => {
|
||||
const { actions } = new Updux({
|
||||
effects: {
|
||||
foo: noopEffect,
|
||||
foo: noopEffect
|
||||
},
|
||||
mutations: { bar: () => () => null },
|
||||
subduxes: {
|
||||
@ -14,25 +14,25 @@ test('actions defined in effects and mutations, multi-level', () => {
|
||||
effects: { baz: noopEffect },
|
||||
mutations: { quux: () => () => null },
|
||||
actions: {
|
||||
foo: (limit:number) => ({limit}),
|
||||
},
|
||||
foo: (limit: number) => ({ limit })
|
||||
}
|
||||
},
|
||||
myothersub: {
|
||||
effects: {
|
||||
foo: noopEffect,
|
||||
},
|
||||
},
|
||||
},
|
||||
foo: noopEffect
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const types = Object.keys(actions);
|
||||
types.sort();
|
||||
|
||||
expect(types).toEqual(['bar', 'baz', 'foo', 'quux']);
|
||||
expect(types).toEqual(["bar", "baz", "foo", "quux"]);
|
||||
|
||||
expect(actions.bar()).toEqual({type: 'bar'});
|
||||
expect(actions.bar('xxx')).toEqual({type: 'bar', payload: 'xxx'});
|
||||
expect(actions.bar(undefined, 'yyy')).toEqual({type: 'bar', meta: 'yyy'});
|
||||
expect(actions.bar()).toEqual({ type: "bar" });
|
||||
expect(actions.bar("xxx")).toEqual({ type: "bar", payload: "xxx" });
|
||||
expect(actions.bar(undefined, "yyy")).toEqual({ type: "bar", meta: "yyy" });
|
||||
|
||||
expect(actions.foo(12)).toEqual({type: 'foo', payload: {limit: 12}});
|
||||
expect(actions.foo(12)).toEqual({ type: "foo", payload: { limit: 12 } });
|
||||
});
|
||||
|
@ -44,32 +44,14 @@ export function actionFor(type: string): ActionCreator {
|
||||
|
||||
type ActionPair = [string, ActionCreator];
|
||||
|
||||
function buildActions(
|
||||
generators: Dictionary<ActionPayloadGenerator> = {},
|
||||
actionNames: string[] = [],
|
||||
subActions: ActionPair[] = []
|
||||
): Dictionary<ActionCreator> {
|
||||
function buildActions(actions: ActionPair[] = []): Dictionary<ActionCreator> {
|
||||
// priority => generics => generic subs => craft subs => creators
|
||||
|
||||
const [crafted, generic] = fp.partition(([type, f]) => !f._genericAction)(
|
||||
subActions
|
||||
fp.compact(actions)
|
||||
);
|
||||
|
||||
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) })
|
||||
])
|
||||
];
|
||||
|
||||
return fp.fromPairs(actions);
|
||||
return fp.fromPairs([...generic, ...crafted]);
|
||||
}
|
||||
|
||||
export default buildActions;
|
||||
|
@ -1,10 +1,22 @@
|
||||
import fp from 'lodash/fp';
|
||||
import fp from "lodash/fp";
|
||||
|
||||
import { Middleware, MiddlewareAPI, Dispatch } from 'redux';
|
||||
import { Dictionary, ActionCreator, Action, UpduxDispatch, UpduxMiddleware, UpduxMiddlewareAPI } from '../types';
|
||||
import { Middleware, MiddlewareAPI, Dispatch } from "redux";
|
||||
import {
|
||||
Dictionary,
|
||||
ActionCreator,
|
||||
Action,
|
||||
UpduxDispatch,
|
||||
UpduxMiddleware,
|
||||
UpduxMiddlewareAPI,
|
||||
EffectEntry
|
||||
} from "../types";
|
||||
|
||||
const MiddlewareFor = (type: any, mw: Middleware ): Middleware => api => next => action => {
|
||||
if (type !== '*' && action.type !== type) return next(action);
|
||||
const MiddlewareFor = (
|
||||
type: any,
|
||||
mw: Middleware
|
||||
): Middleware => api => next => action => {
|
||||
if (!["*", "^", "$"].includes(type) && action.type !== type)
|
||||
return next(action);
|
||||
|
||||
return mw(api)(next)(action);
|
||||
};
|
||||
@ -12,43 +24,34 @@ const MiddlewareFor = (type: any, mw: Middleware ): Middleware => api => next =>
|
||||
type Next = (action: Action) => any;
|
||||
|
||||
function sliceMw(slice: string, mw: Middleware): Middleware {
|
||||
return (api) => {
|
||||
const getSliceState = () => fp.get(slice, api.getState() );
|
||||
return api => {
|
||||
const getSliceState =
|
||||
slice.length > 0 ? () => fp.get(slice, api.getState()) : api.getState;
|
||||
const getRootState = (api as any).getRootState || api.getState;
|
||||
return mw({...api, getState: getSliceState, getRootState} as any )
|
||||
return mw({ ...api, getState: getSliceState, getRootState } as any);
|
||||
};
|
||||
}
|
||||
|
||||
function buildMiddleware<S = any>(
|
||||
effects : Dictionary<UpduxMiddleware<S>>= {},
|
||||
actions : Dictionary<ActionCreator>= {},
|
||||
subduxes :any = {},
|
||||
): UpduxMiddleware<S>
|
||||
{
|
||||
|
||||
const subMiddlewares = fp.flow(
|
||||
fp.mapValues( fp.get('middleware') ),
|
||||
fp.toPairs,
|
||||
fp.filter(x=>x[1]),
|
||||
fp.map( ([ slice, mw ]: [ string, Middleware]) => sliceMw(slice,mw) )
|
||||
)( subduxes );
|
||||
middlewareEntries: any[] = [],
|
||||
actions: Dictionary<ActionCreator> = {}
|
||||
): UpduxMiddleware<S> {
|
||||
let mws = middlewareEntries
|
||||
.map(([slice, actionType, mw, isGen]: any) =>
|
||||
isGen ? [slice, actionType, mw()] : [slice, actionType, mw]
|
||||
)
|
||||
.map(([slice, actionType, mw]) =>
|
||||
MiddlewareFor(actionType, sliceMw(slice, mw))
|
||||
);
|
||||
|
||||
return (api: UpduxMiddlewareAPI<S>) => {
|
||||
|
||||
for (let type in actions) {
|
||||
const ac = actions[type];
|
||||
api.dispatch[type] = (...args: any[]) => api.dispatch(ac(...args));
|
||||
}
|
||||
|
||||
return (original_next: Next) => {
|
||||
return [
|
||||
...fp.toPairs(effects).map(([type, effect]) =>
|
||||
MiddlewareFor(type,effect as Middleware)
|
||||
),
|
||||
...subMiddlewares
|
||||
]
|
||||
.filter(x => x)
|
||||
.reduceRight((next, mw) => mw(api)(next), original_next);
|
||||
return mws.reduceRight((next, mw) => mw(api)(next), original_next);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Updux from '.';
|
||||
import u from 'updeep';
|
||||
import Updux, { actionCreator } from ".";
|
||||
import u from "updeep";
|
||||
|
||||
test('simple effect', () => {
|
||||
test("simple effect", () => {
|
||||
const tracer = jest.fn();
|
||||
|
||||
const store = new Updux({
|
||||
@ -9,13 +9,13 @@ test('simple effect', () => {
|
||||
foo: (api: any) => (next: any) => (action: any) => {
|
||||
tracer();
|
||||
next(action);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}).createStore();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch({type: 'bar'});
|
||||
store.dispatch({ type: "bar" });
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
@ -24,11 +24,11 @@ test('simple effect', () => {
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('effect and sub-effect', () => {
|
||||
test("effect and sub-effect", () => {
|
||||
const tracer = jest.fn();
|
||||
|
||||
const tracerEffect = (signature: string) => (api: any) => (next: any) => (
|
||||
action: any,
|
||||
action: any
|
||||
) => {
|
||||
tracer(signature);
|
||||
next(action);
|
||||
@ -36,27 +36,27 @@ test('effect and sub-effect', () => {
|
||||
|
||||
const store = new Updux({
|
||||
effects: {
|
||||
foo: tracerEffect('root'),
|
||||
foo: tracerEffect("root")
|
||||
},
|
||||
subduxes: {
|
||||
zzz: {
|
||||
effects: {
|
||||
foo: tracerEffect('child'),
|
||||
},
|
||||
},
|
||||
},
|
||||
foo: tracerEffect("child")
|
||||
}
|
||||
}
|
||||
}
|
||||
}).createStore();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch({type: 'bar'});
|
||||
store.dispatch({ type: "bar" });
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch.foo();
|
||||
|
||||
expect(tracer).toHaveBeenNthCalledWith(1, 'root');
|
||||
expect(tracer).toHaveBeenNthCalledWith(2, 'child');
|
||||
expect(tracer).toHaveBeenNthCalledWith(1, "root");
|
||||
expect(tracer).toHaveBeenNthCalledWith(2, "child");
|
||||
});
|
||||
|
||||
test('"*" effect', () => {
|
||||
@ -64,21 +64,21 @@ test('"*" effect', () => {
|
||||
|
||||
const store = new Updux({
|
||||
effects: {
|
||||
'*': api => next => action => {
|
||||
"*": api => next => action => {
|
||||
tracer();
|
||||
next(action);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}).createStore();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch({type: 'bar'});
|
||||
store.dispatch({ type: "bar" });
|
||||
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('async effect', async () => {
|
||||
test("async effect", async () => {
|
||||
function timeout(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
@ -91,8 +91,8 @@ test('async effect', async () => {
|
||||
next(action);
|
||||
await timeout(1000);
|
||||
tracer();
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}).createStore();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
@ -106,7 +106,7 @@ test('async effect', async () => {
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('getState is local', () => {
|
||||
test("getState is local", () => {
|
||||
let childState;
|
||||
let rootState;
|
||||
let rootFromChild;
|
||||
@ -118,8 +118,8 @@ test('getState is local', () => {
|
||||
childState = getState();
|
||||
rootFromChild = getRootState();
|
||||
next(action);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const root = new Updux({
|
||||
@ -129,8 +129,8 @@ test('getState is local', () => {
|
||||
doIt: ({ getState }) => next => action => {
|
||||
rootState = getState();
|
||||
next(action);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const store = root.createStore();
|
||||
@ -140,3 +140,89 @@ test('getState is local', () => {
|
||||
expect(rootFromChild).toEqual({ beta: 24, child: { alpha: 12 } });
|
||||
expect(childState).toEqual({ alpha: 12 });
|
||||
});
|
||||
|
||||
test("middleware as map", () => {
|
||||
let childState;
|
||||
let rootState;
|
||||
let rootFromChild;
|
||||
|
||||
const doIt = actionCreator("doIt");
|
||||
|
||||
const child = new Updux({
|
||||
initial: "",
|
||||
effects: [
|
||||
[
|
||||
doIt,
|
||||
() => next => action => {
|
||||
next(u({ payload: (p: string) => p + "Child" }, action) as any);
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
const root = new Updux({
|
||||
initial: { message: "" },
|
||||
subduxes: { child },
|
||||
effects: [
|
||||
[
|
||||
"^",
|
||||
() => next => action => {
|
||||
next(u({ payload: (p: string) => p + "Pre" }, action) as any);
|
||||
}
|
||||
],
|
||||
[
|
||||
doIt,
|
||||
() => next => action => {
|
||||
next(u({ payload: (p: string) => p + "Root" }, action) as any);
|
||||
}
|
||||
],
|
||||
[
|
||||
"*",
|
||||
() => next => action => {
|
||||
next(u({ payload: (p: string) => p + "After" }, action) as any);
|
||||
}
|
||||
],
|
||||
[
|
||||
"$",
|
||||
() => next => action => {
|
||||
next(u({ payload: (p: string) => p + "End" }, action) as any);
|
||||
}
|
||||
]
|
||||
],
|
||||
mutations: [[doIt, (message: any) => () => ({ message })]]
|
||||
});
|
||||
|
||||
const store = root.createStore();
|
||||
store.dispatch.doIt("");
|
||||
|
||||
expect(store.getState()).toEqual({ message: "PreRootAfterChildEnd" });
|
||||
});
|
||||
|
||||
test("generator", () => {
|
||||
const updux = new Updux({
|
||||
initial: 0,
|
||||
mutations: [["doIt", payload => () => payload]],
|
||||
effects: [
|
||||
[
|
||||
"doIt",
|
||||
() => {
|
||||
let i = 0;
|
||||
return () => (next: any) => (action: any) =>
|
||||
next({ ...action, payload: ++i });
|
||||
},
|
||||
true
|
||||
]
|
||||
]
|
||||
});
|
||||
|
||||
const store1 = updux.createStore();
|
||||
store1.dispatch.doIt();
|
||||
expect(store1.getState()).toEqual(1);
|
||||
store1.dispatch.doIt();
|
||||
expect(store1.getState()).toEqual(2);
|
||||
updux.actions;
|
||||
|
||||
const store2 = updux.createStore();
|
||||
store2.dispatch.doIt();
|
||||
expect(store2.getState()).toEqual(1);
|
||||
});
|
||||
|
@ -183,9 +183,15 @@ export type UpduxConfig<S = any> = {
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
effects?: Dictionary<UpduxMiddleware<S>>;
|
||||
effects?: Dictionary<UpduxMiddleware<S>> | EffectEntry<S>[];
|
||||
};
|
||||
|
||||
export type EffectEntry<S> = [
|
||||
ActionCreator | string,
|
||||
UpduxMiddleware<S>,
|
||||
boolean?
|
||||
];
|
||||
|
||||
export type Upreducer<S = any> = (action: Action) => (state: S) => S;
|
||||
|
||||
export interface UpduxMiddlewareAPI<S> {
|
||||
|
115
src/updux.ts
115
src/updux.ts
@ -1,8 +1,7 @@
|
||||
import fp from "lodash/fp";
|
||||
import u from "updeep";
|
||||
import { observable, computed, toJS } from "mobx";
|
||||
|
||||
import buildActions, { actionFor } from "./buildActions";
|
||||
import buildActions, { actionFor, actionCreator } from "./buildActions";
|
||||
import buildInitial from "./buildInitial";
|
||||
import buildMutations from "./buildMutations";
|
||||
|
||||
@ -18,7 +17,8 @@ import {
|
||||
Upreducer,
|
||||
UpduxDispatch,
|
||||
UpduxMiddleware,
|
||||
MutationEntry
|
||||
MutationEntry,
|
||||
EffectEntry
|
||||
} from "./types";
|
||||
|
||||
import { Middleware, Store } from "redux";
|
||||
@ -99,11 +99,11 @@ export class Updux<S = any> {
|
||||
*/
|
||||
groomMutations: (mutation: Mutation<S>) => Mutation<S>;
|
||||
|
||||
@observable private localEffects: Dictionary<UpduxMiddleware<S>>;
|
||||
private localEffects: EffectEntry<S>[] = [];
|
||||
|
||||
@observable private localActions: Dictionary<ActionCreator>;
|
||||
private localActions: Dictionary<ActionCreator> = {};
|
||||
|
||||
@observable private localMutations: Dictionary<
|
||||
private localMutations: Dictionary<
|
||||
Mutation<S> | [Mutation<S>, boolean | undefined]
|
||||
> = {};
|
||||
|
||||
@ -114,9 +114,18 @@ export class Updux<S = any> {
|
||||
fp.isPlainObject(value) ? new Updux(value) : value
|
||||
)(fp.getOr({}, "subduxes", config)) as Dictionary<Updux>;
|
||||
|
||||
this.localActions = fp.getOr({}, "actions", config);
|
||||
const actions = fp.getOr({}, "actions", config);
|
||||
Object.entries(actions).forEach(([type, payload]: [string, any]): any =>
|
||||
this.addAction(
|
||||
(payload as any).type ? payload : actionCreator(type, payload as any)
|
||||
)
|
||||
);
|
||||
|
||||
this.localEffects = fp.getOr({}, "effects", config);
|
||||
let effects = fp.getOr([], "effects", config);
|
||||
if (!Array.isArray(effects)) {
|
||||
effects = Object.entries(effects);
|
||||
}
|
||||
effects.forEach(effect => this.addEffect(...effect));
|
||||
|
||||
this.initial = buildInitial<any>(
|
||||
config.initial,
|
||||
@ -132,15 +141,15 @@ export class Updux<S = any> {
|
||||
}
|
||||
|
||||
/**
|
||||
* A middleware aggregating all the effects defined in the
|
||||
* Array of middlewares aggregating all the effects defined in the
|
||||
* updux and its subduxes. Effects of the updux itself are
|
||||
* done before the subduxes effects.
|
||||
* Note that `getState` will always return the state of the
|
||||
* local updux. The function `getRootState` is provided
|
||||
* alongside `getState` to get the root state.
|
||||
*/
|
||||
@computed get middleware(): UpduxMiddleware<S> {
|
||||
return buildMiddleware(this.localEffects, this.actions, this.subduxes);
|
||||
get middleware(): UpduxMiddleware<S> {
|
||||
return buildMiddleware(this._middlewareEntries, this.actions);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -157,19 +166,19 @@ export class Updux<S = any> {
|
||||
* actions generated from mutations/effects < non-custom subduxes actions <
|
||||
* custom subduxes actions < custom actions
|
||||
*/
|
||||
@computed get actions(): Dictionary<ActionCreator> {
|
||||
return buildActions(
|
||||
this.localActions,
|
||||
[...Object.keys(this.localMutations), ...Object.keys(this.localEffects)],
|
||||
fp.flatten(
|
||||
get actions(): Dictionary<ActionCreator> {
|
||||
return buildActions([
|
||||
...(Object.entries(this.localActions) as any),
|
||||
...(fp.flatten(
|
||||
Object.values(this.subduxes).map(({ actions }: Updux) =>
|
||||
Object.entries(actions)
|
||||
)
|
||||
)
|
||||
);
|
||||
) as any),
|
||||
,
|
||||
]);
|
||||
}
|
||||
|
||||
@computed get upreducer(): Upreducer<S> {
|
||||
get upreducer(): Upreducer<S> {
|
||||
return buildUpreducer(this.initial, this.mutations);
|
||||
}
|
||||
|
||||
@ -177,7 +186,7 @@ export class Updux<S = any> {
|
||||
* A Redux reducer generated using the computed initial state and
|
||||
* mutations.
|
||||
*/
|
||||
@computed get reducer(): (state: S | undefined, action: Action) => S {
|
||||
get reducer(): (state: S | undefined, action: Action) => S {
|
||||
return (state, action) => this.upreducer(action)(state as S);
|
||||
}
|
||||
|
||||
@ -186,7 +195,7 @@ export class Updux<S = any> {
|
||||
* mutations in both the main updux and its subduxes, the subduxes
|
||||
* mutations will be performed first.
|
||||
*/
|
||||
@computed get mutations(): Dictionary<Mutation<S>> {
|
||||
get mutations(): Dictionary<Mutation<S>> {
|
||||
return buildMutations(this.localMutations, this.subduxes);
|
||||
}
|
||||
|
||||
@ -207,7 +216,7 @@ export class Updux<S = any> {
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
@computed get subduxUpreducer() {
|
||||
get subduxUpreducer() {
|
||||
return buildUpreducer(this.initial, buildMutations({}, this.subduxes));
|
||||
}
|
||||
|
||||
@ -236,14 +245,14 @@ export class Updux<S = any> {
|
||||
* // still work
|
||||
* store.dispatch( actions.addTodo(...) );
|
||||
*/
|
||||
@computed get createStore(): () => StoreWithDispatchActions<S> {
|
||||
get createStore(): () => StoreWithDispatchActions<S> {
|
||||
const actions = this.actions;
|
||||
|
||||
return buildCreateStore<S>(
|
||||
this.reducer,
|
||||
this.initial,
|
||||
this.middleware as Middleware,
|
||||
this.actions
|
||||
this.middleware as any,
|
||||
actions
|
||||
) as () => StoreWithDispatchActions<S, typeof actions>;
|
||||
}
|
||||
|
||||
@ -285,13 +294,67 @@ export class Updux<S = any> {
|
||||
) {
|
||||
let c = fp.isFunction(creator) ? creator : actionFor(creator);
|
||||
|
||||
this.localActions[c.type] = c;
|
||||
this.addAction(c);
|
||||
|
||||
this.localMutations[c.type] = [
|
||||
this.groomMutations(mutation as any) as Mutation<S>,
|
||||
isSink
|
||||
];
|
||||
}
|
||||
|
||||
addEffect(
|
||||
creator: ActionCreator | string,
|
||||
middleware: UpduxMiddleware<S>,
|
||||
isGenerator: boolean = false
|
||||
) {
|
||||
let c = fp.isFunction(creator) ? creator : actionFor(creator);
|
||||
|
||||
this.addAction(c);
|
||||
this.localActions[c.type] = c;
|
||||
this.localEffects.push([c.type, middleware, isGenerator]);
|
||||
}
|
||||
|
||||
addAction(action: string | ActionCreator<any>) {
|
||||
if (typeof action === "string") {
|
||||
if (!this.localActions[action]) {
|
||||
this.localActions[action] = actionFor(action);
|
||||
}
|
||||
} else {
|
||||
this.localActions[action.type] = action;
|
||||
}
|
||||
}
|
||||
|
||||
get _middlewareEntries() {
|
||||
const groupByOrder = (mws: any) =>
|
||||
fp.groupBy(
|
||||
([_, actionType]: any) =>
|
||||
["^", "$"].includes(actionType) ? actionType : "middle",
|
||||
mws
|
||||
);
|
||||
|
||||
let subs = fp.flow([
|
||||
fp.mapValues("_middlewareEntries"),
|
||||
fp.toPairs,
|
||||
fp.map(([slice, entries]) =>
|
||||
entries.map(([ps, ...args]: any) => [[slice, ...ps], ...args])
|
||||
),
|
||||
fp.flatten,
|
||||
groupByOrder
|
||||
])(this.subduxes);
|
||||
|
||||
let local = groupByOrder(this.localEffects.map(x => [[], ...x]));
|
||||
|
||||
return fp.flatten(
|
||||
[
|
||||
local["^"],
|
||||
subs["^"],
|
||||
local.middle,
|
||||
subs.middle,
|
||||
subs["$"],
|
||||
local["$"]
|
||||
].filter(x => x)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Updux;
|
||||
|
Loading…
Reference in New Issue
Block a user