/** * External dependencies */ import type { // eslint-disable-next-line no-restricted-imports combineReducers as reduxCombineReducers, Store as ReduxStore, } from 'redux'; /** * Internal dependencies */ import type { DataEmitter } from './utils/emitter'; import type { MetadataSelectors, MetadataActions, } from './redux-store/metadata/types'; type MapOf< T > = { [ name: string ]: T }; export type ActionCreator = ( ...args: any[] ) => any | Generator; export type Resolver = Function | Generator; export type Selector = Function; export type AnyConfig = ReduxStoreConfig< any, any, any >; export interface StoreInstance< Config extends AnyConfig > { getSelectors: () => SelectorsOf< Config >; getActions: () => ActionCreatorsOf< Config >; subscribe: ( listener: () => void ) => () => void; } export interface StoreDescriptor< Config extends AnyConfig = AnyConfig > { /** * Store Name */ name: string; /** * Creates a store instance */ instantiate: ( registry: DataRegistry ) => StoreInstance< Config >; } export interface ReduxStoreConfig< State, ActionCreators, Selectors > { initialState?: State; reducer: ( state: any, action: any ) => any; actions?: ActionCreators; resolvers?: MapOf< Resolver >; selectors?: Selectors; controls?: MapOf< Function >; } // Return type for the useSelect() hook. export type UseSelectReturn< F extends MapSelect | StoreDescriptor< any > > = F extends MapSelect ? ReturnType< F > : F extends StoreDescriptor< any > ? CurriedSelectorsOf< F > : never; // Return type for the useDispatch() hook. export type UseDispatchReturn< StoreNameOrDescriptor > = StoreNameOrDescriptor extends StoreDescriptor< any > ? ActionCreatorsOf< StoreNameOrDescriptor > : StoreNameOrDescriptor extends undefined ? DispatchFunction : any; export type DispatchFunction = < StoreNameOrDescriptor >( store: StoreNameOrDescriptor ) => DispatchReturn< StoreNameOrDescriptor >; export type DispatchReturn< StoreNameOrDescriptor > = StoreNameOrDescriptor extends StoreDescriptor< any > ? ActionCreatorsOf< StoreNameOrDescriptor > : unknown; export type MapSelect = ( select: SelectFunction, registry: DataRegistry ) => any; export type SelectFunction = < S >( store: S ) => CurriedSelectorsOf< S >; /** * Callback for store's `subscribe()` method that * runs when the store data has changed. */ export type ListenerFunction = () => void; export type CurriedSelectorsOf< S > = S extends StoreDescriptor< ReduxStoreConfig< any, any, infer Selectors > > ? { [ key in keyof Selectors ]: CurriedState< Selectors[ key ] >; } & MetadataSelectors< S > : never; /** * Like CurriedState but wraps the return type in a Promise. * Used for resolveSelect where selectors return promises. * * For generic selectors that define PromiseCurriedSignature, that signature * is used directly to preserve generic type parameters (which would otherwise * be lost when using `infer`). */ type CurriedStateWithPromise< F > = F extends SelectorWithCustomCurrySignature & { PromiseCurriedSignature: infer S; } ? S : F extends SelectorWithCustomCurrySignature & { CurriedSignature: ( ...args: infer P ) => infer R; } ? ( ...args: P ) => Promise< R > : F extends ( state: any, ...args: infer P ) => infer R ? ( ...args: P ) => Promise< R > : F; /** * Like CurriedSelectorsOf but each selector returns a Promise. * Used for resolveSelect. */ export type CurriedSelectorsResolveOf< S > = S extends StoreDescriptor< ReduxStoreConfig< any, any, infer Selectors > > ? { [ key in keyof Selectors ]: CurriedStateWithPromise< Selectors[ key ] >; } : never; /** * Removes the first argument from a function. * * By default, it removes the `state` parameter from * registered selectors since that argument is supplied * by the editor when calling `select(…)`. * * For functions with no arguments, which some selectors * are free to define, returns the original function. * * It is possible to manually provide a custom curried signature * and avoid the automatic inference. When the * F generic argument passed to this helper extends the * SelectorWithCustomCurrySignature type, the F['CurriedSignature'] * property is used verbatim. * * This is useful because TypeScript does not correctly remove * arguments from complex function signatures constrained by * interdependent generic parameters. * For more context, see https://github.com/WordPress/gutenberg/pull/41578 */ type CurriedState< F > = F extends SelectorWithCustomCurrySignature ? F[ 'CurriedSignature' ] : F extends ( state: any, ...args: infer P ) => infer R ? ( ...args: P ) => R : F; /** * Utility to manually specify curried selector signatures. * * It comes handy when TypeScript can't automatically produce the * correct curried function signature. For example: * * ```ts * type BadlyInferredSignature = CurriedState< * ( * state: any, * kind: K, * key: K extends string ? 'one value' : false * ) => K * > * // BadlyInferredSignature evaluates to: * // (kind: string number, key: false "one value") => string number * ``` * * With SelectorWithCustomCurrySignature, we can provide a custom * signature and avoid relying on TypeScript inference: * ```ts * interface MySelectorSignature extends SelectorWithCustomCurrySignature { * ( * state: any, * kind: K, * key: K extends string ? 'one value' : false * ): K; * * CurriedSignature: ( * kind: K, * key: K extends string ? 'one value' : false * ): K; * } * type CorrectlyInferredSignature = CurriedState * // (kind: K, key: K extends string ? 'one value' : false): K; * * For even more context, see https://github.com/WordPress/gutenberg/pull/41578 * ``` */ export interface SelectorWithCustomCurrySignature { CurriedSignature: Function; PromiseCurriedSignature?: Function; } /** * A store name or store descriptor, used throughout the API. */ export type StoreNameOrDescriptor = string | StoreDescriptor; /** * An isolated orchestrator of store registrations. * * Returned by `createRegistry`. Provides methods to register stores, * select data, dispatch actions, and subscribe to changes. */ export interface DataRegistry { batch: ( callback: () => void ) => void; stores: Record< string, InternalStoreInstance >; namespaces: Record< string, InternalStoreInstance >; subscribe: ( listener: ListenerFunction, storeNameOrDescriptor?: StoreNameOrDescriptor ) => () => void; select: { < S extends StoreDescriptor< any > >( store: S ): CurriedSelectorsOf< S >; ( store: StoreNameOrDescriptor ): Record< string, ( ...args: any[] ) => any >; }; resolveSelect: { < S extends StoreDescriptor< any > >( store: S ): CurriedSelectorsResolveOf< S >; ( store: StoreNameOrDescriptor ): Record< string, ( ...args: any[] ) => Promise< any > >; }; suspendSelect: { < S extends StoreDescriptor< any > >( store: S ): CurriedSelectorsOf< S >; ( store: StoreNameOrDescriptor ): Record< string, ( ...args: any[] ) => any >; }; dispatch: { < S extends StoreDescriptor< any > >( store: S ): ActionCreatorsOf< S >; ( store: StoreNameOrDescriptor ): Record< string, ( ...args: any[] ) => any >; }; use: ( plugin: DataPlugin, options?: Record< string, unknown > ) => DataRegistry; register: ( store: StoreDescriptor< any > ) => void; registerGenericStore: ( name: string, store: StoreInstance< AnyConfig > ) => void; registerStore: ( storeName: string, options: ReduxStoreConfig< any, any, any > ) => ReduxStore; __unstableMarkListeningStores: < T >( callback: () => T, ref: { current: string[] | null } ) => T; } /** * The plugin function signature. */ export type DataPlugin = ( registry: DataRegistry, options?: Record< string, unknown > ) => Partial< DataRegistry >; /** * Status of a selector resolution. */ export type ResolutionStatus = 'resolving' | 'finished' | 'error'; /** * State value for a single resolution. */ export type ResolutionState = | { status: 'resolving' } | { status: 'finished' } | { status: 'error'; error: Error | unknown }; /** * A normalized resolver with a `fulfill` method and optional `isFulfilled`. */ export interface NormalizedResolver { /** * The function to call to fulfill the resolver. */ fulfill: ( ...args: any[] ) => any; /** * Optional function to check if the resolver is already fulfilled. */ isFulfilled?: ( state: any, ...args: any[] ) => boolean; /** * Optional function to check if the resolver should be invalidated. */ shouldInvalidate?: ( action: any, ...args: any[] ) => boolean; } /** * A bound selector with optional resolver metadata. */ export interface BoundSelector { ( ...args: any[] ): any; /** * Whether this selector has a resolver attached. */ hasResolver: boolean; /** * Optional function to normalize the arguments. */ __unstableNormalizeArgs?: ( args: any[] ) => any[]; /** * Whether this selector is a registry selector. */ isRegistrySelector?: boolean; /** * The registry instance this selector is bound to. */ registry?: DataRegistry; } /** * The shape of a store instance as seen internally by the registry. * Extends the public StoreInstance with additional internal properties. */ export interface InternalStoreInstance< Config extends AnyConfig = AnyConfig > extends StoreInstance< Config > { /** * The Redux store instance (only for Redux-based stores). */ store?: ReduxStore; /** * The internal emitter for pause/resume batching. */ emitter: DataEmitter; /** * The combined reducer. */ reducer?: ( state: any, action: any ) => any; /** * Bound actions object. */ actions?: Record< string, ActionCreator >; /** * Bound selectors object. */ selectors?: Record< string, Selector >; /** * Resolver definitions. */ resolvers?: Record< string, NormalizedResolver >; /** * Returns resolve-wrapped selectors. */ getResolveSelectors?: () => Record< string, ( ...args: any[] ) => Promise< any > >; /** * Returns suspense-wrapped selectors. */ getSuspendSelectors?: () => Record< string, ( ...args: any[] ) => any >; } /** * Control descriptor for the controls system. */ export interface ControlDescriptor { /** * The type of the control action. */ type: string; /** * The store key to target. */ storeKey: string; /** * The name of the selector (for select/resolveSelect controls). */ selectorName?: string; /** * The name of the action (for dispatch controls). */ actionName?: string; /** * Arguments for the selector or action. */ args: any[]; } /** * Storage interface (Web Storage API subset). */ export interface StorageInterface { getItem: ( key: string ) => string | null; setItem: ( key: string, value: string ) => void; removeItem?: ( key: string ) => void; clear?: VoidFunction; } // Type Helpers. export type ConfigOf< S > = S extends StoreDescriptor< infer C > ? C : never; export type ActionCreatorsOf< T > = T extends StoreDescriptor< ReduxStoreConfig< any, infer ActionCreators, any > > ? PromisifiedActionCreators< ActionCreators > & MetadataActions< T > : T extends ReduxStoreConfig< any, infer ActionCreators, any > ? PromisifiedActionCreators< ActionCreators > : never; // Takes an object containing all action creators for a store and updates the // return type of each action creator to account for internal registry details -- // for example, dispatched actions are wrapped with a Promise. export type PromisifiedActionCreators< ActionCreators > = { [ Action in keyof ActionCreators ]: ActionCreators[ Action ] extends ActionCreator ? PromisifyActionCreator< ActionCreators[ Action ] > : ActionCreators[ Action ]; }; // Wraps action creator return types with a Promise and handles thunks and generators. export type PromisifyActionCreator< Action extends ActionCreator > = ( ...args: Parameters< Action > ) => Promise< ReturnType< Action > extends ( ..._args: any[] ) => any ? ThunkReturnType< Action > : ReturnType< Action > extends Generator< any, infer TReturn, any > ? TReturn : ReturnType< Action > >; // A thunk is an action creator which returns a function, which can optionally // return a Promise. The double ReturnType unwraps the innermost function's // return type, and Awaited gets the type the Promise resolves to. If the return // type is not a Promise, Awaited returns that original type. export type ThunkReturnType< Action extends ActionCreator > = Awaited< ReturnType< ReturnType< Action > > >; type SelectorsOf< Config extends AnyConfig > = Config extends ReduxStoreConfig< any, any, infer Selectors > ? { [ name in keyof Selectors ]: Function } : never; /** * The argument object passed to every thunk function. When parameterized with * a store descriptor, `dispatch`, `select`, and `resolveSelect` are fully * typed against that store's actions and selectors. * * @example * ```ts * const myAction = * ( id: number ) => * async ( { dispatch, select }: ThunkArgs< typeof myStore > ) => { * const record = select.getRecord( id ); * dispatch.setLoading( true ); * }; * ``` */ export interface ThunkArgs< S extends StoreDescriptor = StoreDescriptor, PrivateSelectors extends Record< string, Function > = {}, PrivateActions extends Record< string, ActionCreator > = {}, > { dispatch: ActionCreatorsOf< S > & PromisifiedActionCreators< PrivateActions > & { < R >( thunk: ( ...args: any[] ) => R ): R; ( action: Record< string, unknown > ): unknown; }; select: CurriedSelectorsOf< S > & { [ key in keyof PrivateSelectors ]: CurriedState< PrivateSelectors[ key ] >; }; resolveSelect: CurriedSelectorsResolveOf< S >; registry: DataRegistry; } export type combineReducers = typeof reduxCombineReducers;