/*
* 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
}
}