@7nohe/openapi-react-query-codegen 1.2.0 → 1.2.2

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/README.md CHANGED
@@ -2,13 +2,15 @@
2
2
 
3
3
  > Node.js library that generates [React Query (also called TanStack Query)](https://tanstack.com/query) hooks based on an OpenAPI specification file.
4
4
 
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
+
5
7
  ## Features
6
8
 
7
- - Supports generation of custom react hooks that use React Query's `useQuery` and `useMutation` hooks
8
- - Supports generation of query keys for query caching
9
- - Supports the option to use pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts)
9
+ - Generates custom react hooks that use React Query's `useQuery`, `useSuspenseQuery` and `useMutation` hooks
10
+ - Generates query keys and functions for query caching
11
+ - Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts)
10
12
 
11
- ## Install
13
+ ## Installation
12
14
 
13
15
  ```
14
16
  $ npm install -D @7nohe/openapi-react-query-codegen
@@ -48,7 +50,7 @@ Options:
48
50
  --format <value> Process output folder with formatter? ['biome', 'prettier']
49
51
  --lint <value> Process output folder with linter? ['eslint', 'biome']
50
52
  --operationId Use operation ID to generate operation names?
51
- --serviceResponse <value> Define shape of returned value from service calls ['body', 'generics', 'response']
53
+ --serviceResponse <value> Define shape of returned value from service calls ['body', 'response'] (default: "body")
52
54
  --base <value> Manually set base in OpenAPI config instead of inferring from server value
53
55
  --enums <value> Generate JavaScript objects from enum definitions? ['javascript', 'typescript']
54
56
  --useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
@@ -58,27 +60,32 @@ Options:
58
60
  -h, --help display help for command
59
61
  ```
60
62
 
61
- ## Example Usage
63
+ ### Example Usage
62
64
 
63
- ### Command
65
+ #### Command
64
66
 
65
67
  ```
66
68
  $ openapi-rq -i ./petstore.yaml
67
69
  ```
68
70
 
69
- ### Output directory structure
71
+ #### Output directory structure
70
72
 
71
73
  ```
72
74
  - openapi
73
75
  - queries
74
- - index.ts <- main file that exports common types, variables, and hooks
76
+ - index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
75
77
  - common.ts <- common types
76
78
  - queries.ts <- generated query hooks
77
79
  - suspenses.ts <- generated suspense hooks
80
+ - prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
78
81
  - requests <- output code generated by @hey-api/openapi-ts
79
82
  ```
80
83
 
81
- ### In your app
84
+ - [Prefetching docs](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#prefetching-and-dehydrating-data)
85
+
86
+ #### In your app
87
+
88
+ ##### Using the generated hooks
82
89
 
83
90
  ```tsx
84
91
  // App.tsx
@@ -97,7 +104,7 @@ function App() {
97
104
  export default App;
98
105
  ```
99
106
 
100
- You can also use pure TS clients.
107
+ ##### Using the generated typescript client
101
108
 
102
109
  ```tsx
103
110
  import { useQuery } from "@tanstack/react-query";
@@ -120,7 +127,7 @@ function App() {
120
127
  export default App;
121
128
  ```
122
129
 
123
- You can also use suspense hooks.
130
+ ##### Using Suspense Hooks
124
131
 
125
132
  ```tsx
126
133
  // App.tsx
@@ -153,6 +160,103 @@ function App() {
153
160
  export default App;
154
161
  ```
155
162
 
163
+ ##### Using Mutation hooks
164
+
165
+ ```tsx
166
+ // App.tsx
167
+ import { usePetServiceAddPet } from "../openapi/queries";
168
+
169
+ function App() {
170
+ const { mutate } = usePetServiceAddPet();
171
+
172
+ const handleAddPet = () => {
173
+ mutate({ name: "Fluffy", status: "available" });
174
+ };
175
+
176
+ return (
177
+ <div className="App">
178
+ <h1>Add Pet</h1>
179
+ <button onClick={handleAddPet}>Add Pet</button>
180
+ </div>
181
+ );
182
+ }
183
+
184
+ export default App;
185
+ ```
186
+
187
+ ##### Invalidating queries after mutation
188
+
189
+ 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.
190
+
191
+ Learn more about invalidating queries [here](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation).
192
+
193
+ 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.
194
+
195
+ ```tsx
196
+ import {
197
+ usePetServiceFindPetsByStatus,
198
+ usePetServiceAddPet,
199
+ UsePetServiceFindPetsByStatusKeyFn,
200
+ } from "../openapi/queries";
201
+
202
+ // App.tsx
203
+ function App() {
204
+ const { data } = usePetServiceFindPetsByStatus({ status: ["available"] });
205
+ const { mutate } = usePetServiceAddPet({
206
+ onSuccess: () => {
207
+ queryClient.invalidateQueries({
208
+ // Call the query key function to get the query key, this is important to ensure the query key is created the same way as the query hook, this insures the cache is invalidated correctly and is typed correctly
209
+ queryKey: [UsePetServiceFindPetsByStatusKeyFn()],
210
+ });
211
+ },
212
+ });
213
+
214
+ return (
215
+ <div className="App">
216
+ <h1>Pet List</h1>
217
+ <ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
218
+ <button
219
+ onClick={() => {
220
+ mutate({ name: "Fluffy", status: "available" });
221
+ }}
222
+ >
223
+ Add Pet
224
+ </button>
225
+ </div>
226
+ );
227
+ }
228
+
229
+ export default App;
230
+ ```
231
+
232
+ ##### Runtime Configuration
233
+
234
+ You can modify the default values used by the generated service calls by modifying the OpenAPI configuration singleton object.
235
+
236
+ It's default location is `openapi/requests/core/OpenAPI.ts` and it is also exported from `openapi/index.ts`
237
+
238
+ Import the constant into your runtime and modify it before setting up the react app.
239
+
240
+ ```typescript
241
+ /** main.tsx */
242
+ import { OpenAPI as OpenAPIConfig } from './openapi/requests/core/OpenAPI';
243
+ ...
244
+ OpenAPIConfig.BASE = 'www.domain.com/api';
245
+ OpenAPIConfig.HEADERS = {
246
+ 'x-header-1': 'value-1',
247
+ 'x-header-2': 'value-2',
248
+ };
249
+ ...
250
+ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
251
+ <React.StrictMode>
252
+ <QueryClientProvider client={queryClient}>
253
+ <App />
254
+ </QueryClientProvider>
255
+ </React.StrictMode>
256
+ );
257
+
258
+ ```
259
+
156
260
  ## License
157
261
 
158
262
  MIT
package/dist/cli.mjs CHANGED
@@ -25,7 +25,9 @@ async function setupProgram() {
25
25
  .addOption(new Option("--format <value>", "Process output folder with formatter?").choices(["biome", "prettier"]))
26
26
  .addOption(new Option("--lint <value>", "Process output folder with linter?").choices(["biome", "eslint"]))
27
27
  .option("--operationId", "Use operation ID to generate operation names?")
28
- .addOption(new Option("--serviceResponse <value>", "Define shape of returned value from service calls").choices(["body", "response"]))
28
+ .addOption(new Option("--serviceResponse <value>", "Define shape of returned value from service calls")
29
+ .choices(["body", "response"])
30
+ .default("body"))
29
31
  .option("--base <value>", "Manually set base in OpenAPI config instead of inferring from server value")
30
32
  .addOption(new Option("--enums <value>", "Generate JavaScript objects from enum definitions?").choices(["javascript", "typescript"]))
31
33
  .option("--useDateType", "Use Date type instead of string for date types for models, this will not convert the data to a Date object")
package/dist/common.mjs CHANGED
@@ -5,6 +5,8 @@ import { queriesOutputPath, requestsOutputPath } from "./constants.mjs";
5
5
  export const TData = ts.factory.createIdentifier("TData");
6
6
  export const TError = ts.factory.createIdentifier("TError");
7
7
  export const TContext = ts.factory.createIdentifier("TContext");
8
+ export const EqualsOrGreaterThanToken = ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken);
9
+ export const QuestionToken = ts.factory.createToken(ts.SyntaxKind.QuestionToken);
8
10
  export const queryKeyGenericType = ts.factory.createTypeReferenceNode("TQueryKey");
9
11
  export const queryKeyConstraint = ts.factory.createTypeReferenceNode("Array", [
10
12
  ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
@@ -3,3 +3,10 @@ export const queriesOutputPath = "queries";
3
3
  export const requestsOutputPath = "requests";
4
4
  export const serviceFileName = "services.gen";
5
5
  export const modalsFileName = "types.gen";
6
+ export const OpenApiRqFiles = {
7
+ queries: "queries",
8
+ common: "common",
9
+ suspense: "suspense",
10
+ index: "index",
11
+ prefetch: "prefetch",
12
+ };
@@ -1,5 +1,6 @@
1
1
  import { createUseQuery } from "./createUseQuery.mjs";
2
2
  import { createUseMutation } from "./createUseMutation.mjs";
3
+ import { createPrefetch } from "./createPrefetch.mjs";
3
4
  export const createExports = (service) => {
4
5
  const { klasses } = service;
5
6
  const methods = klasses.map((k) => k.methods).flat();
@@ -9,6 +10,7 @@ export const createExports = (service) => {
9
10
  const allPatch = methods.filter((m) => m.httpMethodName.toUpperCase().includes("PATCH"));
10
11
  const allDelete = methods.filter((m) => m.httpMethodName.toUpperCase().includes("DELETE"));
11
12
  const allGetQueries = allGet.map((m) => createUseQuery(m));
13
+ const allPrefetchQueries = allGet.map((m) => createPrefetch(m));
12
14
  const allPostMutations = allPost.map((m) => createUseMutation(m));
13
15
  const allPutMutations = allPut.map((m) => createUseMutation(m));
14
16
  const allPatchMutations = allPatch.map((m) => createUseMutation(m));
@@ -21,7 +23,12 @@ export const createExports = (service) => {
21
23
  ...allDeleteMutations,
22
24
  ];
23
25
  const commonInQueries = allQueries
24
- .map(({ apiResponse, returnType, key }) => [apiResponse, returnType, key])
26
+ .map(({ apiResponse, returnType, key, queryKeyFn }) => [
27
+ apiResponse,
28
+ returnType,
29
+ key,
30
+ queryKeyFn,
31
+ ])
25
32
  .flat();
26
33
  const commonInMutations = allMutations
27
34
  .map(({ mutationResult }) => [mutationResult])
@@ -36,6 +43,10 @@ export const createExports = (service) => {
36
43
  .map(({ suspenseQueryHook }) => [suspenseQueryHook])
37
44
  .flat();
38
45
  const suspenseExports = [...suspenseQueries];
46
+ const allPrefetches = allPrefetchQueries
47
+ .map(({ prefetchHook }) => [prefetchHook])
48
+ .flat();
49
+ const allPrefetchExports = [...allPrefetches];
39
50
  return {
40
51
  /**
41
52
  * Common types and variables between queries (regular and suspense) and mutations
@@ -49,5 +60,9 @@ export const createExports = (service) => {
49
60
  * Suspense exports are the hooks that are used in the suspense components
50
61
  */
51
62
  suspenseExports,
63
+ /**
64
+ * Prefetch exports are the hooks that are used in the prefetch components
65
+ */
66
+ allPrefetchExports,
52
67
  };
53
68
  };
@@ -0,0 +1,58 @@
1
+ import ts from "typescript";
2
+ import { BuildCommonTypeName, extractPropertiesFromObjectParam, getNameFromMethod, } from "./common.mjs";
3
+ import { createQueryKeyFromMethod, getRequestParamFromMethod, hookNameFromMethod, } from "./createUseQuery.mjs";
4
+ import { addJSDocToNode } from "./util.mjs";
5
+ /**
6
+ * Creates a prefetch function for a query
7
+ */
8
+ function createPrefetchHook({ requestParams, method, className, }) {
9
+ const methodName = getNameFromMethod(method);
10
+ const queryName = hookNameFromMethod({ method, className });
11
+ const customHookName = `prefetch${queryName.charAt(0).toUpperCase() + queryName.slice(1)}`;
12
+ const queryKey = createQueryKeyFromMethod({ method, className });
13
+ // const
14
+ const hookExport = ts.factory.createVariableStatement(
15
+ // export
16
+ [ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
17
+ ts.factory.createVariableDeclaration(ts.factory.createIdentifier(customHookName), undefined, undefined, ts.factory.createArrowFunction(undefined, undefined, [
18
+ ts.factory.createParameterDeclaration(undefined, undefined, "queryClient", undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("QueryClient"))),
19
+ ...requestParams,
20
+ ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createIdentifier("queryClient.prefetchQuery"), undefined, [
21
+ ts.factory.createObjectLiteralExpression([
22
+ ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryKey"), ts.factory.createArrayLiteralExpression([
23
+ BuildCommonTypeName(queryKey),
24
+ method.getParameters().length
25
+ ? ts.factory.createArrayLiteralExpression([
26
+ ts.factory.createObjectLiteralExpression(method
27
+ .getParameters()
28
+ .map((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))
29
+ .flat()),
30
+ ])
31
+ : ts.factory.createArrayLiteralExpression([]),
32
+ ], false)),
33
+ ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryFn"), ts.factory.createArrowFunction(undefined, undefined, [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined, method.getParameters().length
34
+ ? [
35
+ ts.factory.createObjectLiteralExpression(method
36
+ .getParameters()
37
+ .map((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))
38
+ .flat()),
39
+ ]
40
+ : undefined))),
41
+ ]),
42
+ ]))),
43
+ ], ts.NodeFlags.Const));
44
+ return hookExport;
45
+ }
46
+ export const createPrefetch = ({ className, method, jsDoc, }) => {
47
+ const requestParam = getRequestParamFromMethod(method);
48
+ const requestParams = requestParam ? [requestParam] : [];
49
+ const prefetchHook = createPrefetchHook({
50
+ requestParams,
51
+ method,
52
+ className,
53
+ });
54
+ const hookWithJsDoc = addJSDocToNode(prefetchHook, jsDoc);
55
+ return {
56
+ prefetchHook: hookWithJsDoc,
57
+ };
58
+ };
@@ -1,9 +1,10 @@
1
1
  import ts from "typescript";
2
+ import { Project } from "ts-morph";
3
+ import { join } from "path";
4
+ import { OpenApiRqFiles } from "./constants.mjs";
2
5
  import { createImports } from "./createImports.mjs";
3
6
  import { createExports } from "./createExports.mjs";
4
7
  import { getServices } from "./service.mjs";
5
- import { Project } from "ts-morph";
6
- import { join } from "path";
7
8
  const createSourceFile = async (outputPath, serviceEndName) => {
8
9
  const project = new Project({
9
10
  // Optionally specify compiler options, tsconfig.json, in-memory file system, and more here.
@@ -21,29 +22,32 @@ const createSourceFile = async (outputPath, serviceEndName) => {
21
22
  });
22
23
  const exports = createExports(service);
23
24
  const commonSource = ts.factory.createSourceFile([...imports, ...exports.allCommon], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
24
- const commonImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier("* as Common"), undefined), ts.factory.createStringLiteral("./common"), undefined);
25
- const commonExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral("./common"), undefined);
26
- const queriesExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral("./queries"), undefined);
25
+ const commonImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier("* as Common"), undefined), ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
26
+ const commonExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
27
+ const queriesExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.queries}`), undefined);
27
28
  const mainSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.mainExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
28
29
  const suspenseSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.suspenseExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
29
30
  const indexSource = ts.factory.createSourceFile([commonExport, queriesExport], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
31
+ const prefetchSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.allPrefetchExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
30
32
  return {
31
33
  commonSource,
32
34
  mainSource,
33
35
  suspenseSource,
34
36
  indexSource,
37
+ prefetchSource,
35
38
  };
36
39
  };
37
40
  export const createSource = async ({ outputPath, version, serviceEndName, }) => {
38
- const queriesFile = ts.createSourceFile("queries.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
39
- const commonFile = ts.createSourceFile("common.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
40
- const suspenseFile = ts.createSourceFile("suspense.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
41
- const indexFile = ts.createSourceFile("index.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
41
+ const queriesFile = ts.createSourceFile(`${OpenApiRqFiles.queries}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
42
+ const commonFile = ts.createSourceFile(`${OpenApiRqFiles.common}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
43
+ const suspenseFile = ts.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
44
+ const indexFile = ts.createSourceFile(`${OpenApiRqFiles.index}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
45
+ const prefetchFile = ts.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
42
46
  const printer = ts.createPrinter({
43
47
  newLine: ts.NewLineKind.LineFeed,
44
48
  removeComments: false,
45
49
  });
46
- const { commonSource, mainSource, suspenseSource, indexSource } = await createSourceFile(outputPath, serviceEndName);
50
+ const { commonSource, mainSource, suspenseSource, indexSource, prefetchSource, } = await createSourceFile(outputPath, serviceEndName);
47
51
  const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
48
52
  const commonResult = comment +
49
53
  printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
@@ -53,22 +57,28 @@ export const createSource = async ({ outputPath, version, serviceEndName, }) =>
53
57
  printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
54
58
  const indexResult = comment +
55
59
  printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
60
+ const prefetchResult = comment +
61
+ printer.printNode(ts.EmitHint.Unspecified, prefetchSource, prefetchFile);
56
62
  return [
57
63
  {
58
- name: "index.ts",
64
+ name: `${OpenApiRqFiles.index}.ts`,
59
65
  content: indexResult,
60
66
  },
61
67
  {
62
- name: "common.ts",
68
+ name: `${OpenApiRqFiles.common}.ts`,
63
69
  content: commonResult,
64
70
  },
65
71
  {
66
- name: "queries.ts",
72
+ name: `${OpenApiRqFiles.queries}.ts`,
67
73
  content: mainResult,
68
74
  },
69
75
  {
70
- name: "suspense.ts",
76
+ name: `${OpenApiRqFiles.suspense}.ts`,
71
77
  content: suspenseResult,
72
78
  },
79
+ {
80
+ name: `${OpenApiRqFiles.prefetch}.ts`,
81
+ content: prefetchResult,
82
+ },
73
83
  ];
74
84
  };
@@ -1,5 +1,5 @@
1
1
  import ts from "typescript";
2
- import { BuildCommonTypeName, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, getShortType, queryKeyConstraint, queryKeyGenericType, TData, TError, } from "./common.mjs";
2
+ import { BuildCommonTypeName, capitalizeFirstLetter, EqualsOrGreaterThanToken, extractPropertiesFromObjectParam, getNameFromMethod, getShortType, queryKeyConstraint, queryKeyGenericType, QuestionToken, TData, TError, } from "./common.mjs";
3
3
  import { addJSDocToNode } from "./util.mjs";
4
4
  export const createApiResponseType = ({ className, methodName, }) => {
5
5
  /** Awaited<ReturnType<typeof myClass.myMethod>> */
@@ -14,12 +14,15 @@ export const createApiResponseType = ({ className, methodName, }) => {
14
14
  const apiResponse = ts.factory.createTypeAliasDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(`${capitalizeFirstLetter(className)}${capitalizeFirstLetter(methodName)}DefaultResponse`), undefined, awaitedResponseDataType);
15
15
  const responseDataType = ts.factory.createTypeParameterDeclaration(undefined, TData.text, undefined, ts.factory.createTypeReferenceNode(BuildCommonTypeName(apiResponse.name)));
16
16
  return {
17
- /** DefaultResponseDataType
17
+ /**
18
+ * DefaultResponseDataType
19
+ *
18
20
  * export type MyClassMethodDefaultResponse = Awaited<ReturnType<typeof myClass.myMethod>>
19
21
  */
20
22
  apiResponse,
21
23
  /**
22
- * will be the name of the type of the response type of the method
24
+ * This will be the name of the type of the response type of the method
25
+ *
23
26
  * MyClassMethodDefaultResponse
24
27
  */
25
28
  responseDataType,
@@ -54,6 +57,7 @@ export function getRequestParamFromMethod(method) {
54
57
  }
55
58
  /**
56
59
  * Return Type
60
+ *
57
61
  * export const classNameMethodNameQueryResult<TData = MyClassMethodDefaultResponse, TError = unknown> = UseQueryResult<TData, TError>;
58
62
  */
59
63
  export function createReturnTypeExport({ className, methodName, defaultApiResponse, }) {
@@ -73,11 +77,11 @@ export function createQueryKeyExport({ className, methodName, queryKey, }) {
73
77
  ts.factory.createVariableDeclaration(ts.factory.createIdentifier(queryKey), undefined, undefined, ts.factory.createStringLiteral(`${className}${capitalizeFirstLetter(methodName)}`)),
74
78
  ], ts.NodeFlags.Const));
75
79
  }
76
- function hookNameFromMethod({ method, className, }) {
80
+ export function hookNameFromMethod({ method, className, }) {
77
81
  const methodName = getNameFromMethod(method);
78
82
  return `use${className}${capitalizeFirstLetter(methodName)}`;
79
83
  }
80
- function createQueryKeyFromMethod({ method, className, }) {
84
+ export function createQueryKeyFromMethod({ method, className, }) {
81
85
  const customHookName = hookNameFromMethod({ method, className });
82
86
  const queryKey = `${customHookName}Key`;
83
87
  return queryKey;
@@ -87,7 +91,7 @@ function createQueryKeyFromMethod({ method, className, }) {
87
91
  * @param queryString The type of query to use from react-query
88
92
  * @param suffix The suffix to append to the hook name
89
93
  */
90
- function createQueryHook({ queryString, suffix, responseDataType, requestParams, method, className, }) {
94
+ export function createQueryHook({ queryString, suffix, responseDataType, requestParams, method, className, }) {
91
95
  const methodName = getNameFromMethod(method);
92
96
  const customHookName = hookNameFromMethod({ method, className });
93
97
  const queryKey = createQueryKeyFromMethod({ method, className });
@@ -109,23 +113,21 @@ function createQueryHook({ queryString, suffix, responseDataType, requestParams,
109
113
  ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("queryFn")),
110
114
  ]),
111
115
  ])),
112
- ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createIdentifier(queryString), [
116
+ ], undefined, EqualsOrGreaterThanToken, ts.factory.createCallExpression(ts.factory.createIdentifier(queryString), [
113
117
  ts.factory.createTypeReferenceNode(TData),
114
118
  ts.factory.createTypeReferenceNode(TError),
115
119
  ], [
116
120
  ts.factory.createObjectLiteralExpression([
117
- ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryKey"), ts.factory.createArrayLiteralExpression([
118
- BuildCommonTypeName(queryKey),
119
- ts.factory.createSpreadElement(ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(ts.factory.createIdentifier("queryKey"), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), method.getParameters().length
120
- ? ts.factory.createArrayLiteralExpression([
121
- ts.factory.createObjectLiteralExpression(method
122
- .getParameters()
123
- .map((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))
124
- .flat()),
125
- ])
126
- : ts.factory.createArrayLiteralExpression([])))),
127
- ], false)),
128
- ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryFn"), ts.factory.createArrowFunction(undefined, undefined, [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createAsExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined, method.getParameters().length
121
+ ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryKey"), ts.factory.createCallExpression(BuildCommonTypeName(getQueryKeyFnName(queryKey)), undefined, method.getParameters().length
122
+ ? [
123
+ ts.factory.createObjectLiteralExpression(method
124
+ .getParameters()
125
+ .map((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))
126
+ .flat()),
127
+ ts.factory.createIdentifier("queryKey"),
128
+ ]
129
+ : [])),
130
+ ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryFn"), ts.factory.createArrowFunction(undefined, undefined, [], undefined, EqualsOrGreaterThanToken, ts.factory.createAsExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined, method.getParameters().length
129
131
  ? [
130
132
  ts.factory.createObjectLiteralExpression(method
131
133
  .getParameters()
@@ -176,11 +178,43 @@ export const createUseQuery = ({ className, method, jsDoc, }) => {
176
178
  methodName,
177
179
  queryKey,
178
180
  });
181
+ const queryKeyFn = createQueryKeyFnExport(queryKey, method);
179
182
  return {
180
183
  apiResponse: defaultApiResponse,
181
184
  returnType: returnTypeExport,
182
185
  key: queryKeyExport,
183
186
  queryHook: hookWithJsDoc,
184
187
  suspenseQueryHook: suspenseHookWithJsDoc,
188
+ queryKeyFn,
185
189
  };
186
190
  };
191
+ function getQueryKeyFnName(queryKey) {
192
+ return `${capitalizeFirstLetter(queryKey)}Fn`;
193
+ }
194
+ function createQueryKeyFnExport(queryKey, method) {
195
+ const params = getRequestParamFromMethod(method);
196
+ // override key is used to allow the user to override the the queryKey values
197
+ const overrideKey = ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("queryKey"), QuestionToken, ts.factory.createTypeReferenceNode("Array<unknown>", []));
198
+ return ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
199
+ ts.factory.createVariableDeclaration(ts.factory.createIdentifier(getQueryKeyFnName(queryKey)), undefined, undefined, ts.factory.createArrowFunction(undefined, undefined, params ? [params, overrideKey] : [], undefined, EqualsOrGreaterThanToken, queryKeyFn(queryKey, method))),
200
+ ], ts.NodeFlags.Const));
201
+ }
202
+ function queryKeyFn(queryKey, method) {
203
+ const params = getRequestParamFromMethod(method);
204
+ if (!params) {
205
+ return ts.factory.createArrayLiteralExpression([
206
+ ts.factory.createIdentifier(queryKey),
207
+ ]);
208
+ }
209
+ return ts.factory.createArrayLiteralExpression([
210
+ ts.factory.createIdentifier(queryKey),
211
+ ts.factory.createSpreadElement(ts.factory.createParenthesizedExpression(ts.factory.createBinaryExpression(ts.factory.createIdentifier("queryKey"), ts.factory.createToken(ts.SyntaxKind.QuestionQuestionToken), method.getParameters().length
212
+ ? ts.factory.createArrayLiteralExpression([
213
+ ts.factory.createObjectLiteralExpression(method
214
+ .getParameters()
215
+ .map((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))
216
+ .flat()),
217
+ ])
218
+ : ts.factory.createArrayLiteralExpression([])))),
219
+ ], false);
220
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7nohe/openapi-react-query-codegen",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "OpenAPI React Query Codegen",
5
5
  "bin": {
6
6
  "openapi-rq": "dist/cli.mjs"