1import type { Api, ApiContext, Module, ModuleName } from './apiTypes' 2import type { BaseQueryArg, BaseQueryFn } from './baseQueryTypes' 3import type { SerializeQueryArgs } from './defaultSerializeQueryArgs' 4import { defaultSerializeQueryArgs } from './defaultSerializeQueryArgs' 5import type { 6 EndpointBuilder, 7 EndpointDefinitions, 8} from './endpointDefinitions' 9import { DefinitionType } from './endpointDefinitions' 10import { nanoid } from '@reduxjs/toolkit' 11 12export interface CreateApiOptions< 13 BaseQuery extends BaseQueryFn, 14 Definitions extends EndpointDefinitions, 15 ReducerPath extends string = 'api', 16 TagTypes extends string = never 17> { 18 /** 19 * 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. 20 * 21 * @example 22 * 23 * ```ts 24 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' 25 * 26 * const api = createApi({ 27 * // highlight-start 28 * baseQuery: fetchBaseQuery({ baseUrl: '/' }), 29 * // highlight-end 30 * endpoints: (build) => ({ 31 * // ...endpoints 32 * }), 33 * }) 34 * ``` 35 */ 36 baseQuery: BaseQuery 37 /** 38 * 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 an tag type, you will be able to [provide](../../rtk-query/usage/automated-refetching#providing-tags) them with `provides` and [invalidate](../../rtk-query/usage/automated-refetching#invalidating-tags) them with `invalidates` when configuring [endpoints](#endpoints). 39 * 40 * @example 41 * 42 * ```ts 43 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query' 44 * 45 * const api = createApi({ 46 * baseQuery: fetchBaseQuery({ baseUrl: '/' }), 47 * // highlight-start 48 * tagTypes: ['Post', 'User'], 49 * // highlight-end 50 * endpoints: (build) => ({ 51 * // ...endpoints 52 * }), 53 * }) 54 * ``` 55 */ 56 tagTypes?: readonly TagTypes[] 57 /** 58 * 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'`. 59 * 60 * @example 61 * 62 * ```ts 63 * // codeblock-meta title="apis.js" 64 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'; 65 * 66 * const apiOne = createApi({ 67 * // highlight-start 68 * reducerPath: 'apiOne', 69 * // highlight-end 70 * baseQuery: fetchBaseQuery({ baseUrl: '/' }), 71 * endpoints: (builder) => ({ 72 * // ...endpoints 73 * }), 74 * }); 75 * 76 * const apiTwo = createApi({ 77 * // highlight-start 78 * reducerPath: 'apiTwo', 79 * // highlight-end 80 * baseQuery: fetchBaseQuery({ baseUrl: '/' }), 81 * endpoints: (builder) => ({ 82 * // ...endpoints 83 * }), 84 * }); 85 * ``` 86 */ 87 reducerPath?: ReducerPath 88 /** 89 * Accepts a custom function if you have a need to change the creation of cache keys for any reason. 90 */ 91 serializeQueryArgs?: SerializeQueryArgs<BaseQueryArg<BaseQuery>> 92 /** 93 * Endpoints are just a set of operations that you want to perform against your server. You define them as an object using the builder syntax. There are two basic endpoint types: [`query`](../../rtk-query/usage/queries) and [`mutation`](../../rtk-query/usage/mutations). 94 */ 95 endpoints( 96 build: EndpointBuilder<BaseQuery, TagTypes, ReducerPath> 97 ): Definitions 98 /** 99 * 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. 100 * 101 * ```ts 102 * // codeblock-meta title="keepUnusedDataFor example" 103 * 104 * import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react' 105 * interface Post { 106 * id: number 107 * name: string 108 * } 109 * type PostsResponse = Post[] 110 * 111 * const api = createApi({ 112 * baseQuery: fetchBaseQuery({ baseUrl: '/' }), 113 * endpoints: (build) => ({ 114 * getPosts: build.query<PostsResponse, void>({ 115 * query: () => 'posts', 116 * // highlight-start 117 * keepUnusedDataFor: 5 118 * // highlight-end 119 * }) 120 * }) 121 * }) 122 * ``` 123 */ 124 keepUnusedDataFor?: number 125 /** 126 * 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. 127 * - `false` - Will not cause a query to be performed _unless_ it does not exist yet. 128 * - `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. 129 * - `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. 130 * 131 * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false. 132 */ 133 refetchOnMountOrArgChange?: boolean | number 134 /** 135 * 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. 136 * 137 * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false. 138 * 139 * Note: requires [`setupListeners`](./setupListeners) to have been called. 140 */ 141 refetchOnFocus?: boolean 142 /** 143 * Defaults to `false`. This setting allows you to control whether RTK Query will try to refetch all subscribed queries after regaining a network connection. 144 * 145 * If you specify this option alongside `skip: true`, this **will not be evaluated** until `skip` is false. 146 * 147 * Note: requires [`setupListeners`](./setupListeners) to have been called. 148 */ 149 refetchOnReconnect?: boolean 150} 151 152export type CreateApi<Modules extends ModuleName> = { 153 /** 154 * Creates a service to use in your application. Contains only the basic redux logic (the core module). 155 * 156 * @link https://rtk-query-docs.netlify.app/api/createApi 157 */ 158 < 159 BaseQuery extends BaseQueryFn, 160 Definitions extends EndpointDefinitions, 161 ReducerPath extends string = 'api', 162 TagTypes extends string = never 163 >( 164 options: CreateApiOptions<BaseQuery, Definitions, ReducerPath, TagTypes> 165 ): Api<BaseQuery, Definitions, ReducerPath, TagTypes, Modules> 166} 167 168/** 169 * Builds a `createApi` method based on the provided `modules`. 170 * 171 * @link https://rtk-query-docs.netlify.app/concepts/customizing-create-api 172 * 173 * @example 174 * ```ts 175 * const MyContext = React.createContext<ReactReduxContextValue>(null as any); 176 * const customCreateApi = buildCreateApi( 177 * coreModule(), 178 * reactHooksModule({ useDispatch: createDispatchHook(MyContext) }) 179 * ); 180 * ``` 181 * 182 * @param modules - A variable number of modules that customize how the `createApi` method handles endpoints 183 * @returns A `createApi` method using the provided `modules`. 184 */ 185export function buildCreateApi<Modules extends [Module<any>, ...Module<any>[]]>( 186 ...modules: Modules 187): CreateApi<Modules[number]['name']> { 188 return function baseCreateApi(options) { 189 const optionsWithDefaults = { 190 reducerPath: 'api', 191 serializeQueryArgs: defaultSerializeQueryArgs, 192 keepUnusedDataFor: 60, 193 refetchOnMountOrArgChange: false, 194 refetchOnFocus: false, 195 refetchOnReconnect: false, 196 ...options, 197 tagTypes: [...(options.tagTypes || [])], 198 } 199 200 const context: ApiContext<EndpointDefinitions> = { 201 endpointDefinitions: {}, 202 batch(fn) { 203 // placeholder "batch" method to be overridden by plugins, for example with React.unstable_batchedUpdate 204 fn() 205 }, 206 apiUid: nanoid(), 207 } 208 209 const api = { 210 injectEndpoints, 211 enhanceEndpoints({ addTagTypes, endpoints }) { 212 if (addTagTypes) { 213 for (const eT of addTagTypes) { 214 if (!optionsWithDefaults.tagTypes.includes(eT as any)) { 215 optionsWithDefaults.tagTypes.push(eT as any) 216 } 217 } 218 } 219 if (endpoints) { 220 for (const [endpointName, partialDefinition] of Object.entries( 221 endpoints 222 )) { 223 if (typeof partialDefinition === 'function') { 224 partialDefinition(context.endpointDefinitions[endpointName]) 225 } 226 Object.assign( 227 context.endpointDefinitions[endpointName] || {}, 228 partialDefinition 229 ) 230 } 231 } 232 return api 233 }, 234 } as Api<BaseQueryFn, {}, string, string, Modules[number]['name']> 235 236 const initializedModules = modules.map((m) => 237 m.init(api as any, optionsWithDefaults, context) 238 ) 239 240 function injectEndpoints( 241 inject: Parameters<typeof api.injectEndpoints>[0] 242 ) { 243 const evaluatedEndpoints = inject.endpoints({ 244 query: (x) => ({ ...x, type: DefinitionType.query } as any), 245 mutation: (x) => ({ ...x, type: DefinitionType.mutation } as any), 246 }) 247 248 for (const [endpointName, definition] of Object.entries( 249 evaluatedEndpoints 250 )) { 251 if ( 252 !inject.overrideExisting && 253 endpointName in context.endpointDefinitions 254 ) { 255 if ( 256 typeof process !== 'undefined' && 257 process.env.NODE_ENV === 'development' 258 ) { 259 console.error( 260 `called \`injectEndpoints\` to override already-existing endpointName ${endpointName} without specifying \`overrideExisting: true\`` 261 ) 262 } 263 264 continue 265 } 266 context.endpointDefinitions[endpointName] = definition 267 for (const m of initializedModules) { 268 m.injectEndpoint(endpointName, definition) 269 } 270 } 271 272 return api as any 273 } 274 275 return api.injectEndpoints({ endpoints: options.endpoints as any }) 276 } 277} 278