UNPKG

easy-peasy

Version:
621 lines (584 loc) 14.6 kB
/* eslint-disable */ import { Component } from 'react'; import { Diff, KeysOfType, Omit, OptionalKeys, Overwrite, RequiredKeys, } from 'typelevel-ts'; import { Param0, Param1 } from 'type-zoo'; import { compose, AnyAction, Action as ReduxAction, Dispatch as ReduxDispatch, Reducer as ReduxReducer, Store as ReduxStore, StoreEnhancer, Middleware, } from 'redux'; import { string } from 'prop-types'; /** * This allows you to narrow keys of an object type that are index signature * based. * * Based on answer from here: * https://stackoverflow.com/questions/56422807/narrowing-a-type-to-its-properties-that-are-index-signatures/56423972#56423972 */ type IndexSignatureKeysOfType<A extends Object> = { [K in keyof A]: A[K] extends { [key: string]: any } ? string extends keyof A[K] ? K : never : never }[keyof A]; type ActionTypes = Action<any, any> | Thunk<any, any, any, any, any>; type Meta = { path: string[]; parent: string[]; }; export function actionName(action: Action<any, any>): string; export function thunkStartName(action: Thunk<any, any, any, any, any>): string; export function thunkCompleteName( action: Thunk<any, any, any, any, any>, ): string; export function thunkFailName(action: Thunk<any, any, any, any, any>): string; type FilterActionTypes<T extends object> = Omit< T, KeysOfType< T, | Array<any> | Date | RegExp | Reducer<any, any> | Select<any, any> | Listen<any, any, any> > >; type FilterStateTypes<T extends object> = Overwrite< Omit< T, KeysOfType< T, Action<any, any> | Listen<any, any, any> | Thunk<any, any, any, any, any> > >, Pick<T, KeysOfType<T, Select<any, any> | Reducer<any, any>>> >; /** * Filters a model into a type that represents the actions (and effects) only * * @example * * type OnlyActions = Actions<Model>; */ export type Actions<Model extends Object> = { [P in keyof FilterActionTypes< Pick<Model, KeysOfType<Model, object>> >]: Model[P] extends Thunk<any, any, any, any, infer R> ? Param1<Model[P]> extends void ? () => R extends Promise<any> ? R : Promise<R> : (payload: Param1<Model[P]>) => R extends Promise<any> ? R : Promise<R> : Model[P] extends Action<any, any> ? Param1<Model[P]> extends void ? () => void : (payload: Param1<Model[P]>) => void : Actions<Model[P]> }; type RequiredOnly<Model extends Object> = Pick<Model, RequiredKeys<Model>>; type OptionalOnly<Model extends Object> = Pick<Model, OptionalKeys<Model>>; type StateModelValues< Model extends Object, P extends keyof Model > = Model[P] extends Select<any, infer R> ? R : Model[P] extends Reducer<infer R, any> ? R : Model[P] extends object ? Model[P] extends Array<any> | RegExp | Date | Function ? Model[P] : State<Model[P]> : Model[P]; type RecursiveState< OtherModel extends Object, RequiredModel extends Object, OptionalModel extends Object > = Overwrite< { [P in keyof OtherModel]: OtherModel[P] }, Overwrite< { [P in keyof RequiredModel]: StateModelValues<RequiredModel, P> }, { [P in keyof OptionalModel]?: StateModelValues<OptionalModel, P> } > >; type FilterIndexSignatures< StateModel extends Object, RequiredStateModel extends Object, OptionalStateModel extends Object > = RecursiveState< StateModel, Omit<RequiredStateModel, keyof StateModel>, Omit<OptionalStateModel, keyof StateModel> >; /** * Filters a model into a type that represents the state only (i.e. no actions) * * @example * * type StateOnly = State<Model>; */ export type State<Model extends Object> = FilterIndexSignatures< Pick<Model, IndexSignatureKeysOfType<Model>>, FilterStateTypes<RequiredOnly<Model>>, FilterStateTypes<OptionalOnly<Model>> >; /** * Configuration interface for the createStore */ export interface EasyPeasyConfig< InitialState extends Object = {}, Injections = any > { compose?: typeof compose; devTools?: boolean; enhancers?: StoreEnhancer[]; initialState?: InitialState; injections?: Injections; middleware?: Array<Middleware<any, any, any>>; mockActions?: boolean; name?: string; reducerEnhancer?: (reducer: Reducer<any, any>) => Reducer<any, any>; } /** * Enhances the Redux Dispatch with actions * * @example * * type DispatchWithActions = Dispatch<StoreModel>; */ export type Dispatch< StoreModel, Action extends ReduxAction = ReduxAction<any> > = Actions<StoreModel> & ReduxDispatch<Action>; export interface ActionData { type: string; [key: string]: any; } /** * Represents a Redux store, enhanced by easy peasy. * * @example * * type EnhancedReduxStore = Store<StoreModel>; */ export type Store< StoreModel, StoreConfig extends EasyPeasyConfig<any, any> = any > = Overwrite< ReduxStore<State<StoreModel>>, { dispatch: Dispatch<StoreModel>; getMockedActions: () => ActionData[]; clearMockedActions: () => void; triggerListener: <TriggerAction extends ActionTypes | string>( listener: Listen<any>, action: TriggerAction, payload: TriggerAction extends Action<any, infer ActionPayload> ? ActionPayload : TriggerAction extends Thunk<any, infer ThunkPayload> ? ThunkPayload : any, ) => Promise<void>; triggerListeners: <TriggerAction extends ActionTypes | string>( action: TriggerAction, payload: TriggerAction extends Action<any, infer ActionPayload> ? ActionPayload : TriggerAction extends Thunk<any, infer ThunkPayload> ? ThunkPayload : any, ) => Promise<void>; } >; /** * A thunk type. * * Useful when declaring your model. * * @example * * import { Thunk } from 'easy-peasy'; * * interface TodosModel { * todos: Array<string>; * addTodo: Thunk<TodosModel, string>; * } */ export type Thunk< Model extends Object = {}, Payload = void, Injections = any, StoreModel extends Object = {}, Result = any > = { ( actions: Actions<Model>, payload: Payload, helpers: { dispatch: Dispatch<StoreModel>; getState: () => State<Model>; getStoreState: () => State<StoreModel>; injections: Injections; meta: Meta; }, ): Result; type: 'thunk'; payload: Payload; result: Result; }; /** * Declares an thunk action type against your model. * * https://github.com/ctrlplusb/easy-peasy#thunkaction * * @example * * import { thunk } from 'easy-peasy'; * * const store = createStore({ * login: thunk(async (actions, payload) => { * const user = await loginService(payload); * actions.loginSucceeded(user); * }) * }); */ export function thunk< Model extends Object = {}, Payload = void, Injections = any, StoreModel extends Object = {}, Result = any >( thunk: ( actions: Actions<Model>, payload: Payload, helpers: { dispatch: Dispatch<StoreModel>; getState: () => State<Model>; getStoreState: () => State<StoreModel>; injections: Injections; meta: Meta; }, ) => Result, ): Thunk<Model, Payload, Injections, StoreModel, Result>; /** * Action listeners type. * * Useful when declaring your model. * * @example * * import { Listeners } from 'easy-peasy'; * * interface Model { * userListeners: Listeners<Model>; * } * * listen(on => { * on() * }) */ export type Listen< Model extends Object = {}, Injections = any, StoreModel extends Object = {} > = { type: 'listen'; }; /** * Declares action listeners against your model. * * https://github.com/ctrlplusb/easy-peasy#listenersattach * * @example * * import { listen } from 'easy-peasy'; * * const store = createStore({ * users: userModel, * audit: { * logs: [], * add: (state, payload) => { * state.logs.push(payload) * }, * userListeners: listen((on) => { * on(userModel.login, (actions) => { * actions.add('User logged in'); * }); * }) * } * }); */ export function listen< Model extends Object = {}, Injections = any, StoreModel extends Object = {} >( attach: ( on: <ListenAction extends ActionTypes | string>( action: ListenAction, handler: | Thunk< Model, ListenAction extends string ? any : ListenAction extends Thunk<any, any, any, any, any> ? ListenAction['payload'] : Param1<ListenAction>, Injections, StoreModel > | Action< Model, ListenAction extends string ? any : ListenAction extends Thunk<any, any, any, any, any> ? ListenAction['payload'] : Param1<ListenAction> >, ) => void, ) => void, ): Listen<Model, Injections, StoreModel>; /** * An action type. * * Useful when declaring your model. * * @example * * import { Action } from 'easy-peasy'; * * interface Model { * todos: Array<Todo>; * addTodo: Action<Model, Todo>; * } */ export type Action<Model extends Object = {}, Payload = void> = { (state: State<Model>, payload: Payload): void | State<Model>; type: 'action'; payload: Payload; result: void | State<Model>; }; /** * Declares an action type against your model. * * https://github.com/ctrlplusb/easy-peasy#action * * @example * * import { action } from 'easy-peasy'; * * const store = createStore({ * count: 0, * increment: action((state)) => { * state.count += 1; * }) * }); */ export function action<Model extends Object = {}, Payload = any>( action: (state: State<Model>, payload: Payload) => void | State<Model>, ): Action<Model, Payload>; /** * A select type. * * Useful when declaring your model. * * @example * * import { Select } from 'easy-peasy'; * * interface Model { * products: Array<Product>; * totalPrice: Select<Model, number>; * } */ export type Select<Model extends Object = {}, Result = any> = { (state: State<Model>): Result; type: 'select'; result: Result; }; /** * Allows you to declare derived state against your model. * * https://github.com/ctrlplusb/easy-peasy#selectselector * * @example * * import { select } from 'easy-peasy'; * * const store = createStore({ * products: [], * totalPrice: select(state => * state.products.reduce((acc, cur) => acc + cur.price, 0) * ) * }); */ export function select<Model extends Object = {}, Result = any>( select: (state: State<Model>) => Result, dependencies?: Array<Select<any, any>>, ): Select<Model, Result>; /** * A reducer type. * * Useful when declaring your model. * * @example * * import { Reducer } from 'easy-peasy'; * * interface Model { * router: Reducer<ReactRouterState>; * } */ export type Reducer<State = any, Action extends ReduxAction = AnyAction> = { (state: State, action: Action): State; type: 'reducer'; result: State; }; /** * Allows you to declare a custom reducer to manage a bit of your state. * * https://github.com/ctrlplusb/easy-peasy#reducerfn * * @example * * import { reducer } from 'easy-peasy'; * * const store = createStore({ * counter: reducer((state = 1, action) => { * switch (action.type) { * case 'INCREMENT': return state + 1; * default: return state; * } * }) * }); */ export function reducer<State extends Object = {}>( state: ReduxReducer<State>, ): Reducer<State>; /** * Creates an easy-peasy powered Redux store. * * https://github.com/ctrlplusb/easy-peasy#createstoremodel-config * * @example * * import { createStore } from 'easy-peasy'; * * interface StoreModel { * todos: { * items: Array<string>; * } * } * * const store = createStore<StoreModel>({ * todos: { * items: [], * } * }) */ export function createStore< StoreModel extends Object = {}, StoreConfig extends EasyPeasyConfig<any, any> = any >(model: StoreModel, config?: StoreConfig): Store<StoreModel, StoreConfig>; /** * A React Hook allowing you to use state within your component. * * https://github.com/ctrlplusb/easy-peasy#usestoremapstate-externals * * @example * * import { useStore, State } from 'easy-peasy'; * * function MyComponent() { * const todos = useStore((state: State<StoreModel>) => state.todos.items); * return todos.map(todo => <Todo todo={todo} />); * } */ export function useStore<StoreState extends State<any> = {}, Result = any>( mapState: (state: StoreState) => Result, dependencies?: Array<any>, ): Result; /** * A React Hook allowing you to use actions within your component. * * https://github.com/ctrlplusb/easy-peasy#useactionsmapactions * * @example * * import { useActions, Actions } from 'easy-peasy'; * * function MyComponent() { * const addTodo = useAction((actions: Actions<StoreModel>) => actions.todos.add); * return <AddTodoForm save={addTodo} />; * } */ export function useActions<StoreModel extends Object = {}, Result = any>( mapActions: (actions: Actions<StoreModel>) => Result, ): Result; /** * A React Hook allowing you to use the store's dispatch within your component. * * https://github.com/ctrlplusb/easy-peasy#usedispatch * * @example * * import { useDispatch } from 'easy-peasy'; * * function MyComponent() { * const dispatch = useDispatch(); * return <AddTodoForm save={(todo) => dispatch({ type: 'ADD_TODO', payload: todo })} />; * } */ export function useDispatch<StoreModel extends Object = {}>(): Dispatch< StoreModel >; /** * A utility function used to create pre-typed hooks. * * @example * const { useActions, useStore, useDispatch } = createTypedHooks<StoreModel>(); * * useActions(actions => actions.todo.add); // fully typed */ export function createTypedHooks<StoreModel extends Object = {}>(): { useActions: <Result = any>( mapActions: (actions: Actions<StoreModel>) => Result, ) => Result; useAction: <Result = any>( mapAction: (actions: Dispatch<StoreModel>) => Result, ) => Result; useDispatch: () => Dispatch<StoreModel>; useStore: <Result = any>( mapState: (state: State<StoreModel>) => Result, dependencies?: Array<any>, ) => Result; }; /** * Exposes the store to your app (and hooks). * * https://github.com/ctrlplusb/easy-peasy#storeprovider * * @example * * import { StoreProvider } from 'easy-peasy'; * * ReactDOM.render( * <StoreProvider store={store}> * <App /> * </StoreProvider> * ); */ export class StoreProvider<StoreModel = any> extends Component<{ store: Store<StoreModel>; }> {}