feat: add support for subscriptions
This commit is contained in:
parent
86dd272603
commit
9c45ee7efc
20
README.md
20
README.md
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# What's Updux?
|
# What's Updux?
|
||||||
|
|
||||||
So, I'm a fan of [Redux](https://redux.js.org). Two days ago I discovered
|
So, I'm a fan of [Redux](https://redux.js.org). Two days ago I discovered
|
||||||
@ -16,17 +15,16 @@ is the fun in that? And I'm also having a strong love for
|
|||||||
All that to say, say hello to `Updux`. Heavily inspired by `rematch`, but twisted
|
All that to say, say hello to `Updux`. Heavily inspired by `rematch`, but twisted
|
||||||
to work with `updeep` and to fit my peculiar needs. It offers features such as
|
to work with `updeep` and to fit my peculiar needs. It offers features such as
|
||||||
|
|
||||||
* Mimic the way VueX has mutations (reducer reactions to specific actions) and
|
- Mimic the way VueX has mutations (reducer reactions to specific actions) and
|
||||||
effects (middleware reacting to actions that can be asynchronous and/or
|
effects (middleware reacting to actions that can be asynchronous and/or
|
||||||
have side-effects), so everything pertaining to a store are all defined
|
have side-effects), so everything pertaining to a store are all defined
|
||||||
in the space place.
|
in the space place.
|
||||||
* Automatically gather all actions used by the updux's effects and mutations,
|
- Automatically gather all actions used by the updux's effects and mutations,
|
||||||
and makes then accessible as attributes to the `dispatch` object of the
|
and makes then accessible as attributes to the `dispatch` object of the
|
||||||
store.
|
store.
|
||||||
* Mutations have a signature that is friendly to Updux and Immer.
|
- Mutations have a signature that is friendly to Updux and Immer.
|
||||||
* Also, the mutation signature auto-unwrap the payload of the actions for you.
|
- Also, the mutation signature auto-unwrap the payload of the actions for you.
|
||||||
* TypeScript types.
|
- TypeScript types.
|
||||||
|
|
||||||
|
|
||||||
Fair warning: this package is still very new, probably very buggy,
|
Fair warning: this package is still very new, probably very buggy,
|
||||||
definitively very badly documented, and very subject to changes. Caveat
|
definitively very badly documented, and very subject to changes. Caveat
|
||||||
@ -78,6 +76,8 @@ store.dispatch.inc(3);
|
|||||||
# Description
|
# Description
|
||||||
|
|
||||||
Full documentation can be [found here](https://yanick.github.io/updux/).
|
Full documentation can be [found here](https://yanick.github.io/updux/).
|
||||||
|
Right now the best way to understand the whole thing is to go
|
||||||
|
through the [tutorial](https://yanick.github.io/updux/#/tutorial)
|
||||||
|
|
||||||
## Exporting upduxes
|
## Exporting upduxes
|
||||||
|
|
||||||
@ -94,7 +94,6 @@ const updux = new Updux({ ... });
|
|||||||
export default updux;
|
export default updux;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
Then you can use them as subduxes like this:
|
Then you can use them as subduxes like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -196,7 +195,6 @@ const updux = new Updux({
|
|||||||
|
|
||||||
Converting it to Immer would look like:
|
Converting it to Immer would look like:
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
import Updux from 'updux';
|
import Updux from 'updux';
|
||||||
import { produce } from 'Immer';
|
import { produce } from 'Immer';
|
||||||
@ -213,7 +211,6 @@ const updux = new Updux({
|
|||||||
But since typing `produce` over and over is no fun, `groomMutations`
|
But since typing `produce` over and over is no fun, `groomMutations`
|
||||||
can be used to wrap all mutations with it:
|
can be used to wrap all mutations with it:
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
import Updux from 'updux';
|
import Updux from 'updux';
|
||||||
import { produce } from 'Immer';
|
import { produce } from 'Immer';
|
||||||
@ -227,6 +224,3 @@ const updux = new Updux({
|
|||||||
});
|
});
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -101,7 +101,6 @@ For TypeScript projects I recommend declaring the actions as part of the
|
|||||||
configuration passed to the constructors, as it makes them accessible to the class
|
configuration passed to the constructors, as it makes them accessible to the class
|
||||||
at compile-time, and allows Updux to auto-add them to its aggregated `actions` type.
|
at compile-time, and allows Updux to auto-add them to its aggregated `actions` type.
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
const todosUpdux = new Updux({
|
const todosUpdux = new Updux({
|
||||||
actions: {
|
actions: {
|
||||||
@ -210,7 +209,6 @@ todosUpdux.addMutation( '*', (payload,action) => state => {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Effects
|
## Effects
|
||||||
|
|
||||||
In addition to mutations, Updux also provides action-specific middleware, here
|
In addition to mutations, Updux also provides action-specific middleware, here
|
||||||
@ -498,7 +496,7 @@ todosUpdux.addMutation( add_todo_with_id, payload =>
|
|||||||
export default updux.asDux;
|
export default updux.asDux;
|
||||||
```
|
```
|
||||||
|
|
||||||
Note the special '*' subdux key used here. This
|
Note the special '\*' subdux key used here. This
|
||||||
allows the updux to map every item present in its
|
allows the updux to map every item present in its
|
||||||
state to a `todo` updux. See [this recipe](/recipes?id=mapping-a-mutation-to-all-values-of-a-state) for details.
|
state to a `todo` updux. See [this recipe](/recipes?id=mapping-a-mutation-to-all-values-of-a-state) for details.
|
||||||
We could also have written the updux as:
|
We could also have written the updux as:
|
||||||
@ -519,11 +517,9 @@ const updux = new Updux({
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note how we are using the `upreducer` accessor in the first case (which yields
|
Note how we are using the `upreducer` accessor in the first case (which yields
|
||||||
a reducer for the dux using the signature `(payload,action) => state =>
|
a reducer for the dux using the signature `(payload,action) => state => new_state`) and `reducer` in the second case (which yield an equivalent
|
||||||
new_state`) and `reducer` in the second case (which yield an equivalent
|
|
||||||
reducer using the classic signature `(state,action) => new_state`).
|
reducer using the classic signature `(state,action) => new_state`).
|
||||||
|
|
||||||
|
|
||||||
### Main store
|
### Main store
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -573,6 +569,50 @@ at the main level is actually defined as:
|
|||||||
const getNextId = state => next_id.selectors.getNextId(state.next_id);
|
const getNextId = state => next_id.selectors.getNextId(state.next_id);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Subscriptions
|
||||||
|
|
||||||
|
Subscriptions can be added by default to a updux store via the initial config
|
||||||
|
or the method `addSubscription`. The signature of a subscription is:
|
||||||
|
|
||||||
|
```
|
||||||
|
(store) => (state,unsubscribe) => {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Subscriptions registered for an updux and its subduxes are automatically
|
||||||
|
subscribed to the store when calling `createStore`.
|
||||||
|
|
||||||
|
The `state` passed to the subscriptions of the subduxes is the local state.
|
||||||
|
|
||||||
|
Also, all subscriptions are wrapped such that they are called only if the
|
||||||
|
local `state` changed since their last invocation.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
const set_nbr_todos = action('set_nbr_todos', payload() );
|
||||||
|
|
||||||
|
const todos = dux({
|
||||||
|
initial: [],
|
||||||
|
subscriptions: [
|
||||||
|
({dispatch}) => todos => dispatch(set_nbr_todos(todos.length))
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const myDux = dux({
|
||||||
|
initial: {
|
||||||
|
nbr_todos: 0
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
todos,
|
||||||
|
},
|
||||||
|
mutations: [
|
||||||
|
[ set_nbr_todos, nbr_todos => u({nbr_todos}) ]
|
||||||
|
]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
## Exporting upduxes
|
## Exporting upduxes
|
||||||
|
|
||||||
As a general rule, don't directly export your upduxes, but rather use the accessor `asDux`.
|
As a general rule, don't directly export your upduxes, but rather use the accessor `asDux`.
|
||||||
|
@ -43,7 +43,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"docsify:serve": "docsify serve docs",
|
"docsify:serve": "docsify serve docs",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "tap src/**test.ts"
|
"test": "tap src/**test.ts",
|
||||||
|
"lint": "prettier -c --",
|
||||||
|
"lint:fix": "prettier --write --"
|
||||||
},
|
},
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
107
src/subscriptions.test.ts
Normal file
107
src/subscriptions.test.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import tap from 'tap';
|
||||||
|
import Updux from '.';
|
||||||
|
import { action, payload } from 'ts-action';
|
||||||
|
import u from 'updeep';
|
||||||
|
|
||||||
|
const inc = action('inc');
|
||||||
|
const set_double = action('set_double', payload<number>());
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: {
|
||||||
|
x: 0,
|
||||||
|
double: 0,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
inc,
|
||||||
|
},
|
||||||
|
mutations: [
|
||||||
|
[inc, payload => u({ x: x => x + 1 })],
|
||||||
|
[set_double, double => u({ double })],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
dux.addSubscription(store => (state, unsubscribe) => {
|
||||||
|
if (state.x > 2) return unsubscribe();
|
||||||
|
|
||||||
|
store.dispatch(set_double(state.x * 2));
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = dux.createStore();
|
||||||
|
|
||||||
|
store.dispatch(inc());
|
||||||
|
|
||||||
|
tap.same(store.getState(), { x: 1, double: 2 });
|
||||||
|
|
||||||
|
store.dispatch(inc());
|
||||||
|
store.dispatch(inc());
|
||||||
|
|
||||||
|
tap.same(store.getState(), { x: 3, double: 4 }, 'we unsubscribed');
|
||||||
|
|
||||||
|
tap.test('subduxes subscriptions', async t => {
|
||||||
|
const inc_top = action('inc_top');
|
||||||
|
const inc_bar = action('inc_bar');
|
||||||
|
const transform_bar = action('transform_bar', payload());
|
||||||
|
|
||||||
|
const bar = new Updux({
|
||||||
|
initial: 'a',
|
||||||
|
mutations: [
|
||||||
|
[inc_bar, () => state => state + 'a'],
|
||||||
|
[transform_bar, outcome => () => outcome],
|
||||||
|
],
|
||||||
|
subscriptions: [
|
||||||
|
store => (state, unsubscribe) => {
|
||||||
|
console.log({ state });
|
||||||
|
|
||||||
|
if (state.length <= 2) return;
|
||||||
|
unsubscribe();
|
||||||
|
store.dispatch(transform_bar('look at ' + state));
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const dux = new Updux({
|
||||||
|
initial: {
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
subduxes: {
|
||||||
|
bar: bar.asDux,
|
||||||
|
},
|
||||||
|
mutations: [[inc_top, () => u({ count: count => count + 1 })]],
|
||||||
|
effects: [
|
||||||
|
[
|
||||||
|
'*',
|
||||||
|
() => next => action => {
|
||||||
|
console.log('before ', action.type);
|
||||||
|
next(action);
|
||||||
|
console.log({ action });
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
subscriptions: [
|
||||||
|
store => {
|
||||||
|
let previous: any;
|
||||||
|
return ({ count }) => {
|
||||||
|
if (count !== previous) {
|
||||||
|
previous = count;
|
||||||
|
store.dispatch(inc_bar());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = dux.createStore();
|
||||||
|
|
||||||
|
store.dispatch(inc_top());
|
||||||
|
store.dispatch(inc_top());
|
||||||
|
|
||||||
|
t.same(store.getState(), {
|
||||||
|
count: 2,
|
||||||
|
bar: 'look at look at aaa',
|
||||||
|
});
|
||||||
|
store.dispatch(inc_top());
|
||||||
|
t.same(store.getState(), {
|
||||||
|
count: 3,
|
||||||
|
bar: 'look at look at aaaa',
|
||||||
|
});
|
||||||
|
});
|
577
src/types.ts
577
src/types.ts
@ -37,324 +37,339 @@ export type UnionToIntersection<U> = (U extends any
|
|||||||
|
|
||||||
export type StateOf<D> = D extends { initial: infer I } ? I : unknown;
|
export type StateOf<D> = D extends { initial: infer I } ? I : unknown;
|
||||||
|
|
||||||
export type DuxStateCoduxes<C> = C extends Array<infer U> ? UnionToIntersection<StateOf<U>>: unknown
|
export type DuxStateCoduxes<C> = C extends Array<infer U>
|
||||||
export type DuxStateSubduxes<C> =
|
? UnionToIntersection<StateOf<U>>
|
||||||
C extends { '*': infer I } ? {
|
: unknown;
|
||||||
[ key: string ]: StateOf<I>,
|
export type DuxStateSubduxes<C> = C extends { '*': infer I }
|
||||||
[ index: number ]: StateOf<I>,
|
? {
|
||||||
} :
|
[key: string]: StateOf<I>;
|
||||||
C extends object ? { [ K in keyof C]: StateOf<C[K]>}: unknown;
|
[index: number]: StateOf<I>;
|
||||||
|
}
|
||||||
|
: C extends object
|
||||||
|
? { [K in keyof C]: StateOf<C[K]> }
|
||||||
|
: unknown;
|
||||||
|
|
||||||
type DuxStateGlobSub<S> = S extends { '*': infer I } ? StateOf<I> : unknown;
|
type DuxStateGlobSub<S> = S extends { '*': infer I } ? StateOf<I> : unknown;
|
||||||
|
|
||||||
type LocalDuxState<S> = S extends never[] ? unknown[] : S;
|
type LocalDuxState<S> = S extends never[] ? unknown[] : S;
|
||||||
|
|
||||||
/** @ignore */
|
/** @ignore */
|
||||||
type AggDuxState2<L,S,C> = (
|
type AggDuxState2<L, S, C> = (L extends never[]
|
||||||
L extends never[] ? Array<DuxStateGlobSub<S>> : L & DuxStateSubduxes<S> ) & DuxStateCoduxes<C>;
|
? Array<DuxStateGlobSub<S>>
|
||||||
|
: L & DuxStateSubduxes<S>) &
|
||||||
|
DuxStateCoduxes<C>;
|
||||||
|
|
||||||
/** @ignore */
|
/** @ignore */
|
||||||
export type AggDuxState<O,S extends UpduxConfig> = unknown extends O ?
|
export type AggDuxState<O, S extends UpduxConfig> = unknown extends O
|
||||||
AggDuxState2<S['initial'],S['subduxes'],S['coduxes']> : O
|
? AggDuxState2<S['initial'], S['subduxes'], S['coduxes']>
|
||||||
|
: O;
|
||||||
|
|
||||||
type SelectorsOf<C> = C extends { selectors: infer S } ? S : unknown;
|
type SelectorsOf<C> = C extends { selectors: infer S } ? S : unknown;
|
||||||
|
|
||||||
/** @ignore */
|
/** @ignore */
|
||||||
export type DuxSelectorsSubduxes<C> = C extends object ? UnionToIntersection<SelectorsOf<C[keyof C]>> : unknown;
|
export type DuxSelectorsSubduxes<C> = C extends object
|
||||||
|
? UnionToIntersection<SelectorsOf<C[keyof C]>>
|
||||||
|
: unknown;
|
||||||
|
|
||||||
/** @ignore */
|
/** @ignore */
|
||||||
export type DuxSelectorsCoduxes<C> = C extends Array<infer U> ? UnionToIntersection<SelectorsOf<U>> : unknown;
|
export type DuxSelectorsCoduxes<C> = C extends Array<infer U>
|
||||||
|
? UnionToIntersection<SelectorsOf<U>>
|
||||||
|
: unknown;
|
||||||
|
|
||||||
type MaybeReturnType<X> = X extends (...args: any) => any ? ReturnType<X> : unknown;
|
type MaybeReturnType<X> = X extends (...args: any) => any
|
||||||
|
? ReturnType<X>
|
||||||
|
: unknown;
|
||||||
|
|
||||||
type RebaseSelector<S,X> = {
|
type RebaseSelector<S, X> = {
|
||||||
[ K in keyof X]: (state: S) => MaybeReturnType< X[K] >
|
[K in keyof X]: (state: S) => MaybeReturnType<X[K]>;
|
||||||
}
|
};
|
||||||
|
|
||||||
type ActionsOf<C> = C extends { actions: infer A } ? A : {};
|
type ActionsOf<C> = C extends { actions: infer A } ? A : {};
|
||||||
|
|
||||||
type DuxActionsSubduxes<C> = C extends object ? ActionsOf<C[keyof C]> : unknown;
|
type DuxActionsSubduxes<C> = C extends object ? ActionsOf<C[keyof C]> : unknown;
|
||||||
export type DuxActionsCoduxes<C> = C extends Array<infer I> ? UnionToIntersection<ActionsOf<I>> : {};
|
export type DuxActionsCoduxes<C> = C extends Array<infer I>
|
||||||
|
? UnionToIntersection<ActionsOf<I>>
|
||||||
|
: {};
|
||||||
|
|
||||||
type ItemsOf<C> = C extends object? C[keyof C] : unknown
|
type ItemsOf<C> = C extends object ? C[keyof C] : unknown;
|
||||||
|
|
||||||
export type DuxActions<A,C extends UpduxConfig> = A extends object ? A: (
|
export type DuxActions<A, C extends UpduxConfig> = A extends object
|
||||||
UnionToIntersection<ActionsOf<C|ItemsOf<C['subduxes']>|ItemsOf<C['coduxes']>>>
|
? A
|
||||||
);
|
: UnionToIntersection<
|
||||||
|
ActionsOf<C | ItemsOf<C['subduxes']> | ItemsOf<C['coduxes']>>
|
||||||
|
>;
|
||||||
|
|
||||||
export type DuxSelectors<S,X,C extends UpduxConfig> = unknown extends X ? (
|
export type DuxSelectors<S, X, C extends UpduxConfig> = unknown extends X
|
||||||
RebaseSelector<S,
|
? RebaseSelector<
|
||||||
C['selectors'] & DuxSelectorsCoduxes<C['coduxes']> &
|
S,
|
||||||
DuxSelectorsSubduxes<C['subduxes']> >
|
C['selectors'] &
|
||||||
): X
|
DuxSelectorsCoduxes<C['coduxes']> &
|
||||||
|
DuxSelectorsSubduxes<C['subduxes']>
|
||||||
|
>
|
||||||
|
: X;
|
||||||
|
|
||||||
export type Dux<
|
export type Dux<S = unknown, A = unknown, X = unknown, C = unknown> = {
|
||||||
S = unknown,
|
subduxes: Dictionary<Dux>;
|
||||||
A = unknown,
|
coduxes: Dux[];
|
||||||
X = unknown,
|
initial: AggDuxState<S, C>;
|
||||||
C = unknown,
|
actions: A;
|
||||||
> = {
|
subscriptions: Function[];
|
||||||
subduxes: Dictionary<Dux>,
|
};
|
||||||
coduxes: Dux[],
|
|
||||||
initial: AggDuxState<S,C>,
|
|
||||||
actions: A,
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration object given to Updux's constructor.
|
* Configuration object given to Updux's constructor.
|
||||||
*
|
*
|
||||||
* #### arguments
|
* #### arguments
|
||||||
*
|
*
|
||||||
* ##### initial
|
* ##### initial
|
||||||
*
|
*
|
||||||
* Default initial state of the reducer. If applicable, is merged with
|
* Default initial state of the reducer. If applicable, is merged with
|
||||||
* the subduxes initial states, with the parent having precedence.
|
* the subduxes initial states, with the parent having precedence.
|
||||||
*
|
*
|
||||||
* If not provided, defaults to an empty object.
|
* If not provided, defaults to an empty object.
|
||||||
*
|
*
|
||||||
* ##### actions
|
* ##### actions
|
||||||
*
|
*
|
||||||
* [Actions](/concepts/Actions) used by the updux.
|
* [Actions](/concepts/Actions) used by the updux.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import { dux } from 'updux';
|
* import { dux } from 'updux';
|
||||||
* import { action, payload } from 'ts-action';
|
* import { action, payload } from 'ts-action';
|
||||||
*
|
*
|
||||||
* const bar = action('BAR', payload<int>());
|
* const bar = action('BAR', payload<int>());
|
||||||
* const foo = action('FOO');
|
* const foo = action('FOO');
|
||||||
*
|
*
|
||||||
* const myDux = dux({
|
* const myDux = dux({
|
||||||
* actions: {
|
* actions: {
|
||||||
* bar
|
* bar
|
||||||
* },
|
* },
|
||||||
* mutations: [
|
* mutations: [
|
||||||
* [ foo, () => state => state ]
|
* [ foo, () => state => state ]
|
||||||
* ]
|
* ]
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* myDux.actions.foo({ x: 1, y: 2 }); // => { type: foo, x:1, y:2 }
|
* myDux.actions.foo({ x: 1, y: 2 }); // => { type: foo, x:1, y:2 }
|
||||||
* myDux.actions.bar(2); // => { type: bar, payload: 2 }
|
* myDux.actions.bar(2); // => { type: bar, payload: 2 }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* New actions used directly in mutations and effects will be added to the
|
* New actions used directly in mutations and effects will be added to the
|
||||||
* dux actions -- that is, they will be accessible via `dux.actions` -- but will
|
* dux actions -- that is, they will be accessible via `dux.actions` -- but will
|
||||||
* not appear as part of its Typescript type.
|
* not appear as part of its Typescript type.
|
||||||
*
|
*
|
||||||
* ##### selectors
|
* ##### selectors
|
||||||
*
|
*
|
||||||
* Dictionary of selectors for the current updux. The updux also
|
* Dictionary of selectors for the current updux. The updux also
|
||||||
* inherit its subduxes' selectors.
|
* inherit its subduxes' selectors.
|
||||||
*
|
*
|
||||||
* The selectors are available via the class' getter.
|
* The selectors are available via the class' getter.
|
||||||
*
|
*
|
||||||
* ##### mutations
|
* ##### mutations
|
||||||
*
|
*
|
||||||
* mutations: [
|
* mutations: [
|
||||||
* [ action, mutation, isSink ],
|
* [ action, mutation, isSink ],
|
||||||
* ...
|
* ...
|
||||||
* ]
|
* ]
|
||||||
*
|
*
|
||||||
* or
|
* or
|
||||||
*
|
*
|
||||||
* mutations: {
|
* mutations: {
|
||||||
* action: mutation,
|
* action: mutation,
|
||||||
* ...
|
* ...
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* List of mutations for assign to the dux. If you want Typescript goodness, you
|
* List of mutations for assign to the dux. If you want Typescript goodness, you
|
||||||
* probably want to use `addMutation()` instead.
|
* probably want to use `addMutation()` instead.
|
||||||
*
|
*
|
||||||
* In its generic array-of-array form,
|
* In its generic array-of-array form,
|
||||||
* each mutation tuple contains: the action, the mutation,
|
* each mutation tuple contains: the action, the mutation,
|
||||||
* and boolean indicating if this is a sink mutation.
|
* and boolean indicating if this is a sink mutation.
|
||||||
*
|
*
|
||||||
* The action can be an action creator function or a string. If it's a string, it's considered to be the
|
* The action can be an action creator function or a string. If it's a string, it's considered to be the
|
||||||
* action type and a generic `action( actionName, payload() )` creator will be
|
* action type and a generic `action( actionName, payload() )` creator will be
|
||||||
* generated for it. If an action is not already defined in the `actions`
|
* generated for it. If an action is not already defined in the `actions`
|
||||||
* parameter, it'll be automatically added.
|
* parameter, it'll be automatically added.
|
||||||
*
|
*
|
||||||
* The pseudo-action type `*` can be used to match any action not explicitly matched by other mutations.
|
* The pseudo-action type `*` can be used to match any action not explicitly matched by other mutations.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* const todosUpdux = updux({
|
* const todosUpdux = updux({
|
||||||
* mutations: {
|
* mutations: {
|
||||||
* add: todo => state => [ ...state, todo ],
|
* add: todo => state => [ ...state, todo ],
|
||||||
* done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
* done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
||||||
* '*' (payload,action) => state => {
|
* '*' (payload,action) => state => {
|
||||||
* console.warn( "unexpected action ", action.type );
|
* console.warn( "unexpected action ", action.type );
|
||||||
* return state;
|
* return state;
|
||||||
* },
|
* },
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* The signature of the mutations is `(payload,action) => state => newState`.
|
* The signature of the mutations is `(payload,action) => state => newState`.
|
||||||
* It is designed to play well with `Updeep` (and [Immer](https://immerjs.github.io/immer/docs/introduction)). This way, instead of doing
|
* It is designed to play well with `Updeep` (and [Immer](https://immerjs.github.io/immer/docs/introduction)). This way, instead of doing
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* mutation: {
|
* mutation: {
|
||||||
* renameTodo: newName => state => { ...state, name: newName }
|
* renameTodo: newName => state => { ...state, name: newName }
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* we can do
|
* we can do
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* mutation: {
|
* mutation: {
|
||||||
* renameTodo: newName => u({ name: newName })
|
* renameTodo: newName => u({ name: newName })
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* The final argument is the optional boolean `isSink`. If it is true, it'll
|
* The final argument is the optional boolean `isSink`. If it is true, it'll
|
||||||
* prevent subduxes' mutations on the same action. It defaults to `false`.
|
* prevent subduxes' mutations on the same action. It defaults to `false`.
|
||||||
*
|
*
|
||||||
* The object version of the argument can be used as a shortcut when all actions
|
* The object version of the argument can be used as a shortcut when all actions
|
||||||
* are strings. In that case, `isSink` is `false` for all mutations.
|
* are strings. In that case, `isSink` is `false` for all mutations.
|
||||||
*
|
*
|
||||||
* ##### groomMutations
|
* ##### groomMutations
|
||||||
*
|
*
|
||||||
* Function that can be provided to alter all local mutations of the updux
|
* Function that can be provided to alter all local mutations of the updux
|
||||||
* (the mutations of subduxes are left untouched).
|
* (the mutations of subduxes are left untouched).
|
||||||
*
|
*
|
||||||
* Can be used, for example, for Immer integration:
|
* Can be used, for example, for Immer integration:
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import Updux from 'updux';
|
* import Updux from 'updux';
|
||||||
* import { produce } from 'Immer';
|
* import { produce } from 'Immer';
|
||||||
*
|
*
|
||||||
* const updux = new Updux({
|
* const updux = new Updux({
|
||||||
* initial: { counter: 0 },
|
* initial: { counter: 0 },
|
||||||
* groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
* groomMutations: mutation => (...args) => produce( mutation(...args) ),
|
||||||
* mutations: {
|
* mutations: {
|
||||||
* add: (inc=1) => draft => draft.counter += inc
|
* add: (inc=1) => draft => draft.counter += inc
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* Or perhaps for debugging:
|
* Or perhaps for debugging:
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import Updux from 'updux';
|
* import Updux from 'updux';
|
||||||
*
|
*
|
||||||
* const updux = new Updux({
|
* const updux = new Updux({
|
||||||
* initial: { counter: 0 },
|
* initial: { counter: 0 },
|
||||||
* groomMutations: mutation => (...args) => state => {
|
* groomMutations: mutation => (...args) => state => {
|
||||||
* console.log( "got action ", args[1] );
|
* console.log( "got action ", args[1] );
|
||||||
* return mutation(...args)(state);
|
* return mutation(...args)(state);
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
* ##### subduxes
|
* ##### subduxes
|
||||||
*
|
*
|
||||||
* Object mapping slices of the state to sub-upduxes. In addition to creating
|
* Object mapping slices of the state to sub-upduxes. In addition to creating
|
||||||
* sub-reducers for those slices, it'll make the parend updux inherit all the
|
* sub-reducers for those slices, it'll make the parend updux inherit all the
|
||||||
* actions and middleware from its subduxes.
|
* actions and middleware from its subduxes.
|
||||||
*
|
*
|
||||||
* For example, if in plain Redux you would do
|
* For example, if in plain Redux you would do
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import { combineReducers } from 'redux';
|
* import { combineReducers } from 'redux';
|
||||||
* import todosReducer from './todos';
|
* import todosReducer from './todos';
|
||||||
* import statisticsReducer from './statistics';
|
* import statisticsReducer from './statistics';
|
||||||
*
|
*
|
||||||
* const rootReducer = combineReducers({
|
* const rootReducer = combineReducers({
|
||||||
* todos: todosReducer,
|
* todos: todosReducer,
|
||||||
* stats: statisticsReducer,
|
* stats: statisticsReducer,
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* then with Updux you'd do
|
* then with Updux you'd do
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* import { updux } from 'updux';
|
* import { updux } from 'updux';
|
||||||
* import todos from './todos';
|
* import todos from './todos';
|
||||||
* import statistics from './statistics';
|
* import statistics from './statistics';
|
||||||
*
|
*
|
||||||
* const rootUpdux = updux({
|
* const rootUpdux = updux({
|
||||||
* subduxes: {
|
* subduxes: {
|
||||||
* todos,
|
* todos,
|
||||||
* statistics
|
* statistics
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* ##### effects
|
* ##### effects
|
||||||
*
|
*
|
||||||
* Array of arrays or plain object defining asynchronous actions and side-effects triggered by actions.
|
* Array of arrays or plain object defining asynchronous actions and side-effects triggered by actions.
|
||||||
* The effects themselves are Redux middleware, with the `dispatch`
|
* The effects themselves are Redux middleware, with the `dispatch`
|
||||||
* property of the first argument augmented with all the available actions.
|
* property of the first argument augmented with all the available actions.
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* updux({
|
* updux({
|
||||||
* effects: {
|
* effects: {
|
||||||
* fetch: ({dispatch}) => next => async (action) => {
|
* fetch: ({dispatch}) => next => async (action) => {
|
||||||
* next(action);
|
* next(action);
|
||||||
*
|
*
|
||||||
* let result = await fetch(action.payload.url).then( result => result.json() );
|
* let result = await fetch(action.payload.url).then( result => result.json() );
|
||||||
* dispatch.fetchSuccess(result);
|
* dispatch.fetchSuccess(result);
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* import Updux from 'updux';
|
* import Updux from 'updux';
|
||||||
* import { actions, payload } from 'ts-action';
|
* import { actions, payload } from 'ts-action';
|
||||||
* import u from 'updeep';
|
* import u from 'updeep';
|
||||||
*
|
*
|
||||||
* const todoUpdux = new Updux({
|
* const todoUpdux = new Updux({
|
||||||
* initial: {
|
* initial: {
|
||||||
* done: false,
|
* done: false,
|
||||||
* note: "",
|
* note: "",
|
||||||
* },
|
* },
|
||||||
* actions: {
|
* actions: {
|
||||||
* finish: action('FINISH', payload()),
|
* finish: action('FINISH', payload()),
|
||||||
* edit: action('EDIT', payload()),
|
* edit: action('EDIT', payload()),
|
||||||
* },
|
* },
|
||||||
* mutations: [
|
* mutations: [
|
||||||
* [ edit, note => u({note}) ]
|
* [ edit, note => u({note}) ]
|
||||||
* ],
|
* ],
|
||||||
* selectors: {
|
* selectors: {
|
||||||
* getNote: state => state.note
|
* getNote: state => state.note
|
||||||
* },
|
* },
|
||||||
* groomMutations: mutation => transform(mutation),
|
* groomMutations: mutation => transform(mutation),
|
||||||
* subduxes: {
|
* subduxes: {
|
||||||
* foo
|
* foo
|
||||||
* },
|
* },
|
||||||
* effects: {
|
* effects: {
|
||||||
* finish: () => next => action => {
|
* finish: () => next => action => {
|
||||||
* console.log( "Woo! one more bites the dust" );
|
* console.log( "Woo! one more bites the dust" );
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* })
|
* })
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export type UpduxConfig = Partial<{
|
export type UpduxConfig = Partial<{
|
||||||
initial: unknown, /** foo */
|
initial: unknown /** foo */;
|
||||||
subduxes: Dictionary<Dux>,
|
subduxes: Dictionary<Dux>;
|
||||||
coduxes: Dux[],
|
coduxes: Dux[];
|
||||||
actions: Dictionary<ActionCreator>,
|
actions: Dictionary<ActionCreator>;
|
||||||
selectors: Dictionary<Selector>,
|
selectors: Dictionary<Selector>;
|
||||||
mutations: any,
|
mutations: any;
|
||||||
groomMutations: (m: Mutation) => Mutation,
|
groomMutations: (m: Mutation) => Mutation;
|
||||||
effects: any,
|
effects: any;
|
||||||
|
subscriptions: Function[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
|
||||||
export type Upreducer<S = any> = (action: Action) => (state: S) => S;
|
export type Upreducer<S = any> = (action: Action) => (state: S) => S;
|
||||||
|
|
||||||
/** @ignore */
|
/** @ignore */
|
||||||
export interface UpduxMiddlewareAPI<S=any,X = Dictionary<Selector>> {
|
export interface UpduxMiddlewareAPI<S = any, X = Dictionary<Selector>> {
|
||||||
dispatch: Function;
|
dispatch: Function;
|
||||||
getState(): S;
|
getState(): S;
|
||||||
selectors: X;
|
selectors: X;
|
||||||
actions: Dictionary<ActionCreator>;
|
actions: Dictionary<ActionCreator>;
|
||||||
}
|
}
|
||||||
export type UpduxMiddleware<S=any,X = Dictionary<Selector>,A = Action> = (
|
export type UpduxMiddleware<S = any, X = Dictionary<Selector>, A = Action> = (
|
||||||
api: UpduxMiddlewareAPI<S,X>
|
api: UpduxMiddlewareAPI<S, X>
|
||||||
) => (next: Function) => (action: A) => any;
|
) => (next: Function) => (action: A) => any;
|
||||||
|
|
||||||
export type Selector<S = any> = (state: S) => unknown;
|
export type Selector<S = any> = (state: S) => unknown;
|
||||||
|
850
src/updux.ts
850
src/updux.ts
@ -1,6 +1,6 @@
|
|||||||
import fp from 'lodash/fp';
|
import fp from 'lodash/fp';
|
||||||
import { action, payload, ActionCreator, ActionType } from 'ts-action';
|
import { action, payload, ActionCreator, ActionType } from 'ts-action';
|
||||||
import {AnyAction} from 'redux';
|
import { AnyAction } from 'redux';
|
||||||
|
|
||||||
import buildInitial from './buildInitial';
|
import buildInitial from './buildInitial';
|
||||||
import buildMutations from './buildMutations';
|
import buildMutations from './buildMutations';
|
||||||
@ -51,6 +51,39 @@ type StoreWithDispatchActions<
|
|||||||
dispatch: { [type in keyof Actions]: (...args: any) => void };
|
dispatch: { [type in keyof Actions]: (...args: any) => void };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function wrap_subscription(sub) {
|
||||||
|
return store => {
|
||||||
|
const sub_curried = sub(store);
|
||||||
|
let previous: unknown;
|
||||||
|
|
||||||
|
return (state, unsubscribe) => {
|
||||||
|
if (state === previous) return;
|
||||||
|
previous = state;
|
||||||
|
|
||||||
|
return sub_curried(state, unsubscribe);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _subscribeToStore(store: any, subscriptions: Function[] = []) {
|
||||||
|
subscriptions.forEach(sub => {
|
||||||
|
const subscriber = sub(store);
|
||||||
|
let unsub = store.subscribe(() => {
|
||||||
|
const state = store.getState();
|
||||||
|
return subscriber(state, unsub);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sliced_subscription(slice, sub) {
|
||||||
|
return store => {
|
||||||
|
const sub_curried = sub(store);
|
||||||
|
|
||||||
|
return (state, unsubscribe) =>
|
||||||
|
sub_curried(fp.get(slice, state), unsubscribe);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* `Updux` is a way to minimize and simplify the boilerplate associated with the
|
* `Updux` is a way to minimize and simplify the boilerplate associated with the
|
||||||
@ -59,80 +92,85 @@ type StoreWithDispatchActions<
|
|||||||
* In true `Redux`-like fashion, upduxes can be made of sub-upduxes (`subduxes` for short) for different slices of the root state.
|
* In true `Redux`-like fashion, upduxes can be made of sub-upduxes (`subduxes` for short) for different slices of the root state.
|
||||||
*/
|
*/
|
||||||
export class Updux<
|
export class Updux<
|
||||||
S = unknown,
|
S = unknown,
|
||||||
A = null,
|
A = null,
|
||||||
X = unknown,
|
X = unknown,
|
||||||
C extends UpduxConfig = {}
|
C extends UpduxConfig = {}
|
||||||
> {
|
> {
|
||||||
subduxes: Dictionary<Dux>;
|
subduxes: Dictionary<Dux>;
|
||||||
coduxes: Dux[];
|
coduxes: Dux[];
|
||||||
|
|
||||||
private localSelectors: Dictionary<Selector> = {};
|
private localSelectors: Dictionary<Selector> = {};
|
||||||
|
|
||||||
private localInitial: unknown;
|
private localInitial: unknown;
|
||||||
|
|
||||||
groomMutations: (mutation: Mutation<S>) => Mutation<S>;
|
groomMutations: (mutation: Mutation<S>) => Mutation<S>;
|
||||||
|
|
||||||
private localEffects: Effect[] = [];
|
private localEffects: Effect[] = [];
|
||||||
|
|
||||||
private localActions: Dictionary<ActionCreator> = {};
|
private localActions: Dictionary<ActionCreator> = {};
|
||||||
|
|
||||||
private localMutations: Dictionary<
|
private localMutations: Dictionary<
|
||||||
Mutation<S> | [Mutation<S>, boolean | undefined]
|
Mutation<S> | [Mutation<S>, boolean | undefined]
|
||||||
> = {};
|
> = {};
|
||||||
|
|
||||||
get initial(): AggDuxState<S, C> {
|
private localSubscriptions: Function[] = [];
|
||||||
return buildInitial(
|
|
||||||
this.localInitial,
|
|
||||||
this.coduxes.map(({ initial }) => initial),
|
|
||||||
fp.mapValues('initial', this.subduxes)
|
|
||||||
) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
get initial(): AggDuxState<S, C> {
|
||||||
* @param config an [[UpduxConfig]] plain object
|
return buildInitial(
|
||||||
*
|
this.localInitial,
|
||||||
*/
|
this.coduxes.map(({ initial }) => initial),
|
||||||
constructor(config: C = {} as C) {
|
fp.mapValues('initial', this.subduxes)
|
||||||
this.localInitial = config.initial ?? {};
|
) as any;
|
||||||
this.localSelectors = config.selectors ?? {};
|
}
|
||||||
this.coduxes = config.coduxes ?? [];
|
|
||||||
this.subduxes = config.subduxes ?? {};
|
|
||||||
|
|
||||||
Object.entries(config.actions ?? {}).forEach((args) =>
|
/**
|
||||||
(this.addAction as any)(...args)
|
* @param config an [[UpduxConfig]] plain object
|
||||||
);
|
*
|
||||||
|
*/
|
||||||
|
constructor(config: C = {} as C) {
|
||||||
|
this.localInitial = config.initial ?? {};
|
||||||
|
this.localSelectors = config.selectors ?? {};
|
||||||
|
this.coduxes = config.coduxes ?? [];
|
||||||
|
this.subduxes = config.subduxes ?? {};
|
||||||
|
|
||||||
this.coduxes.forEach((c: any) =>
|
Object.entries(config.actions ?? {}).forEach(args =>
|
||||||
Object.entries(c.actions).forEach((args) =>
|
(this.addAction as any)(...args)
|
||||||
(this.addAction as any)(...args)
|
);
|
||||||
)
|
|
||||||
);
|
|
||||||
Object.values(this.subduxes).forEach((c: any) => {
|
|
||||||
Object.entries(c.actions).forEach((args) => {
|
|
||||||
(this.addAction as any)(...args);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.groomMutations =
|
this.coduxes.forEach((c: any) =>
|
||||||
config.groomMutations ?? ((x: Mutation<S>) => x);
|
Object.entries(c.actions).forEach(args =>
|
||||||
|
(this.addAction as any)(...args)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Object.values(this.subduxes).forEach((c: any) => {
|
||||||
|
Object.entries(c.actions).forEach(args => {
|
||||||
|
(this.addAction as any)(...args);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
let effects = config.effects ?? [];
|
if (config.subscriptions) {
|
||||||
|
config.subscriptions.forEach(sub => this.addSubscription(sub));
|
||||||
|
}
|
||||||
|
|
||||||
if (!Array.isArray(effects)) {
|
this.groomMutations = config.groomMutations ?? ((x: Mutation<S>) => x);
|
||||||
effects = (Object.entries(effects) as unknown) as Effect[];
|
|
||||||
}
|
|
||||||
effects.forEach((effect) => (this.addEffect as any)(...effect));
|
|
||||||
|
|
||||||
let mutations = config.mutations ?? [];
|
let effects = config.effects ?? [];
|
||||||
|
|
||||||
if (!Array.isArray(mutations)) {
|
if (!Array.isArray(effects)) {
|
||||||
mutations = fp.toPairs(mutations);
|
effects = (Object.entries(effects) as unknown) as Effect[];
|
||||||
}
|
}
|
||||||
|
effects.forEach(effect => (this.addEffect as any)(...effect));
|
||||||
|
|
||||||
mutations.forEach((args) => (this.addMutation as any)(...args));
|
let mutations = config.mutations ?? [];
|
||||||
|
|
||||||
/*
|
if (!Array.isArray(mutations)) {
|
||||||
|
mutations = fp.toPairs(mutations);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutations.forEach(args => (this.addMutation as any)(...args));
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
Object.entries(selectors).forEach(([name, sel]: [string, Function]) =>
|
Object.entries(selectors).forEach(([name, sel]: [string, Function]) =>
|
||||||
this.addSelector(name, sel as Selector)
|
this.addSelector(name, sel as Selector)
|
||||||
@ -150,373 +188,389 @@ export class Updux<
|
|||||||
);
|
);
|
||||||
|
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of middlewares 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
|
* updux and its subduxes. Effects of the updux itself are
|
||||||
* done before the subduxes effects.
|
* done before the subduxes effects.
|
||||||
* Note that `getState` will always return the state of the
|
* Note that `getState` will always return the state of the
|
||||||
* local updux.
|
* local updux.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* const middleware = updux.middleware;
|
* const middleware = updux.middleware;
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
get middleware(): UpduxMiddleware<
|
get middleware(): UpduxMiddleware<
|
||||||
AggDuxState<S, C>,
|
AggDuxState<S, C>,
|
||||||
DuxSelectors<AggDuxState<S, C>, X, C>
|
DuxSelectors<AggDuxState<S, C>, X, C>
|
||||||
> {
|
> {
|
||||||
const selectors = this.selectors;
|
const selectors = this.selectors;
|
||||||
const actions = this.actions;
|
const actions = this.actions;
|
||||||
return buildMiddleware(
|
return buildMiddleware(
|
||||||
this.localEffects.map((effect) =>
|
this.localEffects.map(effect =>
|
||||||
effectToMw(effect, actions as any, selectors as any)
|
effectToMw(effect, actions as any, selectors as any)
|
||||||
),
|
),
|
||||||
(this.coduxes as any).map(fp.get('middleware')) as any,
|
(this.coduxes as any).map(fp.get('middleware')) as any,
|
||||||
fp.mapValues('middleware', this.subduxes)
|
fp.mapValues('middleware', this.subduxes)
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Action creators for all actions defined or used in the actions, mutations, effects and subduxes
|
* Action creators for all actions defined or used in the actions, mutations, effects and subduxes
|
||||||
* of the updux config.
|
* of the updux config.
|
||||||
*
|
*
|
||||||
* Non-custom action creators defined in `actions` have the signature `(payload={},meta={}) => ({type,
|
* Non-custom action creators defined in `actions` have the signature `(payload={},meta={}) => ({type,
|
||||||
* payload,meta})` (with the extra sugar that if `meta` or `payload` are not
|
* payload,meta})` (with the extra sugar that if `meta` or `payload` are not
|
||||||
* specified, that key won't be present in the produced action).
|
* specified, that key won't be present in the produced action).
|
||||||
*
|
*
|
||||||
* The same action creator can be included
|
* The same action creator can be included
|
||||||
* in multiple subduxes. However, if two different creators
|
* in multiple subduxes. However, if two different creators
|
||||||
* are included for the same action, an error will be thrown.
|
* are included for the same action, an error will be thrown.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* const actions = updux.actions;
|
* const actions = updux.actions;
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
get actions(): DuxActions<A, C> {
|
get actions(): DuxActions<A, C> {
|
||||||
// UpduxActions<Updux<S,A,SUB,CO>> {
|
// UpduxActions<Updux<S,A,SUB,CO>> {
|
||||||
return this.localActions as any;
|
return this.localActions as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
get upreducer(): Upreducer<S> {
|
get upreducer(): Upreducer<S> {
|
||||||
return buildUpreducer(
|
return buildUpreducer(this.initial, this.mutations as any) as any;
|
||||||
this.initial,
|
}
|
||||||
this.mutations as any
|
|
||||||
) as any;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Redux reducer generated using the computed initial state and
|
* A Redux reducer generated using the computed initial state and
|
||||||
* mutations.
|
* mutations.
|
||||||
*/
|
*/
|
||||||
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);
|
return (state, action) => this.upreducer(action)(state as S);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merge of the updux and subduxes mutations. If an action triggers
|
* Merge of the updux and subduxes mutations. If an action triggers
|
||||||
* mutations in both the main updux and its subduxes, the subduxes
|
* mutations in both the main updux and its subduxes, the subduxes
|
||||||
* mutations will be performed first.
|
* mutations will be performed first.
|
||||||
*/
|
*/
|
||||||
get mutations(): Dictionary<Mutation<S>> {
|
get mutations(): Dictionary<Mutation<S>> {
|
||||||
return buildMutations(
|
return buildMutations(
|
||||||
this.localMutations,
|
this.localMutations,
|
||||||
fp.mapValues('upreducer', this.subduxes as any),
|
fp.mapValues('upreducer', this.subduxes as any),
|
||||||
fp.map('upreducer', this.coduxes as any)
|
fp.map('upreducer', this.coduxes as any)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the upreducer made of the merge of all sudbuxes reducers, without
|
* Returns the upreducer made of the merge of all sudbuxes reducers, without
|
||||||
* the local mutations. Useful, for example, for sink mutations.
|
* the local mutations. Useful, for example, for sink mutations.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* import todo from './todo'; // updux for a single todo
|
* import todo from './todo'; // updux for a single todo
|
||||||
* import Updux from 'updux';
|
* import Updux from 'updux';
|
||||||
* import u from 'updeep';
|
* import u from 'updeep';
|
||||||
*
|
*
|
||||||
* const todos = new Updux({ initial: [], subduxes: { '*': todo } });
|
* const todos = new Updux({ initial: [], subduxes: { '*': todo } });
|
||||||
* todos.addMutation(
|
* todos.addMutation(
|
||||||
* todo.actions.done,
|
* todo.actions.done,
|
||||||
* ({todo_id},action) => u.map( u.if( u.is('id',todo_id) ), todos.subduxUpreducer(action) )
|
* ({todo_id},action) => u.map( u.if( u.is('id',todo_id) ), todos.subduxUpreducer(action) )
|
||||||
* true
|
* true
|
||||||
* );
|
* );
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
get subduxUpreducer() {
|
get subduxUpreducer() {
|
||||||
return buildUpreducer(
|
return buildUpreducer(this.initial, buildMutations({}, this.subduxes));
|
||||||
this.initial,
|
}
|
||||||
buildMutations({}, this.subduxes)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a `createStore` function that takes two argument:
|
* Returns a `createStore` function that takes two argument:
|
||||||
* `initial` and `injectEnhancer`. `initial` is a custom
|
* `initial` and `injectEnhancer`. `initial` is a custom
|
||||||
* initial state for the store, and `injectEnhancer` is a function
|
* initial state for the store, and `injectEnhancer` is a function
|
||||||
* taking in the middleware built by the updux object and allowing
|
* taking in the middleware built by the updux object and allowing
|
||||||
* you to wrap it in any enhancer you want.
|
* you to wrap it in any enhancer you want.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
*
|
*
|
||||||
* ```
|
* ```
|
||||||
* const createStore = updux.createStore;
|
* const createStore = updux.createStore;
|
||||||
*
|
*
|
||||||
* const store = createStore(initial);
|
* const store = createStore(initial);
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
get createStore() {
|
createStore(...args: any) {
|
||||||
return buildCreateStore<AggDuxState<S, C>, DuxActions<A, C>>(
|
const store = buildCreateStore<AggDuxState<S, C>, DuxActions<A, C>>(
|
||||||
this.reducer as any,
|
this.reducer as any,
|
||||||
this.middleware as any,
|
this.middleware as any,
|
||||||
this.actions
|
this.actions
|
||||||
);
|
)(...args);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
_subscribeToStore(store, this.subscriptions);
|
||||||
* Returns a <a href="https://github.com/erikras/ducks-modular-redux">ducks</a>-like
|
|
||||||
* plain object holding the reducer from the Updux object and all
|
|
||||||
* its trimmings.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* ```
|
|
||||||
* const {
|
|
||||||
* createStore,
|
|
||||||
* upreducer,
|
|
||||||
* subduxes,
|
|
||||||
* coduxes,
|
|
||||||
* middleware,
|
|
||||||
* actions,
|
|
||||||
* reducer,
|
|
||||||
* mutations,
|
|
||||||
* initial,
|
|
||||||
* selectors,
|
|
||||||
* } = myUpdux.asDux;
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
get asDux() {
|
|
||||||
return {
|
|
||||||
createStore: this.createStore,
|
|
||||||
upreducer: this.upreducer,
|
|
||||||
subduxes: this.subduxes,
|
|
||||||
coduxes: this.coduxes,
|
|
||||||
middleware: this.middleware,
|
|
||||||
actions: this.actions,
|
|
||||||
reducer: this.reducer,
|
|
||||||
mutations: this.mutations,
|
|
||||||
initial: this.initial,
|
|
||||||
selectors: this.selectors,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return store;
|
||||||
* Adds a mutation and its associated action to the updux.
|
}
|
||||||
*
|
|
||||||
* @param isSink - If `true`, disables the subduxes mutations for this action. To
|
|
||||||
* conditionally run the subduxes mutations, check out [[subduxUpreducer]]. Defaults to `false`.
|
|
||||||
*
|
|
||||||
* @remarks
|
|
||||||
*
|
|
||||||
* If a local mutation was already associated to the action,
|
|
||||||
* it will be replaced by the new one.
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* updux.addMutation(
|
|
||||||
* action('ADD', payload<int>() ),
|
|
||||||
* inc => state => state + in
|
|
||||||
* );
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
addMutation<A extends ActionCreator>(
|
|
||||||
creator: A,
|
|
||||||
mutation: Mutation<S, ActionType<A>>,
|
|
||||||
isSink?: boolean
|
|
||||||
);
|
|
||||||
addMutation<A extends ActionCreator = any>(
|
|
||||||
creator: string,
|
|
||||||
mutation: Mutation<S, any>,
|
|
||||||
isSink?: boolean
|
|
||||||
);
|
|
||||||
addMutation<A extends ActionCreator = any>(
|
|
||||||
creator,
|
|
||||||
mutation,
|
|
||||||
isSink
|
|
||||||
) {
|
|
||||||
const c = this.addAction(creator);
|
|
||||||
|
|
||||||
this.localMutations[c.type] = [
|
/**
|
||||||
this.groomMutations(mutation as any) as Mutation<S>,
|
* Returns an array of all subscription functions registered for the dux.
|
||||||
isSink,
|
* Subdux subscriptions are wrapped such that they are getting their
|
||||||
];
|
* local state. Also all subscriptions are further wrapped such that
|
||||||
}
|
* they are only called when the local state changed
|
||||||
|
*/
|
||||||
|
get subscriptions() {
|
||||||
|
let subscriptions = ([
|
||||||
|
this.localSubscriptions,
|
||||||
|
Object.entries(this.subduxes).map(([slice, subdux]) => {
|
||||||
|
return subdux.subscriptions.map(sub =>
|
||||||
|
sliced_subscription(slice, sub)
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
] as any).flat(Infinity);
|
||||||
|
|
||||||
addEffect<AC extends ActionCreator>(
|
return subscriptions.map(sub => wrap_subscription(sub));
|
||||||
creator: AC,
|
}
|
||||||
middleware: UpduxMiddleware<
|
|
||||||
AggDuxState<S, C>,
|
|
||||||
DuxSelectors<AggDuxState<S, C>, X, C>,
|
|
||||||
ReturnType<AC>
|
|
||||||
>,
|
|
||||||
isGenerator?: boolean
|
|
||||||
);
|
|
||||||
addEffect(
|
|
||||||
creator: string,
|
|
||||||
middleware: UpduxMiddleware<
|
|
||||||
AggDuxState<S, C>,
|
|
||||||
DuxSelectors<AggDuxState<S, C>, X, C>
|
|
||||||
>,
|
|
||||||
isGenerator?: boolean
|
|
||||||
);
|
|
||||||
addEffect(creator, middleware, isGenerator = false) {
|
|
||||||
const c = this.addAction(creator);
|
|
||||||
this.localEffects.push([c.type, middleware, isGenerator] as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
// can be
|
/**
|
||||||
//addAction( actionCreator )
|
* Returns a <a href="https://github.com/erikras/ducks-modular-redux">ducks</a>-like
|
||||||
// addAction( 'foo', transform )
|
* plain object holding the reducer from the Updux object and all
|
||||||
/**
|
* its trimmings.
|
||||||
* Adds an action to the updux. It can take an already defined action
|
*
|
||||||
* creator, or any arguments that can be passed to `actionCreator`.
|
* @example
|
||||||
* @example
|
*
|
||||||
* ```
|
* ```
|
||||||
* const action = updux.addAction( name, ...creatorArgs );
|
* const {
|
||||||
* const action = updux.addAction( otherActionCreator );
|
* createStore,
|
||||||
* ```
|
* upreducer,
|
||||||
* @example
|
* subduxes,
|
||||||
* ```
|
* coduxes,
|
||||||
* import {actionCreator, Updux} from 'updux';
|
* middleware,
|
||||||
*
|
* actions,
|
||||||
* const updux = new Updux();
|
* reducer,
|
||||||
*
|
* mutations,
|
||||||
* const foo = updux.addAction('foo');
|
* initial,
|
||||||
* const bar = updux.addAction( 'bar', (x) => ({stuff: x+1}) );
|
* selectors,
|
||||||
*
|
* subscriptions,
|
||||||
* const baz = actionCreator( 'baz' );
|
* } = myUpdux.asDux;
|
||||||
*
|
* ```
|
||||||
* foo({ a: 1}); // => { type: 'foo', payload: { a: 1 } }
|
*
|
||||||
* bar(2); // => { type: 'bar', payload: { stuff: 3 } }
|
*
|
||||||
* baz(); // => { type: 'baz', payload: undefined }
|
*
|
||||||
* ```
|
*
|
||||||
*/
|
*/
|
||||||
addAction(
|
get asDux() {
|
||||||
theaction: string,
|
return {
|
||||||
transform?: any
|
createStore: this.createStore,
|
||||||
): ActionCreator<string, any>;
|
upreducer: this.upreducer,
|
||||||
addAction(
|
subduxes: this.subduxes,
|
||||||
theaction: string | ActionCreator<any>,
|
coduxes: this.coduxes,
|
||||||
transform?: never
|
middleware: this.middleware,
|
||||||
): ActionCreator<string, any>;
|
actions: this.actions,
|
||||||
addAction(actionIn: any, transform: any) {
|
reducer: this.reducer,
|
||||||
let name: string;
|
mutations: this.mutations,
|
||||||
let creator: ActionCreator;
|
initial: this.initial,
|
||||||
|
selectors: this.selectors,
|
||||||
|
subscriptions: this.subscriptions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof actionIn === 'string') {
|
/**
|
||||||
name = actionIn;
|
* Adds a mutation and its associated action to the updux.
|
||||||
|
*
|
||||||
|
* @param isSink - If `true`, disables the subduxes mutations for this action. To
|
||||||
|
* conditionally run the subduxes mutations, check out [[subduxUpreducer]]. Defaults to `false`.
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
*
|
||||||
|
* If a local mutation was already associated to the action,
|
||||||
|
* it will be replaced by the new one.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* updux.addMutation(
|
||||||
|
* action('ADD', payload<int>() ),
|
||||||
|
* inc => state => state + in
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
addMutation<A extends ActionCreator>(
|
||||||
|
creator: A,
|
||||||
|
mutation: Mutation<S, ActionType<A>>,
|
||||||
|
isSink?: boolean
|
||||||
|
);
|
||||||
|
addMutation<A extends ActionCreator = any>(
|
||||||
|
creator: string,
|
||||||
|
mutation: Mutation<S, any>,
|
||||||
|
isSink?: boolean
|
||||||
|
);
|
||||||
|
addMutation<A extends ActionCreator = any>(creator, mutation, isSink) {
|
||||||
|
const c = this.addAction(creator);
|
||||||
|
|
||||||
if (transform) {
|
this.localMutations[c.type] = [
|
||||||
creator = transform.type
|
this.groomMutations(mutation as any) as Mutation<S>,
|
||||||
? transform
|
isSink,
|
||||||
: action(name, (...args: any) => ({
|
];
|
||||||
payload: transform(...args),
|
}
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
creator =
|
|
||||||
this.localActions[name] ?? action(name, payload());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
name = actionIn.type;
|
|
||||||
creator = actionIn;
|
|
||||||
}
|
|
||||||
|
|
||||||
const already = this.localActions[name];
|
addEffect<AC extends ActionCreator>(
|
||||||
|
creator: AC,
|
||||||
|
middleware: UpduxMiddleware<
|
||||||
|
AggDuxState<S, C>,
|
||||||
|
DuxSelectors<AggDuxState<S, C>, X, C>,
|
||||||
|
ReturnType<AC>
|
||||||
|
>,
|
||||||
|
isGenerator?: boolean
|
||||||
|
);
|
||||||
|
addEffect(
|
||||||
|
creator: string,
|
||||||
|
middleware: UpduxMiddleware<
|
||||||
|
AggDuxState<S, C>,
|
||||||
|
DuxSelectors<AggDuxState<S, C>, X, C>
|
||||||
|
>,
|
||||||
|
isGenerator?: boolean
|
||||||
|
);
|
||||||
|
addEffect(creator, middleware, isGenerator = false) {
|
||||||
|
const c = this.addAction(creator);
|
||||||
|
this.localEffects.push([c.type, middleware, isGenerator] as any);
|
||||||
|
}
|
||||||
|
|
||||||
if (!already)
|
// can be
|
||||||
return ((this.localActions as any)[name] = creator) as any;
|
//addAction( actionCreator )
|
||||||
|
// addAction( 'foo', transform )
|
||||||
|
/**
|
||||||
|
* Adds an action to the updux. It can take an already defined action
|
||||||
|
* creator, or any arguments that can be passed to `actionCreator`.
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const action = updux.addAction( name, ...creatorArgs );
|
||||||
|
* const action = updux.addAction( otherActionCreator );
|
||||||
|
* ```
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* import {actionCreator, Updux} from 'updux';
|
||||||
|
*
|
||||||
|
* const updux = new Updux();
|
||||||
|
*
|
||||||
|
* const foo = updux.addAction('foo');
|
||||||
|
* const bar = updux.addAction( 'bar', (x) => ({stuff: x+1}) );
|
||||||
|
*
|
||||||
|
* const baz = actionCreator( 'baz' );
|
||||||
|
*
|
||||||
|
* foo({ a: 1}); // => { type: 'foo', payload: { a: 1 } }
|
||||||
|
* bar(2); // => { type: 'bar', payload: { stuff: 3 } }
|
||||||
|
* baz(); // => { type: 'baz', payload: undefined }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
addAction(theaction: string, transform?: any): ActionCreator<string, any>;
|
||||||
|
addAction(
|
||||||
|
theaction: string | ActionCreator<any>,
|
||||||
|
transform?: never
|
||||||
|
): ActionCreator<string, any>;
|
||||||
|
addAction(actionIn: any, transform: any) {
|
||||||
|
let name: string;
|
||||||
|
let creator: ActionCreator;
|
||||||
|
|
||||||
if (already !== creator && already.type !== '*') {
|
if (typeof actionIn === 'string') {
|
||||||
throw new Error(`action ${name} already exists`);
|
name = actionIn;
|
||||||
}
|
|
||||||
|
|
||||||
return already;
|
if (transform) {
|
||||||
}
|
creator = transform.type
|
||||||
|
? transform
|
||||||
|
: action(name, (...args: any) => ({
|
||||||
|
payload: transform(...args),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
creator = this.localActions[name] ?? action(name, payload());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name = actionIn.type;
|
||||||
|
creator = actionIn;
|
||||||
|
}
|
||||||
|
|
||||||
get _middlewareEntries() {
|
const already = this.localActions[name];
|
||||||
const groupByOrder = (mws: any) =>
|
|
||||||
fp.groupBy(
|
|
||||||
([, , actionType]: any) =>
|
|
||||||
['^', '$'].includes(actionType)
|
|
||||||
? actionType
|
|
||||||
: 'middle',
|
|
||||||
mws
|
|
||||||
);
|
|
||||||
|
|
||||||
const subs = fp.flow([
|
if (!already)
|
||||||
fp.toPairs,
|
return ((this.localActions as any)[name] = creator) as any;
|
||||||
fp.map(([slice, updux]) =>
|
|
||||||
updux._middlewareEntries.map(([u, ps, ...args]: any) => [
|
|
||||||
u,
|
|
||||||
[slice, ...ps],
|
|
||||||
...args,
|
|
||||||
])
|
|
||||||
),
|
|
||||||
fp.flatten,
|
|
||||||
groupByOrder,
|
|
||||||
])(this.subduxes);
|
|
||||||
|
|
||||||
const local = groupByOrder(
|
if (already !== creator && already.type !== '*') {
|
||||||
this.localEffects.map((x) => [this, [], ...x])
|
throw new Error(`action ${name} already exists`);
|
||||||
);
|
}
|
||||||
|
|
||||||
return fp.flatten(
|
return already;
|
||||||
[
|
}
|
||||||
local['^'],
|
|
||||||
subs['^'],
|
|
||||||
local.middle,
|
|
||||||
subs.middle,
|
|
||||||
subs['$'],
|
|
||||||
local['$'],
|
|
||||||
].filter((x) => x)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
addSelector(name: string, selector: Selector) {
|
get _middlewareEntries() {
|
||||||
this.localSelectors[name] = selector;
|
const groupByOrder = (mws: any) =>
|
||||||
}
|
fp.groupBy(
|
||||||
|
([, , actionType]: any) =>
|
||||||
|
['^', '$'].includes(actionType) ? actionType : 'middle',
|
||||||
|
mws
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
const subs = fp.flow([
|
||||||
|
fp.toPairs,
|
||||||
|
fp.map(([slice, updux]) =>
|
||||||
|
updux._middlewareEntries.map(([u, ps, ...args]: any) => [
|
||||||
|
u,
|
||||||
|
[slice, ...ps],
|
||||||
|
...args,
|
||||||
|
])
|
||||||
|
),
|
||||||
|
fp.flatten,
|
||||||
|
groupByOrder,
|
||||||
|
])(this.subduxes);
|
||||||
|
|
||||||
|
const local = groupByOrder(
|
||||||
|
this.localEffects.map(x => [this, [], ...x])
|
||||||
|
);
|
||||||
|
|
||||||
|
return fp.flatten(
|
||||||
|
[
|
||||||
|
local['^'],
|
||||||
|
subs['^'],
|
||||||
|
local.middle,
|
||||||
|
subs.middle,
|
||||||
|
subs['$'],
|
||||||
|
local['$'],
|
||||||
|
].filter(x => x)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addSelector(name: string, selector: Selector) {
|
||||||
|
this.localSelectors[name] = selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
A dictionary of the updux's selectors. Subduxes'
|
A dictionary of the updux's selectors. Subduxes'
|
||||||
selectors are included as well (with the mapping to the
|
selectors are included as well (with the mapping to the
|
||||||
sub-state already taken care of you).
|
sub-state already taken care of you).
|
||||||
*/
|
*/
|
||||||
get selectors(): DuxSelectors<AggDuxState<S, C>, X, C> {
|
get selectors(): DuxSelectors<AggDuxState<S, C>, X, C> {
|
||||||
return buildSelectors(
|
return buildSelectors(
|
||||||
this.localSelectors,
|
this.localSelectors,
|
||||||
fp.map('selectors', this.coduxes),
|
fp.map('selectors', this.coduxes),
|
||||||
fp.mapValues('selectors', this.subduxes)
|
fp.mapValues('selectors', this.subduxes)
|
||||||
) as any;
|
) as any;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Add a subscription to the dux.
|
||||||
|
*/
|
||||||
|
addSubscription(subscription: Function) {
|
||||||
|
this.localSubscriptions = [...this.localSubscriptions, subscription];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default Updux;
|
export default Updux;
|
||||||
|
Loading…
Reference in New Issue
Block a user