@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
1,043 lines (1,031 loc) • 143 kB
text/typescript
import * as _reduxjs_toolkit from '@reduxjs/toolkit';
import { ThunkDispatch, Draft, AsyncThunk, SHOULD_AUTOBATCH, ThunkAction, UnknownAction, SafePromise, SerializedError, PayloadAction, ActionCreatorWithoutPayload, Reducer, Middleware, ActionCreatorWithPayload } from '@reduxjs/toolkit';
import { Patch } from 'immer';
import * as redux from 'redux';
import { CreateSelectorFunction } from 'reselect';
import { StandardSchemaV1 } from '@standard-schema/spec';
import { SchemaError } from '@standard-schema/utils';
type Id<T> = {
[K in keyof T]: T[K];
} & {};
type WithRequiredProp<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
type Override<T1, T2> = T2 extends any ? Omit<T1, keyof T2> & T2 : never;
/**
* Convert a Union type `(A|B)` to an intersection type `(A&B)`
*/
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (k: infer I) => void ? I : never;
type NonOptionalKeys<T> = {
[K in keyof T]-?: undefined extends T[K] ? never : K;
}[keyof T];
type HasRequiredProps<T, True, False> = NonOptionalKeys<T> extends never ? False : True;
type NonUndefined<T> = T extends undefined ? never : T;
type UnwrapPromise<T> = T extends PromiseLike<infer V> ? V : T;
type MaybePromise<T> = T | PromiseLike<T>;
type OmitFromUnion<T, K extends keyof T> = T extends any ? Omit<T, K> : never;
type IsAny<T, True, False = never> = true | false extends (T extends never ? true : false) ? True : False;
type CastAny<T, CastTo> = IsAny<T, CastTo, T>;
interface BaseQueryApi {
signal: AbortSignal;
abort: (reason?: string) => void;
dispatch: ThunkDispatch<any, any, any>;
getState: () => unknown;
extra: unknown;
endpoint: string;
type: 'query' | 'mutation';
/**
* Only available for queries: indicates if a query has been forced,
* i.e. it would have been fetched even if there would already be a cache entry
* (this does not mean that there is already a cache entry though!)
*
* This can be used to for example add a `Cache-Control: no-cache` header for
* invalidated queries.
*/
forced?: boolean;
/**
* Only available for queries: the cache key that was used to store the query result
*/
queryCacheKey?: string;
}
type QueryReturnValue<T = unknown, E = unknown, M = unknown> = {
error: E;
data?: undefined;
meta?: M;
} | {
error?: undefined;
data: T;
meta?: M;
};
type BaseQueryFn<Args = any, Result = unknown, Error = unknown, DefinitionExtraOptions = {}, Meta = {}> = (args: Args, api: BaseQueryApi, extraOptions: DefinitionExtraOptions) => MaybePromise<QueryReturnValue<Result, Error, Meta>>;
type BaseQueryEnhancer<AdditionalArgs = unknown, AdditionalDefinitionExtraOptions = unknown, Config = void> = <BaseQuery extends BaseQueryFn>(baseQuery: BaseQuery, config: Config) => BaseQueryFn<BaseQueryArg<BaseQuery> & AdditionalArgs, BaseQueryResult<BaseQuery>, BaseQueryError<BaseQuery>, BaseQueryExtraOptions<BaseQuery> & AdditionalDefinitionExtraOptions, NonNullable<BaseQueryMeta<BaseQuery>>>;
/**
* @public
*/
type BaseQueryResult<BaseQuery extends BaseQueryFn> = UnwrapPromise<ReturnType<BaseQuery>> extends infer Unwrapped ? Unwrapped extends {
data: any;
} ? Unwrapped['data'] : never : never;
/**
* @public
*/
type BaseQueryMeta<BaseQuery extends BaseQueryFn> = UnwrapPromise<ReturnType<BaseQuery>>['meta'];
/**
* @public
*/
type BaseQueryError<BaseQuery extends BaseQueryFn> = Exclude<UnwrapPromise<ReturnType<BaseQuery>>, {
error?: undefined;
}>['error'];
/**
* @public
*/
type BaseQueryArg<T extends (arg: any, ...args: any[]) => any> = T extends (arg: infer A, ...args: any[]) => any ? A : any;
/**
* @public
*/
type BaseQueryExtraOptions<BaseQuery extends BaseQueryFn> = Parameters<BaseQuery>[2];
declare const defaultSerializeQueryArgs: SerializeQueryArgs<any>;
type SerializeQueryArgs<QueryArgs, ReturnType = string> = (_: {
queryArgs: QueryArgs;
endpointDefinition: EndpointDefinition<any, any, any, any>;
endpointName: string;
}) => ReturnType;
type InternalSerializeQueryArgs = (_: {
queryArgs: any;
endpointDefinition: EndpointDefinition<any, any, any, any>;
endpointName: string;
}) => QueryCacheKey;
type BuildThunksApiEndpointQuery<Definition extends QueryDefinition<any, any, any, any, any>> = Matchers<QueryThunk, Definition>;
type BuildThunksApiEndpointInfiniteQuery<Definition extends InfiniteQueryDefinition<any, any, any, any, any>> = Matchers<InfiniteQueryThunk<any>, Definition>;
type BuildThunksApiEndpointMutation<Definition extends MutationDefinition<any, any, any, any, any>> = Matchers<MutationThunk, Definition>;
type EndpointThunk<Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>, Definition extends EndpointDefinition<any, any, any, any>> = Definition extends EndpointDefinition<infer QueryArg, infer BaseQueryFn, any, infer ResultType> ? Thunk extends AsyncThunk<unknown, infer ATArg, infer ATConfig> ? AsyncThunk<ResultType, ATArg & {
originalArgs: QueryArg;
}, ATConfig & {
rejectValue: BaseQueryError<BaseQueryFn>;
}> : never : Definition extends InfiniteQueryDefinition<infer QueryArg, infer PageParam, infer BaseQueryFn, any, infer ResultType> ? Thunk extends AsyncThunk<unknown, infer ATArg, infer ATConfig> ? AsyncThunk<InfiniteData<ResultType, PageParam>, ATArg & {
originalArgs: QueryArg;
}, ATConfig & {
rejectValue: BaseQueryError<BaseQueryFn>;
}> : never : never;
type PendingAction<Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>, Definition extends EndpointDefinition<any, any, any, any>> = ReturnType<EndpointThunk<Thunk, Definition>['pending']>;
type FulfilledAction<Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>, Definition extends EndpointDefinition<any, any, any, any>> = ReturnType<EndpointThunk<Thunk, Definition>['fulfilled']>;
type RejectedAction<Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>, Definition extends EndpointDefinition<any, any, any, any>> = ReturnType<EndpointThunk<Thunk, Definition>['rejected']>;
type Matcher<M> = (value: any) => value is M;
interface Matchers<Thunk extends QueryThunk | MutationThunk | InfiniteQueryThunk<any>, Definition extends EndpointDefinition<any, any, any, any>> {
matchPending: Matcher<PendingAction<Thunk, Definition>>;
matchFulfilled: Matcher<FulfilledAction<Thunk, Definition>>;
matchRejected: Matcher<RejectedAction<Thunk, Definition>>;
}
type QueryThunkArg = QuerySubstateIdentifier & StartQueryActionCreatorOptions & {
type: 'query';
originalArgs: unknown;
endpointName: string;
};
type InfiniteQueryThunkArg<D extends InfiniteQueryDefinition<any, any, any, any, any>> = QuerySubstateIdentifier & StartInfiniteQueryActionCreatorOptions<D> & {
type: `query`;
originalArgs: unknown;
endpointName: string;
param: unknown;
direction?: InfiniteQueryDirection;
refetchCachedPages?: boolean;
};
type MutationThunkArg = {
type: 'mutation';
originalArgs: unknown;
endpointName: string;
track?: boolean;
fixedCacheKey?: string;
};
type ThunkResult = unknown;
type ThunkApiMetaConfig = {
pendingMeta: {
startedTimeStamp: number;
[SHOULD_AUTOBATCH]: true;
};
fulfilledMeta: {
fulfilledTimeStamp: number;
baseQueryMeta: unknown;
[SHOULD_AUTOBATCH]: true;
};
rejectedMeta: {
baseQueryMeta: unknown;
[SHOULD_AUTOBATCH]: true;
};
};
type QueryThunk = AsyncThunk<ThunkResult, QueryThunkArg, ThunkApiMetaConfig>;
type InfiniteQueryThunk<D extends InfiniteQueryDefinition<any, any, any, any, any>> = AsyncThunk<ThunkResult, InfiniteQueryThunkArg<D>, ThunkApiMetaConfig>;
type MutationThunk = AsyncThunk<ThunkResult, MutationThunkArg, ThunkApiMetaConfig>;
type MaybeDrafted<T> = T | Draft<T>;
type Recipe<T> = (data: MaybeDrafted<T>) => void | MaybeDrafted<T>;
type PatchQueryDataThunk<Definitions extends EndpointDefinitions, PartialState> = <EndpointName extends QueryKeys<Definitions>>(endpointName: EndpointName, arg: QueryArgFrom<Definitions[EndpointName]>, patches: readonly Patch[], updateProvided?: boolean) => ThunkAction<void, PartialState, any, UnknownAction>;
type AllQueryKeys<Definitions extends EndpointDefinitions> = QueryKeys<Definitions> | InfiniteQueryKeys<Definitions>;
type QueryArgFromAnyQueryDefinition<Definitions extends EndpointDefinitions, EndpointName extends AllQueryKeys<Definitions>> = Definitions[EndpointName] extends InfiniteQueryDefinition<any, any, any, any, any> ? InfiniteQueryArgFrom<Definitions[EndpointName]> : Definitions[EndpointName] extends QueryDefinition<any, any, any, any> ? QueryArgFrom<Definitions[EndpointName]> : never;
type DataFromAnyQueryDefinition<Definitions extends EndpointDefinitions, EndpointName extends AllQueryKeys<Definitions>> = Definitions[EndpointName] extends InfiniteQueryDefinition<any, any, any, any, any> ? InfiniteData<ResultTypeFrom<Definitions[EndpointName]>, PageParamFrom<Definitions[EndpointName]>> : Definitions[EndpointName] extends QueryDefinition<any, any, any, any> ? ResultTypeFrom<Definitions[EndpointName]> : unknown;
type UpsertThunkResult<Definitions extends EndpointDefinitions, EndpointName extends AllQueryKeys<Definitions>> = Definitions[EndpointName] extends InfiniteQueryDefinition<any, any, any, any, any> ? InfiniteQueryActionCreatorResult<Definitions[EndpointName]> : Definitions[EndpointName] extends QueryDefinition<any, any, any, any> ? QueryActionCreatorResult<Definitions[EndpointName]> : QueryActionCreatorResult<never>;
type UpdateQueryDataThunk<Definitions extends EndpointDefinitions, PartialState> = <EndpointName extends AllQueryKeys<Definitions>>(endpointName: EndpointName, arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>, updateRecipe: Recipe<DataFromAnyQueryDefinition<Definitions, EndpointName>>, updateProvided?: boolean) => ThunkAction<PatchCollection, PartialState, any, UnknownAction>;
type UpsertQueryDataThunk<Definitions extends EndpointDefinitions, PartialState> = <EndpointName extends AllQueryKeys<Definitions>>(endpointName: EndpointName, arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>, value: DataFromAnyQueryDefinition<Definitions, EndpointName>) => ThunkAction<UpsertThunkResult<Definitions, EndpointName>, PartialState, any, UnknownAction>;
/**
* An object returned from dispatching a `api.util.updateQueryData` call.
*/
type PatchCollection = {
/**
* An `immer` Patch describing the cache update.
*/
patches: Patch[];
/**
* An `immer` Patch to revert the cache update.
*/
inversePatches: Patch[];
/**
* A function that will undo the cache update.
*/
undo: () => void;
};
type SkipToken = typeof skipToken;
/**
* Can be passed into `useQuery`, `useQueryState` or `useQuerySubscription`
* instead of the query argument to get the same effect as if setting
* `skip: true` in the query options.
*
* Useful for scenarios where a query should be skipped when `arg` is `undefined`
* and TypeScript complains about it because `arg` is not allowed to be passed
* in as `undefined`, such as
*
* ```ts
* // codeblock-meta title="will error if the query argument is not allowed to be undefined" no-transpile
* useSomeQuery(arg, { skip: !!arg })
* ```
*
* ```ts
* // codeblock-meta title="using skipToken instead" no-transpile
* useSomeQuery(arg ?? skipToken)
* ```
*
* If passed directly into a query or mutation selector, that selector will always
* return an uninitialized state.
*/
export declare const skipToken: unique symbol;
type BuildSelectorsApiEndpointQuery<Definition extends QueryDefinition<any, any, any, any, any>, Definitions extends EndpointDefinitions> = {
select: QueryResultSelectorFactory<Definition, RootState<Definitions, TagTypesFrom<Definition>, ReducerPathFrom<Definition>>>;
};
type BuildSelectorsApiEndpointInfiniteQuery<Definition extends InfiniteQueryDefinition<any, any, any, any, any>, Definitions extends EndpointDefinitions> = {
select: InfiniteQueryResultSelectorFactory<Definition, RootState<Definitions, TagTypesFrom<Definition>, ReducerPathFrom<Definition>>>;
};
type BuildSelectorsApiEndpointMutation<Definition extends MutationDefinition<any, any, any, any, any>, Definitions extends EndpointDefinitions> = {
select: MutationResultSelectorFactory<Definition, RootState<Definitions, TagTypesFrom<Definition>, ReducerPathFrom<Definition>>>;
};
type QueryResultSelectorFactory<Definition extends QueryDefinition<any, any, any, any>, RootState> = (queryArg: QueryArgFrom<Definition> | SkipToken) => (state: RootState) => QueryResultSelectorResult<Definition>;
type QueryResultSelectorResult<Definition extends QueryDefinition<any, any, any, any>> = QuerySubState<Definition> & RequestStatusFlags;
type InfiniteQueryResultSelectorFactory<Definition extends InfiniteQueryDefinition<any, any, any, any, any>, RootState> = (queryArg: InfiniteQueryArgFrom<Definition> | SkipToken) => (state: RootState) => InfiniteQueryResultSelectorResult<Definition>;
type InfiniteQueryResultFlags = {
hasNextPage: boolean;
hasPreviousPage: boolean;
isFetchingNextPage: boolean;
isFetchingPreviousPage: boolean;
isFetchNextPageError: boolean;
isFetchPreviousPageError: boolean;
};
type InfiniteQueryResultSelectorResult<Definition extends InfiniteQueryDefinition<any, any, any, any, any>> = InfiniteQuerySubState<Definition> & RequestStatusFlags & InfiniteQueryResultFlags;
type MutationResultSelectorFactory<Definition extends MutationDefinition<any, any, any, any>, RootState> = (requestId: string | {
requestId: string | undefined;
fixedCacheKey: string | undefined;
} | SkipToken) => (state: RootState) => MutationResultSelectorResult<Definition>;
type MutationResultSelectorResult<Definition extends MutationDefinition<any, any, any, any>> = MutationSubState<Definition> & RequestStatusFlags;
type BuildInitiateApiEndpointQuery<Definition extends QueryDefinition<any, any, any, any, any>> = {
initiate: StartQueryActionCreator<Definition>;
};
type BuildInitiateApiEndpointInfiniteQuery<Definition extends InfiniteQueryDefinition<any, any, any, any, any>> = {
initiate: StartInfiniteQueryActionCreator<Definition>;
};
type BuildInitiateApiEndpointMutation<Definition extends MutationDefinition<any, any, any, any, any>> = {
initiate: StartMutationActionCreator<Definition>;
};
declare const forceQueryFnSymbol: unique symbol;
type StartQueryActionCreatorOptions = {
subscribe?: boolean;
forceRefetch?: boolean | number;
subscriptionOptions?: SubscriptionOptions;
[forceQueryFnSymbol]?: () => QueryReturnValue;
};
type StartInfiniteQueryActionCreatorOptions<D extends InfiniteQueryDefinition<any, any, any, any, any>> = StartQueryActionCreatorOptions & {
direction?: InfiniteQueryDirection;
param?: unknown;
} & Partial<Pick<Partial<InfiniteQueryConfigOptions<ResultTypeFrom<D>, PageParamFrom<D>, InfiniteQueryArgFrom<D>>>, 'initialPageParam' | 'refetchCachedPages'>>;
type StartQueryActionCreator<D extends QueryDefinition<any, any, any, any, any>> = (arg: QueryArgFrom<D>, options?: StartQueryActionCreatorOptions) => ThunkAction<QueryActionCreatorResult<D>, any, any, UnknownAction>;
type StartInfiniteQueryActionCreator<D extends InfiniteQueryDefinition<any, any, any, any, any>> = (arg: InfiniteQueryArgFrom<D>, options?: StartInfiniteQueryActionCreatorOptions<D>) => ThunkAction<InfiniteQueryActionCreatorResult<D>, any, any, UnknownAction>;
type QueryActionCreatorFields = {
requestId: string;
subscriptionOptions: SubscriptionOptions | undefined;
abort(): void;
unsubscribe(): void;
updateSubscriptionOptions(options: SubscriptionOptions): void;
queryCacheKey: string;
};
type QueryActionCreatorResult<D extends QueryDefinition<any, any, any, any>> = SafePromise<QueryResultSelectorResult<D>> & QueryActionCreatorFields & {
arg: QueryArgFrom<D>;
unwrap(): Promise<ResultTypeFrom<D>>;
refetch(): QueryActionCreatorResult<D>;
};
type InfiniteQueryActionCreatorResult<D extends InfiniteQueryDefinition<any, any, any, any, any>> = SafePromise<InfiniteQueryResultSelectorResult<D>> & QueryActionCreatorFields & {
arg: InfiniteQueryArgFrom<D>;
unwrap(): Promise<InfiniteData<ResultTypeFrom<D>, PageParamFrom<D>>>;
refetch(options?: Pick<StartInfiniteQueryActionCreatorOptions<D>, 'refetchCachedPages'>): InfiniteQueryActionCreatorResult<D>;
};
type StartMutationActionCreator<D extends MutationDefinition<any, any, any, any>> = (arg: QueryArgFrom<D>, options?: {
/**
* If this mutation should be tracked in the store.
* If you just want to manually trigger this mutation using `dispatch` and don't care about the
* result, state & potential errors being held in store, you can set this to false.
* (defaults to `true`)
*/
track?: boolean;
fixedCacheKey?: string;
}) => ThunkAction<MutationActionCreatorResult<D>, any, any, UnknownAction>;
type MutationActionCreatorResult<D extends MutationDefinition<any, any, any, any>> = SafePromise<{
data: ResultTypeFrom<D>;
error?: undefined;
} | {
data?: undefined;
error: Exclude<BaseQueryError<D extends MutationDefinition<any, infer BaseQuery, any, any> ? BaseQuery : never>, undefined> | SerializedError;
}> & {
/** @internal */
arg: {
/**
* The name of the given endpoint for the mutation
*/
endpointName: string;
/**
* The original arguments supplied to the mutation call
*/
originalArgs: QueryArgFrom<D>;
/**
* Whether the mutation is being tracked in the store.
*/
track?: boolean;
fixedCacheKey?: string;
};
/**
* A unique string generated for the request sequence
*/
requestId: string;
/**
* A method to cancel the mutation promise. Note that this is not intended to prevent the mutation
* that was fired off from reaching the server, but only to assist in handling the response.
*
* Calling `abort()` prior to the promise resolving will force it to reach the error state with
* the serialized error:
* `{ name: 'AbortError', message: 'Aborted' }`
*
* @example
* ```ts
* const [updateUser] = useUpdateUserMutation();
*
* useEffect(() => {
* const promise = updateUser(id);
* promise
* .unwrap()
* .catch((err) => {
* if (err.name === 'AbortError') return;
* // else handle the unexpected error
* })
*
* return () => {
* promise.abort();
* }
* }, [id, updateUser])
* ```
*/
abort(): void;
/**
* Unwraps a mutation call to provide the raw response/error.
*
* @remarks
* If you need to access the error or success payload immediately after a mutation, you can chain .unwrap().
*
* @example
* ```ts
* // codeblock-meta title="Using .unwrap"
* addPost({ id: 1, name: 'Example' })
* .unwrap()
* .then((payload) => console.log('fulfilled', payload))
* .catch((error) => console.error('rejected', error));
* ```
*
* @example
* ```ts
* // codeblock-meta title="Using .unwrap with async await"
* try {
* const payload = await addPost({ id: 1, name: 'Example' }).unwrap();
* console.log('fulfilled', payload)
* } catch (error) {
* console.error('rejected', error);
* }
* ```
*/
unwrap(): Promise<ResultTypeFrom<D>>;
/**
* A method to manually unsubscribe from the mutation call, meaning it will be removed from cache after the usual caching grace period.
The value returned by the hook will reset to `isUninitialized` afterwards.
*/
reset(): void;
};
type ReferenceCacheLifecycle = never;
interface QueryBaseLifecycleApi<QueryArg, BaseQuery extends BaseQueryFn, ResultType, ReducerPath extends string = string> extends LifecycleApi<ReducerPath> {
/**
* Gets the current value of this cache entry.
*/
getCacheEntry(): QueryResultSelectorResult<{
type: DefinitionType.query;
} & BaseEndpointDefinition<QueryArg, BaseQuery, ResultType, BaseQueryResult<BaseQuery>>>;
/**
* Updates the current cache entry value.
* For documentation see `api.util.updateQueryData`.
*/
updateCachedData(updateRecipe: Recipe<ResultType>): PatchCollection;
}
type MutationBaseLifecycleApi<QueryArg, BaseQuery extends BaseQueryFn, ResultType, ReducerPath extends string = string> = LifecycleApi<ReducerPath> & {
/**
* Gets the current value of this cache entry.
*/
getCacheEntry(): MutationResultSelectorResult<{
type: DefinitionType.mutation;
} & BaseEndpointDefinition<QueryArg, BaseQuery, ResultType, BaseQueryResult<BaseQuery>>>;
};
type LifecycleApi<ReducerPath extends string = string> = {
/**
* The dispatch method for the store
*/
dispatch: ThunkDispatch<any, any, UnknownAction>;
/**
* A method to get the current state
*/
getState(): RootState<any, any, ReducerPath>;
/**
* `extra` as provided as `thunk.extraArgument` to the `configureStore` `getDefaultMiddleware` option.
*/
extra: unknown;
/**
* A unique ID generated for the mutation
*/
requestId: string;
};
type CacheLifecyclePromises<ResultType = unknown, MetaType = unknown> = {
/**
* Promise that will resolve with the first value for this cache key.
* This allows you to `await` until an actual value is in cache.
*
* If the cache entry is removed from the cache before any value has ever
* been resolved, this Promise will reject with
* `new Error('Promise never resolved before cacheEntryRemoved.')`
* to prevent memory leaks.
* You can just re-throw that error (or not handle it at all) -
* it will be caught outside of `cacheEntryAdded`.
*
* If you don't interact with this promise, it will not throw.
*/
cacheDataLoaded: PromiseWithKnownReason<{
/**
* The (transformed) query result.
*/
data: ResultType;
/**
* The `meta` returned by the `baseQuery`
*/
meta: MetaType;
}, typeof neverResolvedError>;
/**
* Promise that allows you to wait for the point in time when the cache entry
* has been removed from the cache, by not being used/subscribed to any more
* in the application for too long or by dispatching `api.util.resetApiState`.
*/
cacheEntryRemoved: Promise<void>;
};
interface QueryCacheLifecycleApi<QueryArg, BaseQuery extends BaseQueryFn, ResultType, ReducerPath extends string = string> extends QueryBaseLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>, CacheLifecyclePromises<ResultType, BaseQueryMeta<BaseQuery>> {
}
type MutationCacheLifecycleApi<QueryArg, BaseQuery extends BaseQueryFn, ResultType, ReducerPath extends string = string> = MutationBaseLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath> & CacheLifecyclePromises<ResultType, BaseQueryMeta<BaseQuery>>;
type CacheLifecycleQueryExtraOptions<ResultType, QueryArg, BaseQuery extends BaseQueryFn, ReducerPath extends string = string> = {
onCacheEntryAdded?(arg: QueryArg, api: QueryCacheLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>): Promise<void> | void;
};
type CacheLifecycleInfiniteQueryExtraOptions<ResultType, QueryArg, BaseQuery extends BaseQueryFn, ReducerPath extends string = string> = CacheLifecycleQueryExtraOptions<ResultType, QueryArg, BaseQuery, ReducerPath>;
type CacheLifecycleMutationExtraOptions<ResultType, QueryArg, BaseQuery extends BaseQueryFn, ReducerPath extends string = string> = {
onCacheEntryAdded?(arg: QueryArg, api: MutationCacheLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>): Promise<void> | void;
};
declare const neverResolvedError: Error & {
message: "Promise never resolved before cacheEntryRemoved.";
};
type ReferenceQueryLifecycle = never;
type QueryLifecyclePromises<ResultType, BaseQuery extends BaseQueryFn> = {
/**
* Promise that will resolve with the (transformed) query result.
*
* If the query fails, this promise will reject with the error.
*
* This allows you to `await` for the query to finish.
*
* If you don't interact with this promise, it will not throw.
*/
queryFulfilled: PromiseWithKnownReason<{
/**
* The (transformed) query result.
*/
data: ResultType;
/**
* The `meta` returned by the `baseQuery`
*/
meta: BaseQueryMeta<BaseQuery>;
}, QueryFulfilledRejectionReason<BaseQuery>>;
};
type QueryFulfilledRejectionReason<BaseQuery extends BaseQueryFn> = {
error: BaseQueryError<BaseQuery>;
/**
* If this is `false`, that means this error was returned from the `baseQuery` or `queryFn` in a controlled manner.
*/
isUnhandledError: false;
/**
* The `meta` returned by the `baseQuery`
*/
meta: BaseQueryMeta<BaseQuery>;
} | {
error: unknown;
meta?: undefined;
/**
* If this is `true`, that means that this error is the result of `baseQueryFn`, `queryFn`, `transformResponse` or `transformErrorResponse` throwing an error instead of handling it properly.
* There can not be made any assumption about the shape of `error`.
*/
isUnhandledError: true;
};
type QueryLifecycleQueryExtraOptions<ResultType, QueryArg, BaseQuery extends BaseQueryFn, ReducerPath extends string = string> = {
/**
* A function that is called when the individual query is started. The function is called with a lifecycle api object containing properties such as `queryFulfilled`, allowing code to be run when a query is started, when it succeeds, and when it fails (i.e. throughout the lifecycle of an individual query/mutation call).
*
* Can be used to perform side-effects throughout the lifecycle of the query.
*
* @example
* ```ts
* import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
* import { messageCreated } from './notificationsSlice';
*
* export interface Post {
* id: number;
* name: string;
* }
*
* const api = createApi({
* baseQuery: fetchBaseQuery({
* baseUrl: '/',
* }),
* endpoints: (build) => ({
* getPost: build.query<Post, number>({
* query: (id) => `post/${id}`,
* async onQueryStarted(id, { dispatch, queryFulfilled }) {
* // `onStart` side-effect
* dispatch(messageCreated('Fetching posts...'));
* try {
* const { data } = await queryFulfilled;
* // `onSuccess` side-effect
* dispatch(messageCreated('Posts received!'));
* } catch (err) {
* // `onError` side-effect
* dispatch(messageCreated('Error fetching posts!'));
* }
* },
* }),
* }),
* });
* ```
*/
onQueryStarted?(queryArgument: QueryArg, queryLifeCycleApi: QueryLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>): Promise<void> | void;
};
type QueryLifecycleInfiniteQueryExtraOptions<ResultType, QueryArg, BaseQuery extends BaseQueryFn, ReducerPath extends string = string> = QueryLifecycleQueryExtraOptions<ResultType, QueryArg, BaseQuery, ReducerPath>;
type QueryLifecycleMutationExtraOptions<ResultType, QueryArg, BaseQuery extends BaseQueryFn, ReducerPath extends string = string> = {
/**
* A function that is called when the individual mutation is started. The function is called with a lifecycle api object containing properties such as `queryFulfilled`, allowing code to be run when a query is started, when it succeeds, and when it fails (i.e. throughout the lifecycle of an individual query/mutation call).
*
* Can be used for `optimistic updates`.
*
* @example
*
* ```ts
* import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
* export interface Post {
* id: number
* name: string
* }
*
* const api = createApi({
* baseQuery: fetchBaseQuery({
* baseUrl: '/',
* }),
* tagTypes: ['Post'],
* endpoints: (build) => ({
* getPost: build.query<Post, number>({
* query: (id) => `post/${id}`,
* providesTags: ['Post'],
* }),
* updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({
* query: ({ id, ...patch }) => ({
* url: `post/${id}`,
* method: 'PATCH',
* body: patch,
* }),
* invalidatesTags: ['Post'],
* async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
* const patchResult = dispatch(
* api.util.updateQueryData('getPost', id, (draft) => {
* Object.assign(draft, patch)
* })
* )
* try {
* await queryFulfilled
* } catch {
* patchResult.undo()
* }
* },
* }),
* }),
* })
* ```
*/
onQueryStarted?(queryArgument: QueryArg, mutationLifeCycleApi: MutationLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>): Promise<void> | void;
};
interface QueryLifecycleApi<QueryArg, BaseQuery extends BaseQueryFn, ResultType, ReducerPath extends string = string> extends QueryBaseLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath>, QueryLifecyclePromises<ResultType, BaseQuery> {
}
type MutationLifecycleApi<QueryArg, BaseQuery extends BaseQueryFn, ResultType, ReducerPath extends string = string> = MutationBaseLifecycleApi<QueryArg, BaseQuery, ResultType, ReducerPath> & QueryLifecyclePromises<ResultType, BaseQuery>;
/**
* Provides a way to define a strongly-typed version of
* {@linkcode QueryLifecycleQueryExtraOptions.onQueryStarted | onQueryStarted}
* for a specific query.
*
* @example
* <caption>#### __Create and reuse a strongly-typed `onQueryStarted` function__</caption>
*
* ```ts
* import type { TypedQueryOnQueryStarted } from '@reduxjs/toolkit/query'
* import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
*
* type Post = {
* id: number
* title: string
* userId: number
* }
*
* type PostsApiResponse = {
* posts: Post[]
* total: number
* skip: number
* limit: number
* }
*
* type QueryArgument = number | undefined
*
* type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
*
* const baseApiSlice = createApi({
* baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
* reducerPath: 'postsApi',
* tagTypes: ['Posts'],
* endpoints: (build) => ({
* getPosts: build.query<PostsApiResponse, void>({
* query: () => `/posts`,
* }),
*
* getPostById: build.query<Post, QueryArgument>({
* query: (postId) => `/posts/${postId}`,
* }),
* }),
* })
*
* const updatePostOnFulfilled: TypedQueryOnQueryStarted<
* PostsApiResponse,
* QueryArgument,
* BaseQueryFunction,
* 'postsApi'
* > = async (queryArgument, { dispatch, queryFulfilled }) => {
* const result = await queryFulfilled
*
* const { posts } = result.data
*
* // Pre-fill the individual post entries with the results
* // from the list endpoint query
* dispatch(
* baseApiSlice.util.upsertQueryEntries(
* posts.map((post) => ({
* endpointName: 'getPostById',
* arg: post.id,
* value: post,
* })),
* ),
* )
* }
*
* export const extendedApiSlice = baseApiSlice.injectEndpoints({
* endpoints: (build) => ({
* getPostsByUserId: build.query<PostsApiResponse, QueryArgument>({
* query: (userId) => `/posts/user/${userId}`,
*
* onQueryStarted: updatePostOnFulfilled,
* }),
* }),
* })
* ```
*
* @template ResultType - The type of the result `data` returned by the query.
* @template QueryArgumentType - The type of the argument passed into the query.
* @template BaseQueryFunctionType - The type of the base query function being used.
* @template ReducerPath - The type representing the `reducerPath` for the API slice.
*
* @since 2.4.0
* @public
*/
type TypedQueryOnQueryStarted<ResultType, QueryArgumentType, BaseQueryFunctionType extends BaseQueryFn, ReducerPath extends string = string> = QueryLifecycleQueryExtraOptions<ResultType, QueryArgumentType, BaseQueryFunctionType, ReducerPath>['onQueryStarted'];
/**
* Provides a way to define a strongly-typed version of
* {@linkcode QueryLifecycleMutationExtraOptions.onQueryStarted | onQueryStarted}
* for a specific mutation.
*
* @example
* <caption>#### __Create and reuse a strongly-typed `onQueryStarted` function__</caption>
*
* ```ts
* import type { TypedMutationOnQueryStarted } from '@reduxjs/toolkit/query'
* import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
*
* type Post = {
* id: number
* title: string
* userId: number
* }
*
* type PostsApiResponse = {
* posts: Post[]
* total: number
* skip: number
* limit: number
* }
*
* type QueryArgument = Pick<Post, 'id'> & Partial<Post>
*
* type BaseQueryFunction = ReturnType<typeof fetchBaseQuery>
*
* const baseApiSlice = createApi({
* baseQuery: fetchBaseQuery({ baseUrl: 'https://dummyjson.com' }),
* reducerPath: 'postsApi',
* tagTypes: ['Posts'],
* endpoints: (build) => ({
* getPosts: build.query<PostsApiResponse, void>({
* query: () => `/posts`,
* }),
*
* getPostById: build.query<Post, number>({
* query: (postId) => `/posts/${postId}`,
* }),
* }),
* })
*
* const updatePostOnFulfilled: TypedMutationOnQueryStarted<
* Post,
* QueryArgument,
* BaseQueryFunction,
* 'postsApi'
* > = async ({ id, ...patch }, { dispatch, queryFulfilled }) => {
* const patchCollection = dispatch(
* baseApiSlice.util.updateQueryData('getPostById', id, (draftPost) => {
* Object.assign(draftPost, patch)
* }),
* )
*
* try {
* await queryFulfilled
* } catch {
* patchCollection.undo()
* }
* }
*
* export const extendedApiSlice = baseApiSlice.injectEndpoints({
* endpoints: (build) => ({
* addPost: build.mutation<Post, Omit<QueryArgument, 'id'>>({
* query: (body) => ({
* url: `posts/add`,
* method: 'POST',
* body,
* }),
*
* onQueryStarted: updatePostOnFulfilled,
* }),
*
* updatePost: build.mutation<Post, QueryArgument>({
* query: ({ id, ...patch }) => ({
* url: `post/${id}`,
* method: 'PATCH',
* body: patch,
* }),
*
* onQueryStarted: updatePostOnFulfilled,
* }),
* }),
* })
* ```
*
* @template ResultType - The type of the result `data` returned by the query.
* @template QueryArgumentType - The type of the argument passed into the query.
* @template BaseQueryFunctionType - The type of the base query function being used.
* @template ReducerPath - The type representing the `reducerPath` for the API slice.
*
* @since 2.4.0
* @public
*/
type TypedMutationOnQueryStarted<ResultType, QueryArgumentType, BaseQueryFunctionType extends BaseQueryFn, ReducerPath extends string = string> = QueryLifecycleMutationExtraOptions<ResultType, QueryArgumentType, BaseQueryFunctionType, ReducerPath>['onQueryStarted'];
/**
* A typesafe single entry to be upserted into the cache
*/
type NormalizedQueryUpsertEntry<Definitions extends EndpointDefinitions, EndpointName extends AllQueryKeys<Definitions>> = {
endpointName: EndpointName;
arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>;
value: DataFromAnyQueryDefinition<Definitions, EndpointName>;
};
/**
* The internal version that is not typesafe since we can't carry the generics through `createSlice`
*/
type NormalizedQueryUpsertEntryPayload = {
endpointName: string;
arg: unknown;
value: unknown;
};
type ProcessedQueryUpsertEntry = {
queryDescription: QueryThunkArg;
value: unknown;
};
/**
* A typesafe representation of a util action creator that accepts cache entry descriptions to upsert
*/
type UpsertEntries<Definitions extends EndpointDefinitions> = (<EndpointNames extends Array<AllQueryKeys<Definitions>>>(entries: [
...{
[I in keyof EndpointNames]: NormalizedQueryUpsertEntry<Definitions, EndpointNames[I]>;
}
]) => PayloadAction<NormalizedQueryUpsertEntryPayload[]>) & {
match: (action: unknown) => action is PayloadAction<NormalizedQueryUpsertEntryPayload[]>;
};
declare function buildSlice({ reducerPath, queryThunk, mutationThunk, serializeQueryArgs, context: { endpointDefinitions: definitions, apiUid, extractRehydrationInfo, hasRehydrationInfo, }, assertTagType, config, }: {
reducerPath: string;
queryThunk: QueryThunk;
infiniteQueryThunk: InfiniteQueryThunk<any>;
mutationThunk: MutationThunk;
serializeQueryArgs: InternalSerializeQueryArgs;
context: ApiContext<EndpointDefinitions>;
assertTagType: AssertTagTypes;
config: Omit<ConfigState<string>, 'online' | 'focused' | 'middlewareRegistered'>;
}): {
reducer: redux.Reducer<{
queries: QueryState<any>;
mutations: MutationState<any>;
provided: InvalidationState<string>;
subscriptions: SubscriptionState;
config: ConfigState<string>;
}, redux.UnknownAction, Partial<{
queries: QueryState<any> | undefined;
mutations: MutationState<any> | undefined;
provided: InvalidationState<string> | undefined;
subscriptions: SubscriptionState | undefined;
config: ConfigState<string> | undefined;
}>>;
actions: {
resetApiState: _reduxjs_toolkit.ActionCreatorWithoutPayload<`${string}/resetApiState`>;
updateProvidedBy: _reduxjs_toolkit.ActionCreatorWithPreparedPayload<[payload: {
queryCacheKey: QueryCacheKey;
providedTags: readonly FullTagDescription<string>[];
}[]], {
queryCacheKey: QueryCacheKey;
providedTags: readonly FullTagDescription<string>[];
}[], `${string}/invalidation/updateProvidedBy`, never, unknown>;
removeMutationResult: _reduxjs_toolkit.ActionCreatorWithPreparedPayload<[payload: MutationSubstateIdentifier], MutationSubstateIdentifier, `${string}/mutations/removeMutationResult`, never, unknown>;
subscriptionsUpdated: _reduxjs_toolkit.ActionCreatorWithPreparedPayload<[payload: Patch[]], Patch[], `${string}/internalSubscriptions/subscriptionsUpdated`, never, unknown>;
updateSubscriptionOptions: _reduxjs_toolkit.ActionCreatorWithPayload<{
endpointName: string;
requestId: string;
options: Subscribers[number];
} & QuerySubstateIdentifier, `${string}/subscriptions/updateSubscriptionOptions`>;
unsubscribeQueryResult: _reduxjs_toolkit.ActionCreatorWithPayload<{
requestId: string;
} & QuerySubstateIdentifier, `${string}/subscriptions/unsubscribeQueryResult`>;
internal_getRTKQSubscriptions: _reduxjs_toolkit.ActionCreatorWithoutPayload<`${string}/subscriptions/internal_getRTKQSubscriptions`>;
removeQueryResult: _reduxjs_toolkit.ActionCreatorWithPreparedPayload<[payload: QuerySubstateIdentifier], QuerySubstateIdentifier, `${string}/queries/removeQueryResult`, never, unknown>;
cacheEntriesUpserted: _reduxjs_toolkit.ActionCreatorWithPreparedPayload<[payload: NormalizedQueryUpsertEntryPayload[]], ProcessedQueryUpsertEntry[], `${string}/queries/cacheEntriesUpserted`, never, {
RTK_autoBatch: boolean;
requestId: string;
timestamp: number;
}>;
queryResultPatched: _reduxjs_toolkit.ActionCreatorWithPreparedPayload<[payload: QuerySubstateIdentifier & {
patches: readonly Patch[];
}], QuerySubstateIdentifier & {
patches: readonly Patch[];
}, `${string}/queries/queryResultPatched`, never, unknown>;
middlewareRegistered: _reduxjs_toolkit.ActionCreatorWithPayload<string, `${string}/config/middlewareRegistered`>;
};
};
type SliceActions = ReturnType<typeof buildSlice>['actions'];
declare const onFocus: ActionCreatorWithoutPayload<"__rtkq/focused">;
declare const onFocusLost: ActionCreatorWithoutPayload<"__rtkq/unfocused">;
declare const onOnline: ActionCreatorWithoutPayload<"__rtkq/online">;
declare const onOffline: ActionCreatorWithoutPayload<"__rtkq/offline">;
/**
* A utility used to enable `refetchOnMount` and `refetchOnReconnect` behaviors.
* It requires the dispatch method from your store.
* Calling `setupListeners(store.dispatch)` will configure listeners with the recommended defaults,
* but you have the option of providing a callback for more granular control.
*
* @example
* ```ts
* setupListeners(store.dispatch)
* ```
*
* @param dispatch - The dispatch method from your store
* @param customHandler - An optional callback for more granular control over listener behavior
* @returns Return value of the handler.
* The default handler returns an `unsubscribe` method that can be called to remove the listeners.
*/
declare function setupListeners(dispatch: ThunkDispatch<any, any, any>, customHandler?: (dispatch: ThunkDispatch<any, any, any>, actions: {
onFocus: typeof onFocus;
onFocusLost: typeof onFocusLost;
onOnline: typeof onOnline;
onOffline: typeof onOffline;
}) => () => void): () => void;
/**
* Note: this file should import all other files for type discovery and declaration merging
*/
/**
* `ifOlderThan` - (default: `false` | `number`) - _number is value in seconds_
* - If specified, it will only run the query if the difference between `new Date()` and the last `fulfilledTimeStamp` is greater than the given value
*
* @overloadSummary
* `force`
* - If `force: true`, it will ignore the `ifOlderThan` value if it is set and the query will be run even if it exists in the cache.
*/
type PrefetchOptions = {
ifOlderThan?: false | number;
} | {
force?: boolean;
};
export declare const coreModuleName: unique symbol;
type CoreModule = typeof coreModuleName | ReferenceCacheLifecycle | ReferenceQueryLifecycle | ReferenceCacheCollection;
type ThunkWithReturnValue<T> = ThunkAction<T, any, any, UnknownAction>;
interface ApiModules<BaseQuery extends BaseQueryFn, Definitions extends EndpointDefinitions, ReducerPath extends string, TagTypes extends string> {
[coreModuleName]: {
/**
* This api's reducer should be mounted at `store[api.reducerPath]`.
*
* @example
* ```ts
* configureStore({
* reducer: {
* [api.reducerPath]: api.reducer,
* },
* middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware),
* })
* ```
*/
reducerPath: ReducerPath;
/**
* Internal actions not part of the public API. Note: These are subject to change at any given time.
*/
internalActions: InternalActions;
/**
* A standard redux reducer that enables core functionality. Make sure it's included in your store.
*
* @example
* ```ts
* configureStore({
* reducer: {
* [api.reducerPath]: api.reducer,
* },
* middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware),
* })
* ```
*/
reducer: Reducer<CombinedState<Definitions, TagTypes, ReducerPath>, UnknownAction>;
/**
* This is a standard redux middleware and is responsible for things like polling, garbage collection and a handful of other things. Make sure it's included in your store.
*
* @example
* ```ts
* configureStore({
* reducer: {
* [api.reducerPath]: api.reducer,
* },
* middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(api.middleware),
* })
* ```
*/
middleware: Middleware<{}, RootState<Definitions, string, ReducerPath>, ThunkDispatch<any, any, UnknownAction>>;
/**
* A collection of utility thunks for various situations.
*/
util: {
/**
* A thunk that (if dispatched) will return a specific running query, identified
* by `endpointName` and `arg`.
* If that query is not running, dispatching the thunk will result in `undefined`.
*
* Can be used to await a specific query triggered in any way,
* including via hook calls or manually dispatching `initiate` actions.
*
* See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details.
*/
getRunningQueryThunk<EndpointName extends AllQueryKeys<Definitions>>(endpointName: EndpointName, arg: QueryArgFromAnyQueryDefinition<Definitions, EndpointName>): ThunkWithReturnValue<QueryActionCreatorResult<Definitions[EndpointName] & {
type: 'query';
}> | InfiniteQueryActionCreatorResult<Definitions[EndpointName] & {
type: 'infinitequery';
}> | undefined>;
/**
* A thunk that (if dispatched) will return a specific running mutation, identified
* by `endpointName` and `fixedCacheKey` or `requestId`.
* If that mutation is not running, dispatching the thunk will result in `undefined`.
*
* Can be used to await a specific mutation triggered in any way,
* including via hook trigger functions or manually dispatching `initiate` actions.
*
* See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details.
*/
getRunningMutationThunk<EndpointName extends MutationKeys<Definitions>>(endpointName: EndpointName, fixedCacheKeyOrRequestId: string): ThunkWithReturnValue<MutationActionCreatorResult<Definitions[EndpointName] & {
type: 'mutation';
}> | undefined>;
/**
* A thunk that (if dispatched) will return all running queries.
*
* Useful for SSR scenarios to await all running queries triggered in any way,
* including via hook calls or manually dispatching `initiate` actions.
*
* See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details.
*/
getRunningQueriesThunk(): ThunkWithReturnValue<Array<QueryActionCreatorResult<any> | InfiniteQueryActionCreatorResult<any>>>;
/**
* A thunk that (if dispatched) will return all running mutations.
*
* Useful for SSR scenarios to await all running mutations triggered in any way,
* including via hook calls or manually dispatching `initiate` actions.
*
* See https://redux-toolkit.js.org/rtk-query/usage/server-side-rendering for details.
*/
getRunningMutationsThunk(): ThunkWithReturnValue<Array<MutationActionCreatorResult<any>>>;
/**
* A Redux thunk that can be used to manually trigger pre-fetching of data.
*
* The thunk accepts three arguments: the name of the endpoint we are updating (such as `'getPost'`), the appropriate query arg values to construct the desired cache key, and a set of options used to determine if the data actually should be re-fetched based on cache staleness.
*
* React Hooks users will most likely never need to use this directly, as the `usePrefetch` hook will dispatch this thunk internally as needed when you call the prefetching function supplied by the hook.
*
* @example
*
* ```ts no-transpile
* dispatch(api.util.prefetch('getPosts', undefined, { force: true }))
* ```
*/
prefetch<EndpointName extends QueryKeys<Definitions>>(endpointName: EndpointName, arg: QueryArgFrom<Definitions[EndpointName]>, options?: PrefetchOptions): ThunkAction<void, any, any, UnknownAction>;
/**
* A Redux thunk action creator that, when dispatched, creates and applies a set of JSON diff/patch objects to the current state. This immediately updates the Redux state with those changes.
*
* The thunk action creator accepts three arguments: the name of the endpoint we are updating (such as `'getPost'`), the appropriate query arg values to construct the desired cache key, and an `updateRecipe` callback function. The callback receives an Immer-wrapped `draft` of the current state, and may modify the draft to match the expected results after the mutation completes successfully.
*
* The thunk executes _synchronously_, and returns an object containing `{patches: Patch[], inversePatches: Patch[], undo: () => void}`. The `patches` and `inversePatches` are generated using Immer's [`produceWithPatches` method](https://immerjs.github.io/immer/patches).
*
* This is typically used as the first step in implementing optimistic updates. The generated `inversePatches` can be used to revert the updates by calling `dispatch(patchQueryData(endpointName, arg, inversePatches))`. Alternatively, the `undo` method can be called directly to achieve the same effect.
*
* Note that the first two arguments (`endpointName` and `arg`) are used to determine which existing cache entry to update. If no existing cache entry is found, the `updateRecipe` callback will not run.
*
* @ex