/* * Type definitions for Rematch v2.0.0 * Project: Rematch * Definitions by: * Shawn McKay https://github.com/shmck * Bruno Lemos https://github.com/brunolemos * Przemyslaw Jamrozowicz https://github.com/pjamrozowicz * Tian Zhi https://github.com/tianzhich */ import { Action as ReduxAction, Reducer as ReduxReducer, Dispatch as ReduxDispatch, ReducersMapObject, Middleware, StoreEnhancer, StoreCreator, Store as ReduxStore, ActionCreator, } from 'redux' /** * Utility type used to check if the parameters in reducer/effects is optional * Differentiate `[payload?: number]` from `[payload: number | undefined]` * to improve ts experience */ type CheckIfParameterOptional

= P extends [unknown, ...unknown[]] ? false : true /** * Utility type taken by type-fest repository * https://github.com/sindresorhus/type-fest/blob/main/source/merge-exclusive.d.ts * Merges Exclusively two types into one * Used to fix this https://github.com/rematch/rematch/issues/912 */ type Without = { [KeyType in Exclude]: never } export type MergeExclusive = | FirstType | SecondType extends object ? | (Without & SecondType) | (Without & FirstType) : FirstType | SecondType /** * Custom Action interface, adds an additional field - `payload`. * * Strings (instead of Symbols) are used as the type for `type` field inherited * from Redux, because strings are serializable. * * @template TPayload The type of the action's payload. */ export interface Action extends ReduxAction { payload?: TPayload meta?: TMeta } /** * Custom reducer which instead of an action (like in Redux), accepts payload as * as a second argument. * * @template TState The type of state consumed and produced by this reducer. */ export type Reducer = ( state: TState, payload?: Action['payload'], meta?: Action['meta'] ) => TState | void /** ************************** Model *************************** */ /** * Mapping from a model key to model object. * * @template TModels List of all models */ export interface Models> { [key: string]: Model } export interface NamedModel< TModels extends Models, TState = any, TBaseState = TState > extends Model { name: string reducers: ModelReducers } export interface Model< TModels extends Models, TState = any, TBaseState = TState > { name?: string state: TState reducers?: ModelReducers baseReducer?: ReduxReducer effects?: ModelEffects | ModelEffectsCreator } export type ModelReducers = { [key: string]: Reducer } export interface ModelEffects> { [key: string]: ModelEffect } export type ModelEffectThisTyped = { [key: string]: (payload?: any, meta?: any) => Action } export type ModelEffect> = ( this: ModelEffectThisTyped, payload: Action['payload'], rootState: RematchRootState, meta: Action['meta'] ) => any export type ModelEffectsCreator> = ( dispatch: RematchDispatch ) => ModelEffects /** ************************** Plugin *************************** */ export interface PluginConfig< TModels extends Models, TExtraModels extends Models, TExposedModels = Partial > { models?: TExposedModels redux?: InitConfigRedux } export interface Plugin< TModels extends Models, TExtraModels extends Models = Record, TExposedModels = Partial > extends PluginHooks { config?: PluginConfig exposed?: PluginExposed } export interface PluginHooks< TModels extends Models, TExtraModels extends Models > { onStoreCreated?: StoreCreatedHook onModel?: ModelHook onReducer?: ReducerHook onRootReducer?: RootReducerHook createMiddleware?: MiddlewareCreator } export type ModelHook< TModels extends Models, TExtraModels extends Models > = ( model: NamedModel, rematch: RematchStore ) => void export type ReducerHook< TModels extends Models, TExtraModels extends Models > = ( reducer: ReduxReducer, modelName: string, rematch: RematchBag ) => ReduxReducer | void export type RootReducerHook< TModels extends Models, TExtraModels extends Models > = ( reducer: ReduxReducer, rematch: RematchBag ) => ReduxReducer | void export type StoreCreatedHook< TModels extends Models, TExtraModels extends Models > = ( store: RematchStore, rematch: RematchBag ) => RematchStore | void export type MiddlewareCreator< TModels extends Models, TExtraModels extends Models = Record > = (rematch: RematchBag) => Middleware export type ObjectNotAFunction = { [k: string]: any } & ( | { bind?: never } | { call?: never } ) export type PluginExposed< TModels extends Models, TExtraModels extends Models > = { [key: string]: ExposedFunction | ObjectNotAFunction } export type ExposedFunction< TModels extends Models, TExtraModels extends Models > = (rematch: RematchStore, ...args: any) => any /** ************************** Rematch *************************** */ /** * Object for storing information needed for the Rematch store to run. * Purposefully hidden from the end user. */ export interface RematchBag< TModels extends Models, TExtraModels extends Models > { models: NamedModel[] reduxConfig: ConfigRedux forEachPlugin: >( method: Hook, fn: (content: NonNullable[Hook]>) => void ) => void effects: ModelEffects } /** * Initial, optional configuration provided by the user which describes how * Rematch store should be configured. */ export interface InitConfig< TModels extends Models, TExtraModels extends Models > { name?: string models?: TModels | Partial plugins?: Plugin[] redux?: InitConfigRedux } /** * Config created out of the InitConfig by filling in missing properties with * default values and merging in modifications required by plugins * (new models, etc.). */ export interface Config< TModels extends Models, TExtraModels extends Models > extends InitConfig { name: string models: TModels | Partial plugins: Plugin[] redux: ConfigRedux } /** ************************** Rematch-Redux *************************** */ /** * Initial, optional configuration for Redux, provided by the user. It allows * to gain full control over the way Redux is configured by Rematch and * override any defaults. */ export interface InitConfigRedux< TRootState = any, DevtoolComposerGeneric = any > { initialState?: TRootState reducers?: ModelReducers enhancers?: StoreEnhancer[] middlewares?: Middleware[] rootReducers?: ReducersMapObject combineReducers?: | (( reducers: ReducersMapObject ) => ReduxReducer) | undefined createStore?: StoreCreator | undefined devtoolOptions?: DevtoolOptions devtoolComposer?: DevtoolComposerGeneric } /** * Config created out of InitConfigRedux by supplying default values in place * of missing properties. */ export interface ConfigRedux extends InitConfigRedux { reducers: ModelReducers enhancers: StoreEnhancer[] middlewares: Middleware[] rootReducers: ReducersMapObject } export interface RematchStore< TModels extends Models, TExtraModels extends Models > extends ReduxStore, Action> { [index: string]: | ExposedFunction | Record | string name: string dispatch: RematchDispatch addModel: (model: NamedModel) => void } /** ************************** Root State *************************** */ /** * The type of state held by a store. */ export type RematchRootState< TModels extends Models, TExtraModels extends Models = Record > = ExtractRematchStateFromModels /** * A mapping from each model's name to a type of state it holds. */ export type ExtractRematchStateFromModels< TModels extends Models, TExtraModels extends Models > = { [modelKey in keyof TModels]: TModels[modelKey]['state'] } & { [modelKey in keyof TExtraModels]: TExtraModels[modelKey]['state'] } /** ************************** Dispatch *************************** */ /** * Rematch dispatch is a combination of regular redux dispatch method and * an object allowing to dispatch specific actions by calling it the form of * dispatch[modelName][reducerName | effectName](payload). */ export type RematchDispatch> = ReduxDispatch & ExtractRematchDispatchersFromModels /** * Goes over all models and extracts from each a type for dispatcher object * created by Rematch. */ export type ExtractRematchDispatchersFromModels< TModels extends Models > = { [modelKey in keyof TModels]: TModels[modelKey] extends Model ? ModelDispatcher : never } /** * Combines together types extracted from reducers and effects for a model. */ export type ModelDispatcher< TModel extends Model, TModels extends Models > = MergeExclusive< ExtractRematchDispatchersFromEffects, ExtractRematchDispatchersFromReducers< TModel['state'], TModel['reducers'], TModels > > /** * Get return type of rematch dispatcher */ export type ReturnOfDispatcher< IsEffect extends boolean, TReturn = any, TPayload = void, TMeta = void > = IsEffect extends true ? TReturn : Action /** * When payload is of type never, it describes 'empty' dispatcher - meaning * it's a function not taking any arguments and returning an action. * Otherwise, it describes dispatcher which accepts two optional argument (payload/meta) * and returns an action. */ export type RematchDispatcher< IsEffect extends boolean, TPayload extends [p?: unknown] = never, TMeta extends [m?: unknown] = never, TReturn = any > = [TPayload, TMeta] extends [never, never] ? (() => ReturnOfDispatcher) & { isEffect: IsEffect } : [TMeta] extends [never] ? CheckIfParameterOptional extends true ? (( payload?: TPayload[0] ) => ReturnOfDispatcher) & { isEffect: IsEffect } : (( payload: TPayload[0] ) => ReturnOfDispatcher) & { isEffect: IsEffect } : CheckIfParameterOptional extends true ? CheckIfParameterOptional extends true ? (( payload?: TPayload[0], meta?: TMeta[0] ) => ReturnOfDispatcher) & { isEffect: IsEffect } : (( payload: TPayload[0], meta?: TMeta[0] ) => ReturnOfDispatcher) & { isEffect: IsEffect } : (( payload: TPayload[0], meta: TMeta[0] ) => ReturnOfDispatcher) & { isEffect: IsEffect } /** ************************ Reducers Dispatcher ************************* */ /** * Utility type used to extract the whole payload/meta parameter type * from reducer parameters * For example, extract `[meta?: string]` * from `[payload: number, meta?: string]` */ type ExtractParameterFromReducer< P extends unknown[], V extends 'payload' | 'meta' > = P extends [] ? never : P extends [p?: infer TPayload] ? V extends 'payload' ? P extends [infer TPayloadMayUndefined, ...unknown[]] ? [p: TPayloadMayUndefined] : [p?: TPayload] : never : P extends [p?: infer TPayload, m?: infer TMeta, ...args: unknown[]] ? V extends 'payload' ? P extends [infer TPayloadMayUndefined, ...unknown[]] ? [p: TPayloadMayUndefined] : [p?: TPayload] : P extends [unknown, infer TMetaMayUndefined, ...unknown[]] ? [m: TMetaMayUndefined] : [m?: TMeta] : never /** * Extracts a dispatcher for each reducer that is defined for a model. */ export type ExtractRematchDispatchersFromReducers< TState, TReducers extends Model['reducers'], TModels extends Models > = { [reducerKey in keyof TReducers]: ExtractRematchDispatcherFromReducer< TState, TReducers[reducerKey] > } /** * Matches a reducer to different forms and based on the form, selects an * appropriate type for a dispatcher. Mapping goes like this: * - reducer not taking any parameters -> 'empty' dispatcher * - reducer only taking state -> 'empty' dispatcher * - reducer taking state and optional payload (and may also taking optional meta) * -> dispatcher accepting payload and meta as arguments */ export type ExtractRematchDispatcherFromReducer = TReducer extends () => any ? RematchDispatcher : TReducer extends (state: TState, ...args: infer TRest) => TState | void ? TRest extends [] ? RematchDispatcher : TRest[1] extends undefined ? RematchDispatcher> : RematchDispatcher< false, ExtractParameterFromReducer, ExtractParameterFromReducer > : never /** ************************ Effects Dispatcher ************************* */ /** * Based on the shape of 'effects' property it extracts dispatchers from it. * 'effects' can be: * - empty - in this case the type is just never * - an object defining effects * - a function returning effects * If it's a function it infers its return type which must define effects. */ export type ExtractRematchDispatchersFromEffects< TEffects extends Model['effects'], TModels extends Models > = TEffects extends (...args: any[]) => infer R ? R extends ModelEffects ? ExtractRematchDispatchersFromEffectsObject : never : TEffects extends ModelEffects ? ExtractRematchDispatchersFromEffectsObject : never /** * Extracts a dispatcher for each effect that is defined for a model. */ export type ExtractRematchDispatchersFromEffectsObject< TEffects extends ModelEffects, TModels extends Models > = { [effectKey in keyof TEffects]: ExtractRematchDispatcherFromEffect< TEffects[effectKey], TModels > } /** * Utility type used to extract the whole payload/meta parameter type * from effect parameters * For example, extract `[meta?: string]` * from `[payload: number, state: RootState, meta?: string]` */ type ExtractParameterFromEffect< P extends unknown[], V extends 'payload' | 'meta' > = P extends [] ? never : P extends [p?: infer TPayload, s?: unknown] ? V extends 'payload' ? P extends [infer TPayloadMayUndefined, ...unknown[]] ? [p: TPayloadMayUndefined] : [p?: TPayload] : never : P extends [ p?: infer TPayload, s?: unknown, m?: infer TMeta, ...args: unknown[] ] ? V extends 'payload' ? P extends [infer TPayloadMayUndefined, ...unknown[]] ? [p: TPayloadMayUndefined] : [p?: TPayload] : P extends [unknown, unknown, infer TMetaMayUndefined, ...unknown[]] ? [m: TMetaMayUndefined] : [m?: TMeta] : never /** * Matches an effect to different forms and based on the form, selects an * appropriate type for a dispatcher. Mapping goes like this: * - effect not taking any parameters -> 'empty' dispatcher * - effect only taking payload -> dispatcher accepting payload as an argument * - effect taking both payload and root state -> dispatcher accepting payload * as an argument * - effect taking payload, root state and meta -> dispatcher accepting payload * and meta as arguments */ export type ExtractRematchDispatcherFromEffect< TEffect extends ModelEffect, TModels extends Models > = TEffect extends (...args: infer TRest) => infer TReturn ? TRest extends [] ? RematchDispatcher : TRest[1] extends undefined ? RematchDispatcher< true, ExtractParameterFromEffect, never, TReturn > : TRest[2] extends undefined ? RematchDispatcher< true, ExtractParameterFromEffect, never, TReturn > : RematchDispatcher< true, ExtractParameterFromEffect, ExtractParameterFromEffect, TReturn > : never export interface DevtoolOptions { /** * Disables Devtools options, useful for production usages */ disabled?: boolean /** * the instance name to be showed on the monitor page. Default value is `document.title`. * If not specified and there's no document title, it will consist of `tabId` and `instanceId`. */ name?: string /** * action creators functions to be available in the Dispatcher. */ actionCreators?: ActionCreator[] | { [key: string]: ActionCreator } /** * if more than one action is dispatched in the indicated interval, all new actions will be collected and sent at once. * It is the joint between performance and speed. When set to `0`, all actions will be sent instantly. * Set it to a higher value when experiencing perf issues (also `maxAge` to a lower value). * * @default 500 ms. */ latency?: number /** * (> 1) - maximum allowed actions to be stored in the history tree. The oldest actions are removed once maxAge is reached. It's critical for performance. * * @default 50 */ maxAge?: number /** * - `undefined` - will use regular `JSON.stringify` to send data (it's the fast mode). * - `false` - will handle also circular references. * - `true` - will handle also date, regex, undefined, error objects, symbols, maps, sets and functions. * - object, which contains `date`, `regex`, `undefined`, `error`, `symbol`, `map`, `set` and `function` keys. * For each of them you can indicate if to include (by setting as `true`). * For `function` key you can also specify a custom function which handles serialization. * See [`jsan`](https://github.com/kolodny/jsan) for more details. */ serialize?: | boolean | { date?: boolean regex?: boolean undefined?: boolean error?: boolean symbol?: boolean map?: boolean set?: boolean function?: boolean | Function } /** * function which takes `action` object and id number as arguments, and should return `action` object back. */ actionSanitizer?: (action: A, id: number) => A /** * function which takes `state` object and index as arguments, and should return `state` object back. */ stateSanitizer?: (state: S, index: number) => S /** * *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers). * If `actionsWhitelist` specified, `actionsBlacklist` is ignored. */ actionsBlacklist?: string | string[] /** * *string or array of strings as regex* - actions types to be hidden / shown in the monitors (while passed to the reducers). * If `actionsWhitelist` specified, `actionsBlacklist` is ignored. */ actionsWhitelist?: string | string[] /** * called for every action before sending, takes `state` and `action` object, and returns `true` in case it allows sending the current data to the monitor. * Use it as a more advanced version of `actionsBlacklist`/`actionsWhitelist` parameters. */ predicate?: (state: S, action: A) => boolean /** * if specified as `false`, it will not record the changes till clicking on `Start recording` button. * Available only for Redux enhancer, for others use `autoPause`. * * @default true */ shouldRecordChanges?: boolean /** * if specified, whenever clicking on `Pause recording` button and there are actions in the history log, will add this action type. * If not specified, will commit when paused. Available only for Redux enhancer. * * @default "@@PAUSED"" */ pauseActionType?: string /** * auto pauses when the extension’s window is not opened, and so has zero impact on your app when not in use. * Not available for Redux enhancer (as it already does it but storing the data to be sent). * * @default false */ autoPause?: boolean /** * if specified as `true`, it will not allow any non-monitor actions to be dispatched till clicking on `Unlock changes` button. * Available only for Redux enhancer. * * @default false */ shouldStartLocked?: boolean /** * if set to `false`, will not recompute the states on hot reloading (or on replacing the reducers). Available only for Redux enhancer. * * @default true */ shouldHotReload?: boolean /** * if specified as `true`, whenever there's an exception in reducers, the monitors will show the error message, and next actions will not be dispatched. * * @default false */ shouldCatchErrors?: boolean /** * If you want to restrict the extension, specify the features you allow. * If not specified, all of the features are enabled. When set as an object, only those included as `true` will be allowed. * Note that except `true`/`false`, `import` and `export` can be set as `custom` (which is by default for Redux enhancer), meaning that the importing/exporting occurs on the client side. * Otherwise, you'll get/set the data right from the monitor part. */ features?: { /** * start/pause recording of dispatched actions */ pause?: boolean /** * lock/unlock dispatching actions and side effects */ lock?: boolean /** * persist states on page reloading */ persist?: boolean /** * export history of actions in a file */ export?: boolean | 'custom' /** * import history of actions from a file */ import?: boolean | 'custom' /** * jump back and forth (time travelling) */ jump?: boolean /** * skip (cancel) actions */ skip?: boolean /** * drag and drop actions in the history list */ reorder?: boolean /** * dispatch custom actions or action creators */ dispatch?: boolean /** * generate tests for the selected actions */ test?: boolean } /** * Set to true or a stacktrace-returning function to record call stack traces for dispatched actions. * Defaults to false. */ trace?: boolean | ((action: A) => string) /** * The maximum number of stack trace entries to record per action. Defaults to 10. */ traceLimit?: number [key: string]: any } export interface ModelCreator { >(): < R extends ModelReducers | undefined, BR extends ReduxReducer | undefined, E extends ModelEffects | ModelEffectsCreator | undefined, S, BS = S >(mo: { name?: string state: S reducers?: R baseReducer?: BR effects?: E }) => { name?: string state: S } & (E extends undefined ? {} : { effects: E }) & (R extends undefined ? {} : { reducers: R }) & (BR extends undefined ? {} : { baseReducer: BR }) } declare module 'redux' { export interface Dispatch { [modelName: string]: any } } /** * Patch for the incompatibility between Redux.Dispatch and RematchDispatch */ declare module 'react-redux' { interface Connect { , State, TStateProps, TDispatchProps, TOwnProps>( mapStateToProps: MapStateToPropsParam, mapDispatchToProps: MapRematchDispatchToPropsNonObject< TDispatchProps, TOwnProps, RM > ): InferableComponentEnhancerWithProps< TStateProps & TDispatchProps, TOwnProps > } type MapRematchDispatchToPropsNonObject< TDispatchProps, TOwnProps, RM extends Models > = | MapRematchDispatchToPropsFactory | MapRematchDispatchToPropsFunction type MapRematchDispatchToPropsFactory< TDispatchProps, TOwnProps, RM extends Models > = ( dispatch: any, ownProps: TOwnProps ) => MapRematchDispatchToPropsFunction type MapRematchDispatchToPropsFunction< TDispatchProps, TOwnProps, RM extends Models > = (dispatch: any, ownProps: TOwnProps) => TDispatchProps } declare global { interface Window { __REDUX_DEVTOOLS_EXTENSION_COMPOSE__?: any } }