@7nohe/openapi-react-query-codegen 1.6.0 → 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Daiki Urata
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,369 +1,12 @@
1
1
  # OpenAPI React Query Codegen
2
2
 
3
- > Node.js library that generates [React Query (also called TanStack Query)](https://tanstack.com/query) hooks based on an OpenAPI specification file.
3
+ > Code generator for creating [React Query (also known as TanStack Query)](https://tanstack.com/query) hooks based on your OpenAPI schema.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen.svg)](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen)
6
6
 
7
7
  ## Features
8
8
 
9
9
  - Generates custom react hooks that use React Query's `useQuery`, `useSuspenseQuery`, `useMutation` and `useInfiniteQuery` hooks
10
+ - Generates custom functions that use React Query's `ensureQueryData` and `prefetchQuery` functions
10
11
  - Generates query keys and functions for query caching
11
12
  - Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts)
12
-
13
- ## Installation
14
-
15
- ```
16
- $ npm install -D @7nohe/openapi-react-query-codegen
17
- ```
18
-
19
- Register the command to the `scripts` property in your package.json file.
20
-
21
- ```json
22
- {
23
- "scripts": {
24
- "codegen": "openapi-rq -i ./petstore.yaml -c axios"
25
- }
26
- }
27
- ```
28
-
29
- You can also run the command without installing it in your project using the npx command.
30
-
31
- ```bash
32
- $ npx --package @7nohe/openapi-react-query-codegen openapi-rq -i ./petstore.yaml -c axios
33
- ```
34
-
35
- ## Usage
36
-
37
- ```
38
- $ openapi-rq --help
39
-
40
- Usage: openapi-rq [options]
41
-
42
- Generate React Query code based on OpenAPI
43
-
44
- Options:
45
- -V, --version output the version number
46
- -i, --input <value> OpenAPI specification, can be a path, url or string content (required)
47
- -o, --output <value> Output directory (default: "openapi")
48
- -c, --client <value> HTTP client to generate (choices: "angular", "axios", "fetch", "node", "xhr", default: "fetch")
49
- --request <value> Path to custom request file
50
- --format <value> Process output folder with formatter? (choices: "biome", "prettier")
51
- --lint <value> Process output folder with linter? (choices: "biome", "eslint")
52
- --operationId Use operation ID to generate operation names?
53
- --serviceResponse <value> Define shape of returned value from service calls (choices: "body", "response", default: "body")
54
- --base <value> Manually set base in OpenAPI config instead of inferring from server value
55
- --enums <value> Generate JavaScript objects from enum definitions? ['javascript', 'typescript', 'typescript+namespace']
56
- --enums <value> Generate JavaScript objects from enum definitions? (choices: "javascript", "typescript")
57
- --useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
58
- --debug Run in debug mode?
59
- --noSchemas Disable generating JSON schemas
60
- --schemaType <value> Type of JSON schema [Default: 'json'] (choices: "form", "json")
61
- --pageParam <value> Name of the query parameter used for pagination (default: "page")
62
- --nextPageParam <value> Name of the response parameter used for next page (default: "nextPage")
63
- -h, --help display help for command
64
- ```
65
-
66
- ### Example Usage
67
-
68
- #### Command
69
-
70
- ```
71
- $ openapi-rq -i ./petstore.yaml
72
- ```
73
-
74
- #### Output directory structure
75
-
76
- ```
77
- - openapi
78
- - queries
79
- - index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
80
- - common.ts <- common types
81
- - queries.ts <- generated query hooks
82
- - suspenses.ts <- generated suspense hooks
83
- - prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
84
- - requests <- output code generated by @hey-api/openapi-ts
85
- ```
86
-
87
- - [Prefetching docs](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#prefetching-and-dehydrating-data)
88
-
89
- #### In your app
90
-
91
- ##### Using the generated hooks
92
-
93
- ```tsx
94
- // App.tsx
95
- import { usePetServiceFindPetsByStatus } from "../openapi/queries";
96
- function App() {
97
- const { data } = usePetServiceFindPetsByStatus({ status: ["available"] });
98
-
99
- return (
100
- <div className="App">
101
- <h1>Pet List</h1>
102
- <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
103
- </div>
104
- );
105
- }
106
-
107
- export default App;
108
- ```
109
-
110
- ##### Using the generated typescript client
111
-
112
- ```tsx
113
- import { useQuery } from "@tanstack/react-query";
114
- import { PetService } from "../openapi/requests/services";
115
- import { usePetServiceFindPetsByStatusKey } from "../openapi/queries";
116
-
117
- function App() {
118
- // You can still use the auto-generated query key
119
- const { data } = useQuery({
120
- queryKey: [usePetServiceFindPetsByStatusKey],
121
- queryFn: () => {
122
- // Do something here
123
- return PetService.findPetsByStatus(["available"]);
124
- },
125
- });
126
-
127
- return <div className="App">{/* .... */}</div>;
128
- }
129
-
130
- export default App;
131
- ```
132
-
133
- ##### Using Suspense Hooks
134
-
135
- ```tsx
136
- // App.tsx
137
- import { useDefaultClientFindPetsSuspense } from "../openapi/queries/suspense";
138
- function ChildComponent() {
139
- const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });
140
-
141
- return <ul>{data?.map((pet, index) => <li key={pet.id}>{pet.name}</li>)}</ul>;
142
- }
143
-
144
- function ParentComponent() {
145
- return (
146
- <>
147
- <Suspense fallback={<>loading...</>}>
148
- <ChildComponent />
149
- </Suspense>
150
- </>
151
- );
152
- }
153
-
154
- function App() {
155
- return (
156
- <div className="App">
157
- <h1>Pet List</h1>
158
- <ParentComponent />
159
- </div>
160
- );
161
- }
162
-
163
- export default App;
164
- ```
165
-
166
- ##### Using Mutation hooks
167
-
168
- ```tsx
169
- // App.tsx
170
- import { usePetServiceAddPet } from "../openapi/queries";
171
-
172
- function App() {
173
- const { mutate } = usePetServiceAddPet();
174
-
175
- const handleAddPet = () => {
176
- mutate({ name: "Fluffy", status: "available" });
177
- };
178
-
179
- return (
180
- <div className="App">
181
- <h1>Add Pet</h1>
182
- <button onClick={handleAddPet}>Add Pet</button>
183
- </div>
184
- );
185
- }
186
-
187
- export default App;
188
- ```
189
-
190
- ##### Invalidating queries after mutation
191
-
192
- Invalidating queries after a mutation is important to ensure the cache is updated with the new data. This is done by calling the `queryClient.invalidateQueries` function with the query key used by the query hook.
193
-
194
- Learn more about invalidating queries [here](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation).
195
-
196
- To ensure the query key is created the same way as the query hook, you can use the query key function exported by the generated query hooks.
197
-
198
- ```tsx
199
- import {
200
- usePetServiceFindPetsByStatus,
201
- usePetServiceAddPet,
202
- UsePetServiceFindPetsByStatusKeyFn,
203
- } from "../openapi/queries";
204
-
205
- // App.tsx
206
- function App() {
207
- const [status, setStatus] = React.useState(["available"]);
208
- const { data } = usePetServiceFindPetsByStatus({ status });
209
- const { mutate } = usePetServiceAddPet({
210
- onSuccess: () => {
211
- queryClient.invalidateQueries({
212
- // Call the query key function to get the query key
213
- // This is important to ensure the query key is created the same way as the query hook
214
- // This insures the cache is invalidated correctly and is typed correctly
215
- queryKey: [UsePetServiceFindPetsByStatusKeyFn({
216
- status
217
- })],
218
- });
219
- },
220
- });
221
-
222
- return (
223
- <div className="App">
224
- <h1>Pet List</h1>
225
- <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
226
- <button
227
- onClick={() => {
228
- mutate({ name: "Fluffy", status: "available" });
229
- }}
230
- >
231
- Add Pet
232
- </button>
233
- </div>
234
- );
235
- }
236
-
237
- export default App;
238
- ```
239
-
240
- ##### Using Infinite Query hooks
241
-
242
- This feature will generate a function in infiniteQueries.ts when the name specified by the `pageParam` option exists in the query parameters and the name specified by the `nextPageParam` option exists in the response.
243
-
244
- Example Schema:
245
-
246
- ```yml
247
- paths:
248
- /paginated-pets:
249
- get:
250
- description: |
251
- Returns paginated pets from the system that the user has access to
252
- operationId: findPaginatedPets
253
- parameters:
254
- - name: page
255
- in: query
256
- description: page number
257
- required: false
258
- schema:
259
- type: integer
260
- format: int32
261
- - name: tags
262
- in: query
263
- description: tags to filter by
264
- required: false
265
- style: form
266
- schema:
267
- type: array
268
- items:
269
- type: string
270
- - name: limit
271
- in: query
272
- description: maximum number of results to return
273
- required: false
274
- schema:
275
- type: integer
276
- format: int32
277
- responses:
278
- '200':
279
- description: pet response
280
- content:
281
- application/json:
282
- schema:
283
- type: object
284
- properties:
285
- pets:
286
- type: array
287
- items:
288
- $ref: '#/components/schemas/Pet'
289
- nextPage:
290
- type: integer
291
- format: int32
292
- minimum: 1
293
- ```
294
-
295
- Usage of Generated Hooks:
296
-
297
- ```ts
298
- import { useDefaultServiceFindPaginatedPetsInfinite } from "@/openapi/queries/infiniteQueries";
299
-
300
- const { data, fetchNextPage } = useDefaultServiceFindPaginatedPetsInfinite({
301
- limit: 10,
302
- tags: [],
303
- });
304
- ```
305
-
306
- ##### Runtime Configuration
307
-
308
- You can modify the default values used by the generated service calls by modifying the OpenAPI configuration singleton object.
309
-
310
- It's default location is `openapi/requests/core/OpenAPI.ts` and it is also exported from `openapi/index.ts`
311
-
312
- Import the constant into your runtime and modify it before setting up the react app.
313
-
314
- ```typescript
315
- /** main.tsx */
316
- import { OpenAPI as OpenAPIConfig } from './openapi/requests/core/OpenAPI';
317
- ...
318
- OpenAPIConfig.BASE = 'www.domain.com/api';
319
- OpenAPIConfig.HEADERS = {
320
- 'x-header-1': 'value-1',
321
- 'x-header-2': 'value-2',
322
- };
323
- ...
324
- ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
325
- <React.StrictMode>
326
- <QueryClientProvider client={queryClient}>
327
- <App />
328
- </QueryClientProvider>
329
- </React.StrictMode>
330
- );
331
-
332
- ```
333
-
334
- ## Development
335
-
336
- ### Install dependencies
337
-
338
- ```bash
339
- pnpm install
340
- ```
341
-
342
- ### Run tests
343
- ```bash
344
- pnpm test
345
- ```
346
-
347
- ### Run linter
348
- ```bash
349
- pnpm lint
350
- ```
351
-
352
- ### Run linter and fix
353
- ```bash
354
- pnpm lint:fix
355
- ```
356
-
357
- ### Update snapshots
358
- ```bash
359
- pnpm snapshot
360
- ```
361
-
362
- ### Build example and validate generated code
363
- ```bash
364
- npm run build && pnpm --filter @7nohe/react-app generate:api && pnpm --filter @7nohe/react-app test:generated
365
- ```
366
-
367
- ## License
368
-
369
- MIT
package/dist/cli.mjs CHANGED
@@ -19,23 +19,19 @@ async function setupProgram() {
19
19
  .requiredOption("-i, --input <value>", "OpenAPI specification, can be a path, url or string content (required)")
20
20
  .option("-o, --output <value>", "Output directory", defaultOutputPath)
21
21
  .addOption(new Option("-c, --client <value>", "HTTP client to generate")
22
- .choices(["angular", "axios", "fetch", "node", "xhr"])
23
- .default("fetch"))
24
- .option("--request <value>", "Path to custom request file")
22
+ .choices(["@hey-api/client-fetch", "@hey-api/client-axios"])
23
+ .default("@hey-api/client-fetch"))
25
24
  .addOption(new Option("--format <value>", "Process output folder with formatter?").choices(["biome", "prettier"]))
26
25
  .addOption(new Option("--lint <value>", "Process output folder with linter?").choices(["biome", "eslint"]))
27
26
  .option("--operationId", "Use operation ID to generate operation names?")
28
- .addOption(new Option("--serviceResponse <value>", "Define shape of returned value from service calls")
29
- .choices(["body", "response"])
30
- .default("body"))
31
- .option("--base <value>", "Manually set base in OpenAPI config instead of inferring from server value")
32
- .addOption(new Option("--enums <value>", "Generate JavaScript objects from enum definitions?").choices(["javascript", "typescript", "typescript+namespace"]))
27
+ .addOption(new Option("--enums <value>", "Generate JavaScript objects from enum definitions?").choices(["javascript", "typescript"]))
33
28
  .option("--useDateType", "Use Date type instead of string for date types for models, this will not convert the data to a Date object")
34
29
  .option("--debug", "Run in debug mode?")
35
30
  .option("--noSchemas", "Disable generating JSON schemas")
36
31
  .addOption(new Option("--schemaType <value>", "Type of JSON schema [Default: 'json']").choices(["form", "json"]))
37
32
  .option("--pageParam <value>", "Name of the query parameter used for pagination", "page")
38
33
  .option("--nextPageParam <value>", "Name of the response parameter used for next page", "nextPage")
34
+ .option("--initialPageParam <value>", "Initial page value to query", "1")
39
35
  .parse();
40
36
  const options = program.opts();
41
37
  await generate(options, version);
package/dist/common.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { ArrowFunction } from "ts-morph";
3
4
  import ts from "typescript";
4
5
  import { queriesOutputPath, requestsOutputPath } from "./constants.mjs";
5
6
  export const TData = ts.factory.createIdentifier("TData");
@@ -17,12 +18,22 @@ export const capitalizeFirstLetter = (str) => {
17
18
  export const lowercaseFirstLetter = (str) => {
18
19
  return str.charAt(0).toLowerCase() + str.slice(1);
19
20
  };
20
- export const getNameFromMethod = (method) => {
21
- const methodName = method.getName();
22
- if (!methodName) {
23
- throw new Error("Method name not found");
21
+ export const getVariableArrowFunctionParameters = (variable) => {
22
+ const initializer = variable.getInitializer();
23
+ if (!initializer) {
24
+ throw new Error("Initializer not found");
24
25
  }
25
- return methodName;
26
+ if (!ArrowFunction.isArrowFunction(initializer)) {
27
+ throw new Error("Initializer is not an arrow function");
28
+ }
29
+ return initializer.getParameters();
30
+ };
31
+ export const getNameFromVariable = (variable) => {
32
+ const variableName = variable.getName();
33
+ if (!variableName) {
34
+ throw new Error("Variable name not found");
35
+ }
36
+ return variableName;
26
37
  };
27
38
  export async function exists(f) {
28
39
  try {
@@ -63,11 +74,14 @@ export function extractPropertiesFromObjectParam(param) {
63
74
  .getNode()
64
75
  .getType()
65
76
  .getProperties()
66
- .map((prop) => ({
67
- name: prop.getName(),
68
- optional: prop.isOptional(),
69
- type: prop.getValueDeclaration()?.getType(),
70
- }));
77
+ .filter((prop) => prop.getValueDeclaration()?.getType())
78
+ .map((prop) => {
79
+ return {
80
+ name: prop.getName(),
81
+ optional: prop.isOptional(),
82
+ type: prop.getValueDeclaration()?.getType(),
83
+ };
84
+ });
71
85
  return paramNodes;
72
86
  }
73
87
  /**
@@ -135,3 +149,72 @@ export function buildRequestsOutputPath(outputPath) {
135
149
  export function buildQueriesOutputPath(outputPath) {
136
150
  return path.join(outputPath, queriesOutputPath);
137
151
  }
152
+ export function getQueryKeyFnName(queryKey) {
153
+ return `${capitalizeFirstLetter(queryKey)}Fn`;
154
+ }
155
+ /**
156
+ * Create QueryKey/MutationKey exports
157
+ */
158
+ export function createQueryKeyExport({ methodName, queryKey, }) {
159
+ return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
160
+ ts.factory.createVariableDeclaration(ts.factory.createIdentifier(queryKey), undefined, undefined, ts.factory.createStringLiteral(`${capitalizeFirstLetter(methodName)}`)),
161
+ ], ts.NodeFlags.Const));
162
+ }
163
+ export function createQueryKeyFnExport(queryKey, method, type = "query") {
164
+ // Mutation keys don't require clientOptions
165
+ const params = type === "query" ? getRequestParamFromMethod(method) : null;
166
+ // override key is used to allow the user to override the the queryKey values
167
+ const overrideKey = ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier(type === "query" ? "queryKey" : "mutationKey"), QuestionToken, ts.factory.createTypeReferenceNode("Array<unknown>", []));
168
+ return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
169
+ ts.factory.createVariableDeclaration(ts.factory.createIdentifier(getQueryKeyFnName(queryKey)), undefined, undefined, ts.factory.createArrowFunction(undefined, undefined, params ? [params, overrideKey] : [overrideKey], undefined, EqualsOrGreaterThanToken, type === "query"
170
+ ? queryKeyFn(queryKey, method)
171
+ : mutationKeyFn(queryKey))),
172
+ ], ts.NodeFlags.Const));
173
+ }
174
+ function queryKeyFn(queryKey, method) {
175
+ return ts.factory.createArrayLiteralExpression([
176
+ ts.factory.createIdentifier(queryKey),
177
+ ts.factory.createSpreadElement(ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(ts.factory.createIdentifier("queryKey"), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), getVariableArrowFunctionParameters(method)
178
+ ? // [...clientOptions]
179
+ ts.factory.createArrayLiteralExpression([
180
+ ts.factory.createIdentifier("clientOptions"),
181
+ ])
182
+ : // []
183
+ ts.factory.createArrayLiteralExpression()))),
184
+ ], false);
185
+ }
186
+ function mutationKeyFn(mutationKey) {
187
+ return ts.factory.createArrayLiteralExpression([
188
+ ts.factory.createIdentifier(mutationKey),
189
+ ts.factory.createSpreadElement(ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(ts.factory.createIdentifier("mutationKey"), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), ts.factory.createArrayLiteralExpression()))),
190
+ ], false);
191
+ }
192
+ export function getRequestParamFromMethod(method, pageParam, modelNames = []) {
193
+ if (!getVariableArrowFunctionParameters(method).length) {
194
+ return null;
195
+ }
196
+ const methodName = getNameFromVariable(method);
197
+ const params = getVariableArrowFunctionParameters(method).flatMap((param) => {
198
+ const paramNodes = extractPropertiesFromObjectParam(param);
199
+ return paramNodes
200
+ .filter((p) => p.name !== pageParam)
201
+ .map((refParam) => ({
202
+ name: refParam.name,
203
+ // TODO: Client<Request, Response, unknown, RequestOptions> -> Client<Request, Response, unknown>
204
+ typeName: getShortType(refParam.type?.getText() ?? ""),
205
+ optional: refParam.optional,
206
+ }));
207
+ });
208
+ const areAllPropertiesOptional = params.every((param) => param.optional);
209
+ return ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("clientOptions"), undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Options"), [
210
+ ts.factory.createTypeReferenceNode(modelNames.includes(`${capitalizeFirstLetter(methodName)}Data`)
211
+ ? `${capitalizeFirstLetter(methodName)}Data`
212
+ : "unknown"),
213
+ ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("true")),
214
+ ]),
215
+ // if all params are optional, we create an empty object literal
216
+ // so the hook can be called without any parameters
217
+ areAllPropertiesOptional
218
+ ? ts.factory.createObjectLiteralExpression()
219
+ : undefined);
220
+ }
@@ -2,7 +2,7 @@ export const defaultOutputPath = "openapi";
2
2
  export const queriesOutputPath = "queries";
3
3
  export const requestsOutputPath = "requests";
4
4
  export const serviceFileName = "services.gen";
5
- export const modalsFileName = "types.gen";
5
+ export const modelsFileName = "types.gen";
6
6
  export const OpenApiRqFiles = {
7
7
  queries: "queries",
8
8
  infiniteQueries: "infiniteQueries",
@@ -10,4 +10,5 @@ export const OpenApiRqFiles = {
10
10
  suspense: "suspense",
11
11
  index: "index",
12
12
  prefetch: "prefetch",
13
+ ensureQueryData: "ensureQueryData",
13
14
  };
@@ -1,20 +1,61 @@
1
- import { createPrefetch } from "./createPrefetch.mjs";
1
+ import ts from "typescript";
2
+ import { capitalizeFirstLetter } from "./common.mjs";
3
+ import { modelsFileName } from "./constants.mjs";
4
+ import { createPrefetchOrEnsure } from "./createPrefetchOrEnsure.mjs";
2
5
  import { createUseMutation } from "./createUseMutation.mjs";
3
6
  import { createUseQuery } from "./createUseQuery.mjs";
4
- export const createExports = (service, pageParam, nextPageParam) => {
5
- const { klasses } = service;
6
- const methods = klasses.flatMap((k) => k.methods);
7
+ export const createExports = ({ service, client, project, pageParam, nextPageParam, initialPageParam, }) => {
8
+ const { methods } = service;
9
+ const methodDataNames = methods.reduce((acc, data) => {
10
+ const methodName = data.method.getName();
11
+ acc[`${capitalizeFirstLetter(methodName)}Data`] = methodName;
12
+ return acc;
13
+ }, {});
14
+ const modelsFile = project
15
+ .getSourceFiles?.()
16
+ .find((sourceFile) => sourceFile.getFilePath().includes(modelsFileName));
17
+ const modelDeclarations = modelsFile?.getExportedDeclarations();
18
+ const entries = modelDeclarations?.entries();
19
+ const modelNames = [];
20
+ const paginatableMethods = [];
21
+ for (const [key, value] of entries ?? []) {
22
+ modelNames.push(key);
23
+ const node = value[0].compilerNode;
24
+ if (ts.isTypeAliasDeclaration(node) && methodDataNames[key] !== undefined) {
25
+ // get the type alias declaration
26
+ const typeAliasDeclaration = node.type;
27
+ if (typeAliasDeclaration.kind === ts.SyntaxKind.TypeLiteral) {
28
+ const query = typeAliasDeclaration.members.find((m) => m.kind === ts.SyntaxKind.PropertySignature &&
29
+ m.name?.getText() === "query");
30
+ if (query &&
31
+ query.type.members
32
+ .map((m) => m.name?.getText())
33
+ .includes(pageParam)) {
34
+ paginatableMethods.push(methodDataNames[key]);
35
+ }
36
+ }
37
+ }
38
+ }
7
39
  const allGet = methods.filter((m) => m.httpMethodName.toUpperCase().includes("GET"));
8
40
  const allPost = methods.filter((m) => m.httpMethodName.toUpperCase().includes("POST"));
9
41
  const allPut = methods.filter((m) => m.httpMethodName.toUpperCase().includes("PUT"));
10
42
  const allPatch = methods.filter((m) => m.httpMethodName.toUpperCase().includes("PATCH"));
11
43
  const allDelete = methods.filter((m) => m.httpMethodName.toUpperCase().includes("DELETE"));
12
- const allGetQueries = allGet.map((m) => createUseQuery(m, pageParam, nextPageParam));
13
- const allPrefetchQueries = allGet.map((m) => createPrefetch(m));
14
- const allPostMutations = allPost.map((m) => createUseMutation(m));
15
- const allPutMutations = allPut.map((m) => createUseMutation(m));
16
- const allPatchMutations = allPatch.map((m) => createUseMutation(m));
17
- const allDeleteMutations = allDelete.map((m) => createUseMutation(m));
44
+ const allGetQueries = allGet.map((m) => createUseQuery({
45
+ functionDescription: m,
46
+ client,
47
+ pageParam,
48
+ nextPageParam,
49
+ initialPageParam,
50
+ paginatableMethods,
51
+ modelNames,
52
+ }));
53
+ const allPrefetchQueries = allGet.map((m) => createPrefetchOrEnsure({ ...m, functionType: "prefetch", modelNames }));
54
+ const allEnsureQueries = allGet.map((m) => createPrefetchOrEnsure({ ...m, functionType: "ensure", modelNames }));
55
+ const allPostMutations = allPost.map((m) => createUseMutation({ functionDescription: m, modelNames, client }));
56
+ const allPutMutations = allPut.map((m) => createUseMutation({ functionDescription: m, modelNames, client }));
57
+ const allPatchMutations = allPatch.map((m) => createUseMutation({ functionDescription: m, modelNames, client }));
58
+ const allDeleteMutations = allDelete.map((m) => createUseMutation({ functionDescription: m, modelNames, client }));
18
59
  const allQueries = [...allGetQueries];
19
60
  const allMutations = [
20
61
  ...allPostMutations,
@@ -28,8 +69,10 @@ export const createExports = (service, pageParam, nextPageParam) => {
28
69
  key,
29
70
  queryKeyFn,
30
71
  ]);
31
- const commonInMutations = allMutations.flatMap(({ mutationResult }) => [
72
+ const commonInMutations = allMutations.flatMap(({ mutationResult, key, mutationKeyFn }) => [
32
73
  mutationResult,
74
+ key,
75
+ mutationKeyFn,
33
76
  ]);
34
77
  const allCommon = [...commonInQueries, ...commonInMutations];
35
78
  const mainQueries = allQueries.flatMap(({ queryHook }) => [queryHook]);
@@ -44,9 +87,8 @@ export const createExports = (service, pageParam, nextPageParam) => {
44
87
  suspenseQueryHook,
45
88
  ]);
46
89
  const suspenseExports = [...suspenseQueries];
47
- const allPrefetches = allPrefetchQueries.flatMap(({ prefetchHook }) => [
48
- prefetchHook,
49
- ]);
90
+ const allPrefetches = allPrefetchQueries.flatMap(({ hook }) => [hook]);
91
+ const allEnsures = allEnsureQueries.flatMap(({ hook }) => [hook]);
50
92
  const allPrefetchExports = [...allPrefetches];
51
93
  return {
52
94
  /**
@@ -69,5 +111,9 @@ export const createExports = (service, pageParam, nextPageParam) => {
69
111
  * Prefetch exports are the hooks that are used in the prefetch components
70
112
  */
71
113
  allPrefetchExports,
114
+ /**
115
+ * Ensure exports are the hooks that are used in the loader components
116
+ */
117
+ allEnsures,
72
118
  };
73
119
  };