source: node_modules/@reduxjs/toolkit/src/query/createApi.ts

Last change on this file was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 18.4 KB
RevLine 
[a762898]1import {
2 getEndpointDefinition,
3 type Api,
4 type ApiContext,
5 type Module,
6 type ModuleName,
7} from './apiTypes'
8import type { CombinedState } from './core/apiState'
9import type { BaseQueryArg, BaseQueryFn } from './baseQueryTypes'
10import type { SerializeQueryArgs } from './defaultSerializeQueryArgs'
11import { defaultSerializeQueryArgs } from './defaultSerializeQueryArgs'
12import type {
13 EndpointBuilder,
14 EndpointDefinitions,
15 SchemaFailureConverter,
16 SchemaFailureHandler,
17 SchemaType,
18} from './endpointDefinitions'
19import {
20 DefinitionType,
21 ENDPOINT_INFINITEQUERY,
22 ENDPOINT_MUTATION,
23 ENDPOINT_QUERY,
24 isInfiniteQueryDefinition,
25 isQueryDefinition,
26} from './endpointDefinitions'
27import { nanoid } from './core/rtkImports'
28import type { UnknownAction } from '@reduxjs/toolkit'
29import type { NoInfer } from './tsHelpers'
30import { weakMapMemoize } from 'reselect'
31
32export interface CreateApiOptions<
33 BaseQuery extends BaseQueryFn,
34 Definitions extends EndpointDefinitions,
35 ReducerPath extends string = 'api',
36 TagTypes extends string = never,
37> {
38 /**
39 * The base query used by each endpoint if no `queryFn` option is specified. RTK Query exports a utility called [fetchBaseQuery](./fetchBaseQuery) as a lightweight wrapper around `fetch` for common use-cases. See [Customizing Queries](../../rtk-query/usage/customizing-queries) if `fetchBaseQuery` does not handle your requirements.
40 *
41 * @example
42 *
43 * ```ts
44 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
45 *
46 * const api = createApi({
47 * // highlight-start
48 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
49 * // highlight-end
50 * endpoints: (build) => ({
51 * // ...endpoints
52 * }),
53 * })
54 * ```
55 */
56 baseQuery: BaseQuery
57 /**
58 * An array of string tag type names. Specifying tag types is optional, but you should define them so that they can be used for caching and invalidation. When defining a tag type, you will be able to [provide](../../rtk-query/usage/automated-refetching#providing-tags) them with `providesTags` and [invalidate](../../rtk-query/usage/automated-refetching#invalidating-tags) them with `invalidatesTags` when configuring [endpoints](#endpoints).
59 *
60 * @example
61 *
62 * ```ts
63 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
64 *
65 * const api = createApi({
66 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
67 * // highlight-start
68 * tagTypes: ['Post', 'User'],
69 * // highlight-end
70 * endpoints: (build) => ({
71 * // ...endpoints
72 * }),
73 * })
74 * ```
75 */
76 tagTypes?: readonly TagTypes[]
77 /**
78 * The `reducerPath` is a _unique_ key that your service will be mounted to in your store. If you call `createApi` more than once in your application, you will need to provide a unique value each time. Defaults to `'api'`.
79 *
80 * @example
81 *
82 * ```ts
83 * // codeblock-meta title="apis.js"
84 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
85 *
86 * const apiOne = createApi({
87 * // highlight-start
88 * reducerPath: 'apiOne',
89 * // highlight-end
90 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
91 * endpoints: (builder) => ({
92 * // ...endpoints
93 * }),
94 * });
95 *
96 * const apiTwo = createApi({
97 * // highlight-start
98 * reducerPath: 'apiTwo',
99 * // highlight-end
100 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
101 * endpoints: (builder) => ({
102 * // ...endpoints
103 * }),
104 * });
105 * ```
106 */
107 reducerPath?: ReducerPath
108 /**
109 * Accepts a custom function if you have a need to change the creation of cache keys for any reason.
110 */
111 serializeQueryArgs?: SerializeQueryArgs<unknown>
112 /**
113 * Endpoints are a set of operations that you want to perform against your server. You define them as an object using the builder syntax. There are three endpoint types: [`query`](../../rtk-query/usage/queries), [`infiniteQuery`](../../rtk-query/usage/infinite-queries) and [`mutation`](../../rtk-query/usage/mutations).
114 */
115 endpoints(
116 build: EndpointBuilder<BaseQuery, TagTypes, ReducerPath>,
117 ): Definitions
118 /**
119 * Defaults to `60` _(this value is in seconds)_. This is how long RTK Query will keep your data cached for **after** the last component unsubscribes. For example, if you query an endpoint, then unmount the component, then mount another component that makes the same request within the given time frame, the most recent value will be served from the cache.
120 *
121 * @example
122 * ```ts
123 * // codeblock-meta title="keepUnusedDataFor example"
124 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
125 * interface Post {
126 * id: number
127 * name: string
128 * }
129 * type PostsResponse = Post[]
130 *
131 * const api = createApi({
132 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
133 * endpoints: (build) => ({
134 * getPosts: build.query<PostsResponse, void>({
135 * query: () => 'posts'
136 * })
137 * }),
138 * // highlight-start
139 * keepUnusedDataFor: 5
140 * // highlight-end
141 * })
142 * ```
143 */
144 keepUnusedDataFor?: number
145 /**
146 * Defaults to `false`. This setting allows you to control whether if a cached result is already available RTK Query will only serve a cached result, or if it should `refetch` when set to `true` or if an adequate amount of time has passed since the last successful query result.
147 * - `false` - Will not cause a query to be performed _unless_ it does not exist yet.
148 * - `true` - Will always refetch when a new subscriber to a query is added. Behaves the same as calling the `refetch` callback or passing `forceRefetch: true` in the action creator.
149 * - `number` - **Value is in seconds**. If a number is provided and there is an existing query in the cache, it will compare the current time vs the last fulfilled timestamp, and only refetch if enough time has elapsed.
150 *
151 * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false.
152 */
153 refetchOnMountOrArgChange?: boolean | number
154 /**
155 * Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after the application window regains focus.
156 *
157 * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false.
158 *
159 * Note: requires [`setupListeners`](./setupListeners) to have been called.
160 */
161 refetchOnFocus?: boolean
162 /**
163 * Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after regaining a network connection.
164 *
165 * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false.
166 *
167 * Note: requires [`setupListeners`](./setupListeners) to have been called.
168 */
169 refetchOnReconnect?: boolean
170 /**
171 * Defaults to `'delayed'`. This setting allows you to control when tags are invalidated after a mutation.
172 *
173 * - `'immediately'`: Queries are invalidated instantly after the mutation finished, even if they are running.
174 * If the query provides tags that were invalidated while it ran, it won't be re-fetched.
175 * - `'delayed'`: Invalidation only happens after all queries and mutations are settled.
176 * This ensures that queries are always invalidated correctly and automatically "batches" invalidations of concurrent mutations.
177 * Note that if you constantly have some queries (or mutations) running, this can delay tag invalidations indefinitely.
178 */
179 invalidationBehavior?: 'delayed' | 'immediately'
180 /**
181 * A function that is passed every dispatched action. If this returns something other than `undefined`,
182 * that return value will be used to rehydrate fulfilled & errored queries.
183 *
184 * @example
185 *
186 * ```ts
187 * // codeblock-meta title="next-redux-wrapper rehydration example"
188 * import type { Action, PayloadAction } from '@reduxjs/toolkit'
189 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
190 * import { HYDRATE } from 'next-redux-wrapper'
191 *
192 * type RootState = any; // normally inferred from state
193 *
194 * function isHydrateAction(action: Action): action is PayloadAction<RootState> {
195 * return action.type === HYDRATE
196 * }
197 *
198 * export const api = createApi({
199 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
200 * // highlight-start
201 * extractRehydrationInfo(action, { reducerPath }): any {
202 * if (isHydrateAction(action)) {
203 * return action.payload[reducerPath]
204 * }
205 * },
206 * // highlight-end
207 * endpoints: (build) => ({
208 * // omitted
209 * }),
210 * })
211 * ```
212 */
213 extractRehydrationInfo?: (
214 action: UnknownAction,
215 {
216 reducerPath,
217 }: {
218 reducerPath: ReducerPath
219 },
220 ) =>
221 | undefined
222 | CombinedState<
223 NoInfer<Definitions>,
224 NoInfer<TagTypes>,
225 NoInfer<ReducerPath>
226 >
227
228 /**
229 * A function that is called when a schema validation fails.
230 *
231 * Gets called with a `NamedSchemaError` and an object containing the endpoint name, the type of the endpoint, the argument passed to the endpoint, and the query cache key (if applicable).
232 *
233 * `NamedSchemaError` has the following properties:
234 * - `issues`: an array of issues that caused the validation to fail
235 * - `value`: the value that was passed to the schema
236 * - `schemaName`: the name of the schema that was used to validate the value (e.g. `argSchema`)
237 *
238 * @example
239 * ```ts
240 * // codeblock-meta no-transpile
241 * import { createApi } from '@reduxjs/toolkit/query/react'
242 * import * as v from "valibot"
243 *
244 * const api = createApi({
245 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
246 * endpoints: (build) => ({
247 * getPost: build.query<Post, { id: number }>({
248 * query: ({ id }) => `/post/${id}`,
249 * }),
250 * }),
251 * onSchemaFailure: (error, info) => {
252 * console.error(error, info)
253 * },
254 * })
255 * ```
256 */
257 onSchemaFailure?: SchemaFailureHandler
258
259 /**
260 * Convert a schema validation failure into an error shape matching base query errors.
261 *
262 * When not provided, schema failures are treated as fatal, and normal error handling such as tag invalidation will not be executed.
263 *
264 * @example
265 * ```ts
266 * // codeblock-meta no-transpile
267 * import { createApi } from '@reduxjs/toolkit/query/react'
268 * import * as v from "valibot"
269 *
270 * const api = createApi({
271 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
272 * endpoints: (build) => ({
273 * getPost: build.query<Post, { id: number }>({
274 * query: ({ id }) => `/post/${id}`,
275 * responseSchema: v.object({ id: v.number(), name: v.string() }),
276 * }),
277 * }),
278 * catchSchemaFailure: (error, info) => ({
279 * status: "CUSTOM_ERROR",
280 * error: error.schemaName + " failed validation",
281 * data: error.issues,
282 * }),
283 * })
284 * ```
285 */
286 catchSchemaFailure?: SchemaFailureConverter<BaseQuery>
287
288 /**
289 * Defaults to `false`.
290 *
291 * If set to `true`, will skip schema validation for all endpoints, unless overridden by the endpoint.
292 *
293 * Can be overridden for specific schemas by passing an array of schema types to skip.
294 *
295 * @example
296 * ```ts
297 * // codeblock-meta no-transpile
298 * import { createApi } from '@reduxjs/toolkit/query/react'
299 * import * as v from "valibot"
300 *
301 * const api = createApi({
302 * baseQuery: fetchBaseQuery({ baseUrl: '/' }),
303 * skipSchemaValidation: process.env.NODE_ENV === "test" ? ["response"] : false, // skip schema validation for response in tests, since we'll be mocking the response
304 * endpoints: (build) => ({
305 * getPost: build.query<Post, { id: number }>({
306 * query: ({ id }) => `/post/${id}`,
307 * responseSchema: v.object({ id: v.number(), name: v.string() }),
308 * }),
309 * })
310 * })
311 * ```
312 */
313 skipSchemaValidation?: boolean | SchemaType[]
314}
315
316export type CreateApi<Modules extends ModuleName> = {
317 /**
318 * Creates a service to use in your application. Contains only the basic redux logic (the core module).
319 *
320 * @link https://redux-toolkit.js.org/rtk-query/api/createApi
321 */
322 <
323 BaseQuery extends BaseQueryFn,
324 Definitions extends EndpointDefinitions,
325 ReducerPath extends string = 'api',
326 TagTypes extends string = never,
327 >(
328 options: CreateApiOptions<BaseQuery, Definitions, ReducerPath, TagTypes>,
329 ): Api<BaseQuery, Definitions, ReducerPath, TagTypes, Modules>
330}
331
332/**
333 * Builds a `createApi` method based on the provided `modules`.
334 *
335 * @link https://redux-toolkit.js.org/rtk-query/usage/customizing-create-api
336 *
337 * @example
338 * ```ts
339 * const MyContext = React.createContext<ReactReduxContextValue | null>(null);
340 * const customCreateApi = buildCreateApi(
341 * coreModule(),
342 * reactHooksModule({
343 * hooks: {
344 * useDispatch: createDispatchHook(MyContext),
345 * useSelector: createSelectorHook(MyContext),
346 * useStore: createStoreHook(MyContext)
347 * }
348 * })
349 * );
350 * ```
351 *
352 * @param modules - A variable number of modules that customize how the `createApi` method handles endpoints
353 * @returns A `createApi` method using the provided `modules`.
354 */
355export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>(
356 ...modules: Modules
357): CreateApi<Modules[number]['name']> {
358 return function baseCreateApi(options) {
359 const extractRehydrationInfo = weakMapMemoize((action: UnknownAction) =>
360 options.extractRehydrationInfo?.(action, {
361 reducerPath: (options.reducerPath ?? 'api') as any,
362 }),
363 )
364
365 const optionsWithDefaults: CreateApiOptions<any, any, any, any> = {
366 reducerPath: 'api',
367 keepUnusedDataFor: 60,
368 refetchOnMountOrArgChange: false,
369 refetchOnFocus: false,
370 refetchOnReconnect: false,
371 invalidationBehavior: 'delayed',
372 ...options,
373 extractRehydrationInfo,
374 serializeQueryArgs(queryArgsApi) {
375 let finalSerializeQueryArgs = defaultSerializeQueryArgs
376 if ('serializeQueryArgs' in queryArgsApi.endpointDefinition) {
377 const endpointSQA =
378 queryArgsApi.endpointDefinition.serializeQueryArgs!
379 finalSerializeQueryArgs = (queryArgsApi) => {
380 const initialResult = endpointSQA(queryArgsApi)
381 if (typeof initialResult === 'string') {
382 // If the user function returned a string, use it as-is
383 return initialResult
384 } else {
385 // Assume they returned an object (such as a subset of the original
386 // query args) or a primitive, and serialize it ourselves
387 return defaultSerializeQueryArgs({
388 ...queryArgsApi,
389 queryArgs: initialResult,
390 })
391 }
392 }
393 } else if (options.serializeQueryArgs) {
394 finalSerializeQueryArgs = options.serializeQueryArgs
395 }
396
397 return finalSerializeQueryArgs(queryArgsApi)
398 },
399 tagTypes: [...(options.tagTypes || [])],
400 }
401
402 const context: ApiContext<EndpointDefinitions> = {
403 endpointDefinitions: {},
404 batch(fn) {
405 // placeholder "batch" method to be overridden by plugins, for example with React.unstable_batchedUpdate
406 fn()
407 },
408 apiUid: nanoid(),
409 extractRehydrationInfo,
410 hasRehydrationInfo: weakMapMemoize(
411 (action) => extractRehydrationInfo(action) != null,
412 ),
413 }
414
415 const api = {
416 injectEndpoints,
417 enhanceEndpoints({ addTagTypes, endpoints }) {
418 if (addTagTypes) {
419 for (const eT of addTagTypes) {
420 if (!optionsWithDefaults.tagTypes!.includes(eT as any)) {
421 ;(optionsWithDefaults.tagTypes as any[]).push(eT)
422 }
423 }
424 }
425 if (endpoints) {
426 for (const [endpointName, partialDefinition] of Object.entries(
427 endpoints,
428 )) {
429 if (typeof partialDefinition === 'function') {
430 partialDefinition(getEndpointDefinition(context, endpointName))
431 } else {
432 Object.assign(
433 getEndpointDefinition(context, endpointName) || {},
434 partialDefinition,
435 )
436 }
437 }
438 }
439 return api
440 },
441 } as Api<BaseQueryFn, {}, string, string, Modules[number]['name']>
442
443 const initializedModules = modules.map((m) =>
444 m.init(api as any, optionsWithDefaults as any, context),
445 )
446
447 function injectEndpoints(
448 inject: Parameters<typeof api.injectEndpoints>[0],
449 ) {
450 const evaluatedEndpoints = inject.endpoints({
451 query: (x) => ({ ...x, type: ENDPOINT_QUERY }) as any,
452 mutation: (x) => ({ ...x, type: ENDPOINT_MUTATION }) as any,
453 infiniteQuery: (x) => ({ ...x, type: ENDPOINT_INFINITEQUERY }) as any,
454 })
455
456 for (const [endpointName, definition] of Object.entries(
457 evaluatedEndpoints,
458 )) {
459 if (
460 inject.overrideExisting !== true &&
461 endpointName in context.endpointDefinitions
462 ) {
463 if (inject.overrideExisting === 'throw') {
464 throw new Error(
465 `called \`injectEndpoints\` to override already-existing endpointName ${endpointName} without specifying \`overrideExisting: true\``,
466 )
467 } else if (
468 typeof process !== 'undefined' &&
469 process.env.NODE_ENV === 'development'
470 ) {
471 console.error(
472 `called \`injectEndpoints\` to override already-existing endpointName ${endpointName} without specifying \`overrideExisting: true\``,
473 )
474 }
475
476 continue
477 }
478
479 if (
480 typeof process !== 'undefined' &&
481 process.env.NODE_ENV === 'development'
482 ) {
483 if (isInfiniteQueryDefinition(definition)) {
484 const { infiniteQueryOptions } = definition
485 const { maxPages, getPreviousPageParam } = infiniteQueryOptions
486
487 if (typeof maxPages === 'number') {
488 if (maxPages < 1) {
489 throw new Error(
490 `maxPages for endpoint '${endpointName}' must be a number greater than 0`,
491 )
492 }
493
494 if (typeof getPreviousPageParam !== 'function') {
495 throw new Error(
496 `getPreviousPageParam for endpoint '${endpointName}' must be a function if maxPages is used`,
497 )
498 }
499 }
500 }
501 }
502
503 context.endpointDefinitions[endpointName] = definition
504 for (const m of initializedModules) {
505 m.injectEndpoint(endpointName, definition)
506 }
507 }
508
509 return api as any
510 }
511
512 return api.injectEndpoints({ endpoints: options.endpoints as any })
513 }
514}
Note: See TracBrowser for help on using the repository browser.