2020-01-02 18:53:24 +00:00
|
|
|
import { Dispatch, Middleware } from "redux";
|
2019-10-23 17:25:39 +00:00
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
type MaybePayload<P> = P extends object | string | boolean | number
|
|
|
|
? {
|
|
|
|
payload: P;
|
|
|
|
}
|
2020-01-02 18:53:24 +00:00
|
|
|
: { payload?: P };
|
2019-10-23 17:25:39 +00:00
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
export type Action<T extends string = string, P = any> = {
|
|
|
|
type: T;
|
|
|
|
} & MaybePayload<P>;
|
2019-10-23 17:25:39 +00:00
|
|
|
|
2020-01-02 18:53:24 +00:00
|
|
|
export type Dictionary<T> = { [key: string]: T };
|
2019-10-23 17:28:13 +00:00
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
export type Mutation<S = any, A extends Action = Action> = (
|
2020-01-02 18:53:24 +00:00
|
|
|
payload: A["payload"],
|
|
|
|
action: A
|
2019-11-05 01:34:14 +00:00
|
|
|
) => (state: S) => S;
|
2019-10-23 18:44:12 +00:00
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
export type ActionPayloadGenerator = (...args: any[]) => any;
|
2019-10-23 19:55:12 +00:00
|
|
|
|
2020-01-02 18:53:24 +00:00
|
|
|
export type MutationEntry = [
|
|
|
|
ActionCreator | string,
|
|
|
|
Mutation<any, Action<string, any>>,
|
|
|
|
boolean?
|
|
|
|
];
|
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
export type ActionCreator<T extends string = string, P = any> = {
|
|
|
|
type: T;
|
|
|
|
_genericAction?: boolean;
|
|
|
|
} & ((...args: any[]) => Action<T, P>);
|
2019-10-24 15:17:57 +00:00
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
export type UpduxDispatch = Dispatch & Dictionary<Function>;
|
2019-10-23 21:47:43 +00:00
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
/**
|
2020-01-02 18:53:24 +00:00
|
|
|
* Configuration object given to Updux's constructor.
|
|
|
|
* @typeparam S Type of the Updux's state. Defaults to `any`.
|
|
|
|
*/
|
|
|
|
export type UpduxConfig<S = any> = {
|
2019-11-05 01:34:14 +00:00
|
|
|
/**
|
|
|
|
* The default initial state of the reducer. Can be anything your
|
|
|
|
* heart desires.
|
|
|
|
*/
|
|
|
|
initial?: S;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Object mapping slices of the state to sub-upduxes.
|
|
|
|
*
|
|
|
|
* For example, if in plain Redux you would do
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* import { combineReducers } from 'redux';
|
|
|
|
* import todosReducer from './todos';
|
|
|
|
* import statisticsReducer from './statistics';
|
|
|
|
*
|
|
|
|
* const rootReducer = combineReducers({
|
|
|
|
* todos: todosReducer,
|
|
|
|
* stats: statisticsReducer,
|
|
|
|
* });
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* then with Updux you'd do
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* import { updux } from 'updux';
|
|
|
|
* import todos from './todos';
|
|
|
|
* import statistics from './statistics';
|
|
|
|
*
|
|
|
|
* const rootUpdux = updux({
|
|
|
|
* subduxes: {
|
|
|
|
* todos, statistics
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
*/
|
|
|
|
subduxes?: {};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generic action creations are automatically created from the mutations and effects, but you can
|
|
|
|
* also define custom action creator here. The object's values are function that
|
|
|
|
* transform the arguments of the creator to the action's payload.
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* const { actions } = updox({
|
|
|
|
* mutations: {
|
|
|
|
* foo: () => state => state,
|
|
|
|
* }
|
|
|
|
* actions: {
|
|
|
|
* bar: (x,y) => ({x,y})
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
* actions.foo({ x: 1, y: 2 }); // => { type: foo, payload: { x:1, y:2 } }
|
|
|
|
* actions.bar(1,2); // => { type: bar, payload: { x:1, y:2 } }
|
|
|
|
* ```
|
|
|
|
*/
|
|
|
|
|
|
|
|
actions?: {
|
|
|
|
[type: string]: ActionCreator;
|
|
|
|
};
|
|
|
|
|
2020-01-03 22:27:11 +00:00
|
|
|
selectors?: Dictionary<Selector>;
|
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
/**
|
|
|
|
* Object mapping actions to the associated state mutation.
|
|
|
|
*
|
|
|
|
* For example, in `Redux` you'd do
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* function todosReducer(state=[],action) {
|
|
|
|
*
|
|
|
|
* switch(action.type) {
|
|
|
|
* case 'ADD': return [ ...state, action.payload ];
|
|
|
|
*
|
|
|
|
* case 'DONE': return state.map( todo => todo.id === action.payload
|
|
|
|
* ? { ...todo, done: true } : todo )
|
|
|
|
*
|
|
|
|
* default: return state;
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* With Updux:
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* const todosUpdux = updux({
|
|
|
|
* mutations: {
|
|
|
|
* add: todo => state => [ ...state, todo ],
|
|
|
|
* done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) )
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* 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
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* mutation: {
|
|
|
|
* renameTodo: newName => state => { ...state, name: newName }
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* we can do
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* mutation: {
|
|
|
|
* renameTodo: newName => u({ name: newName })
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* Also, the special key `*` can be used to match any
|
|
|
|
* action not explicitly matched by other mutations.
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* const todosUpdux = updux({
|
|
|
|
* mutations: {
|
|
|
|
* add: todo => state => [ ...state, todo ],
|
|
|
|
* done: done_id => u.map( u.if( ({id} => id === done_id), {done: true} ) ),
|
|
|
|
* '*' (payload,action) => state => {
|
|
|
|
* console.warn( "unexpected action ", action.type );
|
|
|
|
* return state;
|
|
|
|
* },
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
*/
|
2020-01-02 18:53:24 +00:00
|
|
|
mutations?: { [actionType: string]: Mutation<S> } | MutationEntry[];
|
2019-11-05 01:34:14 +00:00
|
|
|
|
|
|
|
groomMutations?: (m: Mutation<S>) => Mutation<S>;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Plain object defining asynchronous actions and side-effects triggered by actions.
|
|
|
|
* The effects themselves are Redux middleware, expect with the `dispatch`
|
|
|
|
* property of the first argument augmented with all the available actions.
|
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* updux({
|
|
|
|
* effects: {
|
|
|
|
* fetch: ({dispatch}) => next => async (action) => {
|
|
|
|
* next(action);
|
|
|
|
*
|
|
|
|
* let result = await fetch(action.payload.url).then( result => result.json() );
|
|
|
|
* dispatch.fetchSuccess(result);
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
*/
|
2020-01-03 01:04:41 +00:00
|
|
|
effects?: Dictionary<UpduxMiddleware<S>> | EffectEntry<S>[];
|
2019-11-05 01:34:14 +00:00
|
|
|
};
|
|
|
|
|
2020-01-03 01:04:41 +00:00
|
|
|
export type EffectEntry<S> = [
|
|
|
|
ActionCreator | string,
|
|
|
|
UpduxMiddleware<S>,
|
|
|
|
boolean?
|
|
|
|
];
|
|
|
|
|
2019-11-05 01:34:14 +00:00
|
|
|
export type Upreducer<S = any> = (action: Action) => (state: S) => S;
|
2019-11-07 00:01:31 +00:00
|
|
|
|
|
|
|
export interface UpduxMiddlewareAPI<S> {
|
2020-01-02 18:53:24 +00:00
|
|
|
dispatch: UpduxDispatch;
|
|
|
|
getState(): any;
|
|
|
|
getRootState(): S;
|
2019-11-07 00:01:31 +00:00
|
|
|
}
|
2020-01-02 18:53:24 +00:00
|
|
|
export type UpduxMiddleware<S = any> = (
|
|
|
|
api: UpduxMiddlewareAPI<S>
|
|
|
|
) => (next: UpduxDispatch) => (action: Action) => any;
|
2020-01-03 17:00:24 +00:00
|
|
|
|
|
|
|
export type Selector<S = any> = (state:S) => unknown;
|