@7nohe/openapi-react-query-codegen 1.6.1 → 1.6.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
@@ -7,6 +7,7 @@
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
 
@@ -79,9 +80,10 @@ $ openapi-rq -i ./petstore.yaml
79
80
  - queries
80
81
  - index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
81
82
  - common.ts <- common types
83
+ - ensureQueryData.ts <- generated ensureQueryData functions
82
84
  - queries.ts <- generated query hooks
83
85
  - suspenses.ts <- generated suspense hooks
84
- - prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
86
+ - prefetch.ts <- generated prefetch functions learn more about prefetching in in link below
85
87
  - requests <- output code generated by @hey-api/openapi-ts
86
88
  ```
87
89
 
package/dist/cli.mjs CHANGED
@@ -36,7 +36,7 @@ async function setupProgram() {
36
36
  .addOption(new Option("--schemaType <value>", "Type of JSON schema [Default: 'json']").choices(["form", "json"]))
37
37
  .option("--pageParam <value>", "Name of the query parameter used for pagination", "page")
38
38
  .option("--nextPageParam <value>", "Name of the response parameter used for next page", "nextPage")
39
- .option("--initialPageParam <value>", "Initial page value to query", "initialPageParam")
39
+ .option("--initialPageParam <value>", "Initial page value to query", "1")
40
40
  .parse();
41
41
  const options = program.opts();
42
42
  await generate(options, version);
@@ -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,4 +1,4 @@
1
- import { createPrefetch } from "./createPrefetch.mjs";
1
+ import { createPrefetchOrEnsure } from "./createPrefetchOrEnsure.mjs";
2
2
  import { createUseMutation } from "./createUseMutation.mjs";
3
3
  import { createUseQuery } from "./createUseQuery.mjs";
4
4
  export const createExports = (service, pageParam, nextPageParam, initialPageParam) => {
@@ -10,7 +10,8 @@ export const createExports = (service, pageParam, nextPageParam, initialPagePara
10
10
  const allPatch = methods.filter((m) => m.httpMethodName.toUpperCase().includes("PATCH"));
11
11
  const allDelete = methods.filter((m) => m.httpMethodName.toUpperCase().includes("DELETE"));
12
12
  const allGetQueries = allGet.map((m) => createUseQuery(m, pageParam, nextPageParam, initialPageParam));
13
- const allPrefetchQueries = allGet.map((m) => createPrefetch(m));
13
+ const allPrefetchQueries = allGet.map((m) => createPrefetchOrEnsure({ ...m, functionType: "prefetch" }));
14
+ const allEnsureQueries = allGet.map((m) => createPrefetchOrEnsure({ ...m, functionType: "ensure" }));
14
15
  const allPostMutations = allPost.map((m) => createUseMutation(m));
15
16
  const allPutMutations = allPut.map((m) => createUseMutation(m));
16
17
  const allPatchMutations = allPatch.map((m) => createUseMutation(m));
@@ -44,9 +45,8 @@ export const createExports = (service, pageParam, nextPageParam, initialPagePara
44
45
  suspenseQueryHook,
45
46
  ]);
46
47
  const suspenseExports = [...suspenseQueries];
47
- const allPrefetches = allPrefetchQueries.flatMap(({ prefetchHook }) => [
48
- prefetchHook,
49
- ]);
48
+ const allPrefetches = allPrefetchQueries.flatMap(({ hook }) => [hook]);
49
+ const allEnsures = allEnsureQueries.flatMap(({ hook }) => [hook]);
50
50
  const allPrefetchExports = [...allPrefetches];
51
51
  return {
52
52
  /**
@@ -69,5 +69,9 @@ export const createExports = (service, pageParam, nextPageParam, initialPagePara
69
69
  * Prefetch exports are the hooks that are used in the prefetch components
70
70
  */
71
71
  allPrefetchExports,
72
+ /**
73
+ * Ensure exports are the hooks that are used in the loader components
74
+ */
75
+ allEnsures,
72
76
  };
73
77
  };
@@ -3,12 +3,15 @@ import { BuildCommonTypeName, extractPropertiesFromObjectParam, getNameFromMetho
3
3
  import { createQueryKeyFromMethod, getQueryKeyFnName, getRequestParamFromMethod, hookNameFromMethod, } from "./createUseQuery.mjs";
4
4
  import { addJSDocToNode } from "./util.mjs";
5
5
  /**
6
- * Creates a prefetch function for a query
6
+ * Creates a prefetch/ensure function for a query
7
7
  */
8
- function createPrefetchHook({ requestParams, method, className, }) {
8
+ function createPrefetchOrEnsureHook({ requestParams, method, className, functionType, }) {
9
9
  const methodName = getNameFromMethod(method);
10
10
  const queryName = hookNameFromMethod({ method, className });
11
- const customHookName = `prefetch${queryName.charAt(0).toUpperCase() + queryName.slice(1)}`;
11
+ let customHookName = `prefetch${queryName.charAt(0).toUpperCase() + queryName.slice(1)}`;
12
+ if (functionType === "ensure") {
13
+ customHookName = `ensure${queryName.charAt(0).toUpperCase() + queryName.slice(1)}Data`;
14
+ }
12
15
  const queryKey = createQueryKeyFromMethod({ method, className });
13
16
  // const
14
17
  const hookExport = ts.factory.createVariableStatement(
@@ -17,7 +20,7 @@ function createPrefetchHook({ requestParams, method, className, }) {
17
20
  ts.factory.createVariableDeclaration(ts.factory.createIdentifier(customHookName), undefined, undefined, ts.factory.createArrowFunction(undefined, undefined, [
18
21
  ts.factory.createParameterDeclaration(undefined, undefined, "queryClient", undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("QueryClient"))),
19
22
  ...requestParams,
20
- ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createIdentifier("queryClient.prefetchQuery"), undefined, [
23
+ ], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createIdentifier(`queryClient.${functionType === "prefetch" ? "prefetchQuery" : "ensureQueryData"}`), undefined, [
21
24
  ts.factory.createObjectLiteralExpression([
22
25
  ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryKey"), ts.factory.createCallExpression(BuildCommonTypeName(getQueryKeyFnName(queryKey)), undefined, method.getParameters().length
23
26
  ? [
@@ -38,16 +41,17 @@ function createPrefetchHook({ requestParams, method, className, }) {
38
41
  ], ts.NodeFlags.Const));
39
42
  return hookExport;
40
43
  }
41
- export const createPrefetch = ({ className, method, jsDoc, }) => {
44
+ export const createPrefetchOrEnsure = ({ className, method, jsDoc, functionType, }) => {
42
45
  const requestParam = getRequestParamFromMethod(method);
43
46
  const requestParams = requestParam ? [requestParam] : [];
44
- const prefetchHook = createPrefetchHook({
47
+ const prefetchOrEnsureHook = createPrefetchOrEnsureHook({
45
48
  requestParams,
46
49
  method,
47
50
  className,
51
+ functionType,
48
52
  });
49
- const hookWithJsDoc = addJSDocToNode(prefetchHook, jsDoc);
53
+ const hookWithJsDoc = addJSDocToNode(prefetchOrEnsureHook, jsDoc);
50
54
  return {
51
- prefetchHook: hookWithJsDoc,
55
+ hook: hookWithJsDoc,
52
56
  };
53
57
  };
@@ -30,6 +30,7 @@ const createSourceFile = async (outputPath, serviceEndName, pageParam, nextPageP
30
30
  const suspenseSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.suspenseExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
31
31
  const indexSource = ts.factory.createSourceFile([commonExport, queriesExport], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
32
32
  const prefetchSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.allPrefetchExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
33
+ const ensureSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.allEnsures], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
33
34
  return {
34
35
  commonSource,
35
36
  infiniteQueriesSource,
@@ -37,6 +38,7 @@ const createSourceFile = async (outputPath, serviceEndName, pageParam, nextPageP
37
38
  suspenseSource,
38
39
  indexSource,
39
40
  prefetchSource,
41
+ ensureSource,
40
42
  };
41
43
  };
42
44
  export const createSource = async ({ outputPath, version, serviceEndName, pageParam, nextPageParam, initialPageParam, }) => {
@@ -46,11 +48,12 @@ export const createSource = async ({ outputPath, version, serviceEndName, pagePa
46
48
  const suspenseFile = ts.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
47
49
  const indexFile = ts.createSourceFile(`${OpenApiRqFiles.index}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
48
50
  const prefetchFile = ts.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
51
+ const ensureQueryDataFile = ts.createSourceFile(`${OpenApiRqFiles.ensureQueryData}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
49
52
  const printer = ts.createPrinter({
50
53
  newLine: ts.NewLineKind.LineFeed,
51
54
  removeComments: false,
52
55
  });
53
- const { commonSource, mainSource, infiniteQueriesSource, suspenseSource, indexSource, prefetchSource, } = await createSourceFile(outputPath, serviceEndName, pageParam, nextPageParam, initialPageParam);
56
+ const { commonSource, mainSource, infiniteQueriesSource, suspenseSource, indexSource, prefetchSource, ensureSource, } = await createSourceFile(outputPath, serviceEndName, pageParam, nextPageParam, initialPageParam);
54
57
  const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
55
58
  const commonResult = comment +
56
59
  printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
@@ -64,6 +67,8 @@ export const createSource = async ({ outputPath, version, serviceEndName, pagePa
64
67
  printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
65
68
  const prefetchResult = comment +
66
69
  printer.printNode(ts.EmitHint.Unspecified, prefetchSource, prefetchFile);
70
+ const enqureResult = comment +
71
+ printer.printNode(ts.EmitHint.Unspecified, ensureSource, ensureQueryDataFile);
67
72
  return [
68
73
  {
69
74
  name: `${OpenApiRqFiles.index}.ts`,
@@ -89,5 +94,9 @@ export const createSource = async ({ outputPath, version, serviceEndName, pagePa
89
94
  name: `${OpenApiRqFiles.prefetch}.ts`,
90
95
  content: prefetchResult,
91
96
  },
97
+ {
98
+ name: `${OpenApiRqFiles.ensureQueryData}.ts`,
99
+ content: enqureResult,
100
+ },
92
101
  ];
93
102
  };
@@ -85,6 +85,30 @@ export function createQueryKeyFromMethod({ method, className, }) {
85
85
  const queryKey = `${customHookName}Key`;
86
86
  return queryKey;
87
87
  }
88
+ /**
89
+ * Extracts the type of the next page parameter from the given properties.
90
+ *
91
+ * @param properties The properties to search through.
92
+ * @param nextPageParam The name of the next page parameter.
93
+ * @returns The type of the next page parameter, if found.
94
+ */
95
+ function findNextPageParamType(properties, nextPageParam) {
96
+ if (!properties)
97
+ return undefined;
98
+ for (const property of properties) {
99
+ if (property.getName() === nextPageParam) {
100
+ return property?.getDeclarations()?.at(0)?.getType()?.getText();
101
+ }
102
+ const type = property.getDeclarations().at(0)?.getType();
103
+ const nestedProperties = type?.getProperties();
104
+ if (!type?.isObject() || type.isArray())
105
+ continue;
106
+ const result = findNextPageParamType(nestedProperties, nextPageParam);
107
+ if (result)
108
+ return result;
109
+ }
110
+ return undefined;
111
+ }
88
112
  /**
89
113
  * Creates a custom hook for a query
90
114
  * @param queryString The type of query to use from react-query
@@ -101,6 +125,11 @@ export function createQueryHook({ queryString, suffix, responseDataType, request
101
125
  const isInfiniteQuery = queryString === "useInfiniteQuery";
102
126
  const responseDataTypeRef = responseDataType.default;
103
127
  const responseDataTypeIdentifier = responseDataTypeRef.typeName;
128
+ const arg = method.getReturnType().getTypeArguments().at(0);
129
+ const nextPageParamTypePropetires = arg?.getProperties();
130
+ const nextPageParamType = arg?.isObject() && nextPageParam
131
+ ? findNextPageParamType(nextPageParamTypePropetires, nextPageParam.split(".").at(-1) ?? "")
132
+ : undefined;
104
133
  const hookExport = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
105
134
  ts.factory.createVariableDeclaration(ts.factory.createIdentifier(`${customHookName}${suffix}`), undefined, undefined, ts.factory.createArrowFunction(undefined, ts.factory.createNodeArray([
106
135
  isInfiniteQuery
@@ -151,11 +180,15 @@ export function createQueryHook({ queryString, suffix, responseDataType, request
151
180
  ts.factory.createObjectLiteralExpression(method
152
181
  .getParameters()
153
182
  .flatMap((param) => extractPropertiesFromObjectParam(param).map((p) => p.name === pageParam
154
- ? ts.factory.createPropertyAssignment(ts.factory.createIdentifier(p.name), ts.factory.createAsExpression(ts.factory.createIdentifier("pageParam"), ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)))
183
+ ? ts.factory.createPropertyAssignment(ts.factory.createIdentifier(p.name), ts.factory.createAsExpression(ts.factory.createIdentifier("pageParam"), ts.factory.createKeywordTypeNode(p.type?.getText() === "number"
184
+ ? ts.SyntaxKind
185
+ .NumberKeyword
186
+ : ts.SyntaxKind
187
+ .StringKeyword)))
155
188
  : ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))),
156
189
  ]
157
190
  : undefined), ts.factory.createTypeReferenceNode(TData)))),
158
- ...createInfiniteQueryParams(pageParam, nextPageParam, initialPageParam),
191
+ ...createInfiniteQueryParams(pageParam, nextPageParam, initialPageParam, nextPageParamType),
159
192
  ts.factory.createSpreadAssignment(ts.factory.createIdentifier("options")),
160
193
  ]),
161
194
  ]))),
@@ -258,7 +291,7 @@ function queryKeyFn(queryKey, method) {
258
291
  : ts.factory.createArrayLiteralExpression([])))),
259
292
  ], false);
260
293
  }
261
- function createInfiniteQueryParams(pageParam, nextPageParam, initialPageParam = "1") {
294
+ function createInfiniteQueryParams(pageParam, nextPageParam, initialPageParam = "1", type) {
262
295
  if (pageParam === undefined || nextPageParam === undefined) {
263
296
  return [];
264
297
  }
@@ -272,6 +305,8 @@ function createInfiniteQueryParams(pageParam, nextPageParam, initialPageParam =
272
305
  return ts.factory.createTypeLiteralNode([
273
306
  ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(segment), undefined, acc),
274
307
  ]);
275
- }, ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)))), ts.factory.createIdentifier(nextPageParam)))),
308
+ }, ts.factory.createKeywordTypeNode(type === "number"
309
+ ? ts.SyntaxKind.NumberKeyword
310
+ : ts.SyntaxKind.StringKeyword)))), ts.factory.createIdentifier(nextPageParam)))),
276
311
  ];
277
312
  }
package/dist/generate.mjs CHANGED
@@ -27,6 +27,7 @@ export async function generate(options, version) {
27
27
  export: true,
28
28
  response: formattedOptions.serviceResponse,
29
29
  asClass: true,
30
+ operationId: formattedOptions.operationId ?? false,
30
31
  },
31
32
  types: {
32
33
  dates: formattedOptions.useDateType,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7nohe/openapi-react-query-codegen",
3
- "version": "1.6.1",
3
+ "version": "1.6.2",
4
4
  "description": "OpenAPI React Query Codegen",
5
5
  "keywords": [
6
6
  "codegen",
@@ -61,7 +61,7 @@
61
61
  "lint": "biome check .",
62
62
  "lint:fix": "biome check --write .",
63
63
  "preview": "npm run build && npm -C examples/react-app run generate:api",
64
- "release": "npx git-ensure -a && npx bumpp --commit --tag --push",
64
+ "release": "npx git-ensure -a -b v1 && npx bumpp --commit --tag --push",
65
65
  "snapshot": "vitest --update",
66
66
  "test": "vitest --coverage.enabled true"
67
67
  }