@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 +21 -0
- package/README.md +2 -362
- package/dist/cli.mjs +4 -9
- package/dist/common.mjs +93 -10
- package/dist/constants.mjs +2 -1
- package/dist/createExports.mjs +60 -14
- package/dist/createImports.mjs +10 -5
- package/dist/createPrefetchOrEnsure.mjs +56 -0
- package/dist/createSource.mjs +27 -5
- package/dist/createUseMutation.mjs +47 -32
- package/dist/createUseQuery.mjs +66 -110
- package/dist/format.mjs +46 -0
- package/dist/generate.mjs +11 -6
- package/dist/service.mjs +63 -63
- package/package.json +37 -27
- package/dist/createPrefetch.mjs +0 -53
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
|
-
>
|
|
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
|
[](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(["
|
|
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("--
|
|
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", "
|
|
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
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
23
|
-
throw new Error("
|
|
21
|
+
export const getVariableArrowFunctionParameters = (variable) => {
|
|
22
|
+
const initializer = variable.getInitializer();
|
|
23
|
+
if (!initializer) {
|
|
24
|
+
throw new Error("Initializer not found");
|
|
24
25
|
}
|
|
25
|
-
|
|
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
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
}
|
package/dist/constants.mjs
CHANGED
|
@@ -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
|
|
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
|
};
|
package/dist/createExports.mjs
CHANGED
|
@@ -1,20 +1,61 @@
|
|
|
1
|
-
import
|
|
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 {
|
|
6
|
-
const
|
|
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(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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(({
|
|
48
|
-
|
|
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
|
};
|