@7nohe/openapi-react-query-codegen 1.6.1 → 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,372 +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
- --initialPageParam <value> Initial value for the pagination parameter (default: "1")
64
- -h, --help display help for command
65
- ```
66
-
67
- ### Example Usage
68
-
69
- #### Command
70
-
71
- ```
72
- $ openapi-rq -i ./petstore.yaml
73
- ```
74
-
75
- #### Output directory structure
76
-
77
- ```
78
- - openapi
79
- - queries
80
- - index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
81
- - common.ts <- common types
82
- - queries.ts <- generated query hooks
83
- - suspenses.ts <- generated suspense hooks
84
- - prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
85
- - requests <- output code generated by @hey-api/openapi-ts
86
- ```
87
-
88
- - [Prefetching docs](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#prefetching-and-dehydrating-data)
89
-
90
- #### In your app
91
-
92
- ##### Using the generated hooks
93
-
94
- ```tsx
95
- // App.tsx
96
- import { usePetServiceFindPetsByStatus } from "../openapi/queries";
97
- function App() {
98
- const { data } = usePetServiceFindPetsByStatus({ status: ["available"] });
99
-
100
- return (
101
- <div className="App">
102
- <h1>Pet List</h1>
103
- <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
104
- </div>
105
- );
106
- }
107
-
108
- export default App;
109
- ```
110
-
111
- ##### Using the generated typescript client
112
-
113
- ```tsx
114
- import { useQuery } from "@tanstack/react-query";
115
- import { PetService } from "../openapi/requests/services";
116
- import { usePetServiceFindPetsByStatusKey } from "../openapi/queries";
117
-
118
- function App() {
119
- // You can still use the auto-generated query key
120
- const { data } = useQuery({
121
- queryKey: [usePetServiceFindPetsByStatusKey],
122
- queryFn: () => {
123
- // Do something here
124
- return PetService.findPetsByStatus(["available"]);
125
- },
126
- });
127
-
128
- return <div className="App">{/* .... */}</div>;
129
- }
130
-
131
- export default App;
132
- ```
133
-
134
- ##### Using Suspense Hooks
135
-
136
- ```tsx
137
- // App.tsx
138
- import { useDefaultClientFindPetsSuspense } from "../openapi/queries/suspense";
139
- function ChildComponent() {
140
- const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });
141
-
142
- return <ul>{data?.map((pet, index) => <li key={pet.id}>{pet.name}</li>)}</ul>;
143
- }
144
-
145
- function ParentComponent() {
146
- return (
147
- <>
148
- <Suspense fallback={<>loading...</>}>
149
- <ChildComponent />
150
- </Suspense>
151
- </>
152
- );
153
- }
154
-
155
- function App() {
156
- return (
157
- <div className="App">
158
- <h1>Pet List</h1>
159
- <ParentComponent />
160
- </div>
161
- );
162
- }
163
-
164
- export default App;
165
- ```
166
-
167
- ##### Using Mutation hooks
168
-
169
- ```tsx
170
- // App.tsx
171
- import { usePetServiceAddPet } from "../openapi/queries";
172
-
173
- function App() {
174
- const { mutate } = usePetServiceAddPet();
175
-
176
- const handleAddPet = () => {
177
- mutate({ name: "Fluffy", status: "available" });
178
- };
179
-
180
- return (
181
- <div className="App">
182
- <h1>Add Pet</h1>
183
- <button onClick={handleAddPet}>Add Pet</button>
184
- </div>
185
- );
186
- }
187
-
188
- export default App;
189
- ```
190
-
191
- ##### Invalidating queries after mutation
192
-
193
- 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.
194
-
195
- Learn more about invalidating queries [here](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation).
196
-
197
- 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.
198
-
199
- ```tsx
200
- import {
201
- usePetServiceFindPetsByStatus,
202
- usePetServiceAddPet,
203
- UsePetServiceFindPetsByStatusKeyFn,
204
- } from "../openapi/queries";
205
-
206
- // App.tsx
207
- function App() {
208
- const [status, setStatus] = React.useState(["available"]);
209
- const { data } = usePetServiceFindPetsByStatus({ status });
210
- const { mutate } = usePetServiceAddPet({
211
- onSuccess: () => {
212
- queryClient.invalidateQueries({
213
- // Call the query key function to get the query key
214
- // This is important to ensure the query key is created the same way as the query hook
215
- // This insures the cache is invalidated correctly and is typed correctly
216
- queryKey: [UsePetServiceFindPetsByStatusKeyFn({
217
- status
218
- })],
219
- });
220
- },
221
- });
222
-
223
- return (
224
- <div className="App">
225
- <h1>Pet List</h1>
226
- <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
227
- <button
228
- onClick={() => {
229
- mutate({ name: "Fluffy", status: "available" });
230
- }}
231
- >
232
- Add Pet
233
- </button>
234
- </div>
235
- );
236
- }
237
-
238
- export default App;
239
- ```
240
-
241
- ##### Using Infinite Query hooks
242
-
243
- 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.
244
-
245
- The `initialPageParam` option can be specified to set the intial page to load, defaults to 1. The `nextPageParam` supports dot notation for nested values (i.e. `meta.next`).
246
-
247
- Example Schema:
248
-
249
- ```yml
250
- paths:
251
- /paginated-pets:
252
- get:
253
- description: |
254
- Returns paginated pets from the system that the user has access to
255
- operationId: findPaginatedPets
256
- parameters:
257
- - name: page
258
- in: query
259
- description: page number
260
- required: false
261
- schema:
262
- type: integer
263
- format: int32
264
- - name: tags
265
- in: query
266
- description: tags to filter by
267
- required: false
268
- style: form
269
- schema:
270
- type: array
271
- items:
272
- type: string
273
- - name: limit
274
- in: query
275
- description: maximum number of results to return
276
- required: false
277
- schema:
278
- type: integer
279
- format: int32
280
- responses:
281
- '200':
282
- description: pet response
283
- content:
284
- application/json:
285
- schema:
286
- type: object
287
- properties:
288
- pets:
289
- type: array
290
- items:
291
- $ref: '#/components/schemas/Pet'
292
- nextPage:
293
- type: integer
294
- format: int32
295
- minimum: 1
296
- ```
297
-
298
- Usage of Generated Hooks:
299
-
300
- ```ts
301
- import { useDefaultServiceFindPaginatedPetsInfinite } from "@/openapi/queries/infiniteQueries";
302
-
303
- const { data, fetchNextPage } = useDefaultServiceFindPaginatedPetsInfinite({
304
- limit: 10,
305
- tags: [],
306
- });
307
- ```
308
-
309
- ##### Runtime Configuration
310
-
311
- You can modify the default values used by the generated service calls by modifying the OpenAPI configuration singleton object.
312
-
313
- It's default location is `openapi/requests/core/OpenAPI.ts` and it is also exported from `openapi/index.ts`
314
-
315
- Import the constant into your runtime and modify it before setting up the react app.
316
-
317
- ```typescript
318
- /** main.tsx */
319
- import { OpenAPI as OpenAPIConfig } from './openapi/requests/core/OpenAPI';
320
- ...
321
- OpenAPIConfig.BASE = 'www.domain.com/api';
322
- OpenAPIConfig.HEADERS = {
323
- 'x-header-1': 'value-1',
324
- 'x-header-2': 'value-2',
325
- };
326
- ...
327
- ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
328
- <React.StrictMode>
329
- <QueryClientProvider client={queryClient}>
330
- <App />
331
- </QueryClientProvider>
332
- </React.StrictMode>
333
- );
334
-
335
- ```
336
-
337
- ## Development
338
-
339
- ### Install dependencies
340
-
341
- ```bash
342
- pnpm install
343
- ```
344
-
345
- ### Run tests
346
- ```bash
347
- pnpm test
348
- ```
349
-
350
- ### Run linter
351
- ```bash
352
- pnpm lint
353
- ```
354
-
355
- ### Run linter and fix
356
- ```bash
357
- pnpm lint:fix
358
- ```
359
-
360
- ### Update snapshots
361
- ```bash
362
- pnpm snapshot
363
- ```
364
-
365
- ### Build example and validate generated code
366
- ```bash
367
- npm run build && pnpm --filter @7nohe/react-app generate:api && pnpm --filter @7nohe/react-app test:generated
368
- ```
369
-
370
- ## License
371
-
372
- MIT
package/dist/cli.mjs CHANGED
@@ -19,24 +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")
39
- .option("--initialPageParam <value>", "Initial page value to query", "initialPageParam")
34
+ .option("--initialPageParam <value>", "Initial page value to query", "1")
40
35
  .parse();
41
36
  const options = program.opts();
42
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, initialPageParam) => {
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, initialPageParam));
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, initialPagePara
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, initialPagePara
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, initialPagePara
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
  };