@7nohe/openapi-react-query-codegen 2.0.0 → 2.2.0

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/dist/common.mjs CHANGED
@@ -192,22 +192,15 @@ function mutationKeyFn(mutationKey) {
192
192
  ], false);
193
193
  }
194
194
  export function getRequestParamFromMethod(method, pageParam, modelNames = []) {
195
- if (!getVariableArrowFunctionParameters(method).length) {
195
+ const sdkParams = getVariableArrowFunctionParameters(method);
196
+ if (!sdkParams.length) {
196
197
  return null;
197
198
  }
198
199
  const methodName = getNameFromVariable(method);
199
- const params = getVariableArrowFunctionParameters(method).flatMap((param) => {
200
- const paramNodes = extractPropertiesFromObjectParam(param);
201
- return paramNodes
202
- .filter((p) => p.name !== pageParam)
203
- .map((refParam) => ({
204
- name: refParam.name,
205
- // TODO: Client<Request, Response, unknown, RequestOptions> -> Client<Request, Response, unknown>
206
- typeName: getShortType(refParam.type?.getText() ?? ""),
207
- optional: refParam.optional,
208
- }));
209
- });
210
- const areAllPropertiesOptional = params.every((param) => param.optional);
200
+ // Use the SDK function's parameter optionality as the authoritative check.
201
+ // Generic types like Options<XData, ThrowOnError> may not resolve correctly
202
+ // via extractPropertiesFromObjectParam for type alias properties (path, url).
203
+ const areAllPropertiesOptional = sdkParams[0].isOptional();
211
204
  return ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("clientOptions"), undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Options"), [
212
205
  ts.factory.createTypeReferenceNode(modelNames.includes(`${capitalizeFirstLetter(methodName)}Data`)
213
206
  ? `${capitalizeFirstLetter(methodName)}Data`
@@ -1,7 +1,7 @@
1
1
  export const defaultOutputPath = "openapi";
2
2
  export const queriesOutputPath = "queries";
3
3
  export const requestsOutputPath = "requests";
4
- export const serviceFileName = "services.gen";
4
+ export const serviceFileName = "sdk.gen";
5
5
  export const modelsFileName = "types.gen";
6
6
  export const OpenApiRqFiles = {
7
7
  queries: "queries",
@@ -24,14 +24,16 @@ export const createExports = ({ service, client, project, pageParam, nextPagePar
24
24
  if (ts.isTypeAliasDeclaration(node) && methodDataNames[key] !== undefined) {
25
25
  // get the type alias declaration
26
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]);
27
+ if (ts.isTypeLiteralNode(typeAliasDeclaration)) {
28
+ const query = typeAliasDeclaration.members.find((m) => ts.isPropertySignature(m) && m.name?.getText() === "query");
29
+ if (query) {
30
+ const queryType = query.type;
31
+ const members = queryType && ts.isTypeLiteralNode(queryType)
32
+ ? queryType.members
33
+ : undefined;
34
+ if (members?.map((m) => m.name?.getText()).includes(pageParam)) {
35
+ paginatableMethods.push(methodDataNames[key]);
36
+ }
35
37
  }
36
38
  }
37
39
  }
@@ -82,7 +84,7 @@ export const createExports = ({ service, client, project, pageParam, nextPagePar
82
84
  const mainExports = [...mainQueries, ...mainMutations];
83
85
  const infiniteQueriesExports = allQueries
84
86
  .flatMap(({ infiniteQueryHook }) => [infiniteQueryHook])
85
- .filter(Boolean);
87
+ .filter((x) => x != null);
86
88
  const suspenseQueries = allQueries.flatMap(({ suspenseQueryHook }) => [
87
89
  suspenseQueryHook,
88
90
  ]);
@@ -16,13 +16,13 @@ export const createImports = ({ project, client, }) => {
16
16
  ? Array.from(modelsFile.getExportedDeclarations().keys())
17
17
  : [];
18
18
  const serviceExports = Array.from(serviceFile.getExportedDeclarations().keys());
19
- const serviceNames = serviceExports;
19
+ // Filter out type-only exports (e.g. Options) to avoid duplicate imports,
20
+ // since Options is already imported separately from the client module.
21
+ const serviceNames = serviceExports.filter((name) => name !== "Options");
20
22
  const imports = [
21
- ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
22
- ts.factory.createImportSpecifier(true, undefined, ts.factory.createIdentifier("Options")),
23
- ])), ts.factory.createStringLiteral(client === "@hey-api/client-axios"
24
- ? "@hey-api/client-axios"
25
- : "@hey-api/client-fetch"), undefined),
23
+ ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(true, undefined, ts.factory.createNamedImports([
24
+ ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("Options")),
25
+ ])), ts.factory.createStringLiteral(join("../requests", serviceFileName)), undefined),
26
26
  ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
27
27
  ts.factory.createImportSpecifier(true, undefined, ts.factory.createIdentifier("QueryClient")),
28
28
  ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("useQuery")),
@@ -23,11 +23,17 @@ export const createUseMutation = ({ functionDescription: { method, jsDoc }, mode
23
23
  const responseDataType = ts.factory.createTypeParameterDeclaration(undefined, TData, undefined, ts.factory.createTypeReferenceNode(BuildCommonTypeName(mutationResult.name)));
24
24
  // @hey-api/client-axios -> `TError = AxiosError<AddPetError>`
25
25
  // @hey-api/client-fetch -> `TError = AddPetError`
26
- const responseErrorType = ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, client === "@hey-api/client-axios"
27
- ? ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("AxiosError"), [
28
- ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(`${capitalizeFirstLetter(methodName)}Error`)),
29
- ])
30
- : ts.factory.createTypeReferenceNode(`${capitalizeFirstLetter(methodName)}Error`));
26
+ const errorTypeName = `${capitalizeFirstLetter(methodName)}Error`;
27
+ const hasErrorType = modelNames.includes(errorTypeName);
28
+ const responseErrorType = ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, hasErrorType
29
+ ? client === "@hey-api/client-axios"
30
+ ? ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("AxiosError"), [
31
+ ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(errorTypeName)),
32
+ ])
33
+ : ts.factory.createTypeReferenceNode(errorTypeName)
34
+ : client === "@hey-api/client-axios"
35
+ ? ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("AxiosError"), [ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)])
36
+ : ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword));
31
37
  const methodParameters = getVariableArrowFunctionParameters(method).length !== 0
32
38
  ? ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Options"), [
33
39
  ts.factory.createTypeReferenceNode(modelNames.includes(`${capitalizeFirstLetter(methodName)}Data`)
@@ -1,7 +1,7 @@
1
1
  import ts from "typescript";
2
2
  import { BuildCommonTypeName, EqualsOrGreaterThanToken, TData, TError, capitalizeFirstLetter, createQueryKeyExport, createQueryKeyFnExport, getNameFromVariable, getQueryKeyFnName, getRequestParamFromMethod, getVariableArrowFunctionParameters, queryKeyConstraint, queryKeyGenericType, } from "./common.mjs";
3
3
  import { addJSDocToNode } from "./util.mjs";
4
- const createApiResponseType = ({ methodName, client, }) => {
4
+ const createApiResponseType = ({ methodName, client, modelNames, }) => {
5
5
  /** Awaited<ReturnType<typeof myClass.myMethod>> */
6
6
  const awaitedResponseDataType = ts.factory.createIndexedAccessTypeNode(ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Awaited"), [
7
7
  ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("ReturnType"), [
@@ -17,11 +17,17 @@ const createApiResponseType = ({ methodName, client, }) => {
17
17
  const suspenseResponseDataType = ts.factory.createTypeParameterDeclaration(undefined, TData.text, undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("NonNullable"), [
18
18
  ts.factory.createTypeReferenceNode(BuildCommonTypeName(apiResponse.name)),
19
19
  ]));
20
- const responseErrorType = ts.factory.createTypeParameterDeclaration(undefined, TError.text, undefined, client === "@hey-api/client-axios"
21
- ? ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("AxiosError"), [
22
- ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(`${capitalizeFirstLetter(methodName)}Error`)),
23
- ])
24
- : ts.factory.createTypeReferenceNode(`${capitalizeFirstLetter(methodName)}Error`));
20
+ const errorTypeName = `${capitalizeFirstLetter(methodName)}Error`;
21
+ const hasErrorType = modelNames.includes(errorTypeName);
22
+ const responseErrorType = ts.factory.createTypeParameterDeclaration(undefined, TError.text, undefined, hasErrorType
23
+ ? client === "@hey-api/client-axios"
24
+ ? ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("AxiosError"), [
25
+ ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(errorTypeName)),
26
+ ])
27
+ : ts.factory.createTypeReferenceNode(errorTypeName)
28
+ : client === "@hey-api/client-axios"
29
+ ? ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("AxiosError"), [ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)])
30
+ : ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword));
25
31
  return {
26
32
  /**
27
33
  * DefaultResponseDataType
@@ -87,6 +93,9 @@ function createQueryHook({ queryString, suffix, responseDataType, responseErrorT
87
93
  }
88
94
  const isInfiniteQuery = queryString === "useInfiniteQuery";
89
95
  const isSuspenseQuery = queryString === "useSuspenseQuery";
96
+ // ts.TypeParameterDeclaration.default is ts.TypeNode | undefined.
97
+ // We know it's a TypeReferenceNode with an Identifier typeName because we created it
98
+ // via ts.factory in createApiResponseType, but TypeScript cannot infer the specific subtype.
90
99
  const responseDataTypeRef = responseDataType.default;
91
100
  const responseDataTypeIdentifier = responseDataTypeRef.typeName;
92
101
  const hookExport = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
@@ -169,6 +178,7 @@ export const createUseQuery = ({ functionDescription: { method, jsDoc }, client,
169
178
  const { apiResponse: defaultApiResponse, responseDataType, suspenseResponseDataType, responseErrorType, } = createApiResponseType({
170
179
  methodName,
171
180
  client,
181
+ modelNames,
172
182
  });
173
183
  const requestParam = getRequestParamFromMethod(method, undefined, modelNames);
174
184
  const infiniteRequestParam = getRequestParamFromMethod(method, pageParam, modelNames);
package/dist/generate.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import path from "node:path";
1
3
  import { createClient } from "@hey-api/openapi-ts";
2
4
  import { buildQueriesOutputPath, buildRequestsOutputPath, formatOptions, } from "./common.mjs";
3
5
  import { createSource } from "./createSource.mjs";
@@ -6,34 +8,50 @@ import { print } from "./print.mjs";
6
8
  export async function generate(options, version) {
7
9
  const openApiOutputPath = buildRequestsOutputPath(options.output);
8
10
  const formattedOptions = formatOptions(options);
11
+ // Map CLI options to new plugins system
12
+ const clientPlugin = formattedOptions.client === "@hey-api/client-axios"
13
+ ? "@hey-api/client-axios"
14
+ : "@hey-api/client-fetch";
15
+ const typescriptPlugin = formattedOptions.enums
16
+ ? {
17
+ name: "@hey-api/typescript",
18
+ enums: formattedOptions.enums,
19
+ }
20
+ : "@hey-api/typescript";
21
+ const sdkPlugin = formattedOptions.noOperationId
22
+ ? {
23
+ name: "@hey-api/sdk",
24
+ // `operationId: false` was deprecated in favor of `operations.nesting`
25
+ operations: {
26
+ nesting: "id",
27
+ },
28
+ }
29
+ : "@hey-api/sdk";
30
+ const plugins = [
31
+ clientPlugin,
32
+ typescriptPlugin,
33
+ sdkPlugin,
34
+ ];
35
+ // Conditionally add schemas plugin
36
+ if (!formattedOptions.noSchemas) {
37
+ plugins.push(formattedOptions.schemaType
38
+ ? {
39
+ name: "@hey-api/schemas",
40
+ type: formattedOptions.schemaType,
41
+ }
42
+ : "@hey-api/schemas");
43
+ }
9
44
  const config = {
10
- client: formattedOptions.client,
11
- debug: formattedOptions.debug,
12
45
  dryRun: false,
13
- exportCore: true,
14
- output: {
15
- format: formattedOptions.format,
16
- lint: formattedOptions.lint,
17
- path: openApiOutputPath,
18
- },
19
46
  input: formattedOptions.input,
20
- schemas: {
21
- export: !formattedOptions.noSchemas,
22
- type: formattedOptions.schemaType,
23
- },
24
- services: {
25
- export: true,
26
- asClass: false,
27
- operationId: !formattedOptions.noOperationId,
28
- },
29
- types: {
30
- dates: formattedOptions.useDateType,
31
- export: true,
32
- enums: formattedOptions.enums,
33
- },
34
- useOptions: true,
47
+ output: openApiOutputPath,
48
+ plugins,
35
49
  };
36
50
  await createClient(config);
51
+ // Generate backward-compatible services.gen.ts shim
52
+ // client.gen.ts has the `client` instance; sdk.gen.ts has SDK functions
53
+ const shimContent = `// This file is auto-generated for backward compatibility\nexport * from './client.gen.js';\nexport * from './sdk.gen.js';\n`;
54
+ await writeFile(path.join(openApiOutputPath, "services.gen.ts"), shimContent);
37
55
  const source = await createSource({
38
56
  outputPath: openApiOutputPath,
39
57
  client: formattedOptions.client,
package/dist/service.mjs CHANGED
@@ -15,36 +15,55 @@ export async function getServices(project) {
15
15
  }
16
16
  export function getMethodsFromService(node) {
17
17
  const variableStatements = node.getVariableStatements();
18
- // The first variable statement is `const client = createClient(createConfig())`, so we skip it
19
- return variableStatements.splice(1).flatMap((variableStatement) => {
18
+ // Filter to only exported variable statements that contain arrow functions
19
+ const exportedStatements = variableStatements.filter((statement) => {
20
+ if (!statement.isExported())
21
+ return false;
22
+ const declarations = statement.getDeclarations();
23
+ return declarations.some((decl) => {
24
+ const initializer = decl.getInitializer();
25
+ return initializer && ts.isArrowFunction(initializer.compilerNode);
26
+ });
27
+ });
28
+ return exportedStatements.flatMap((variableStatement) => {
20
29
  const declarations = variableStatement.getDeclarations();
21
- return declarations.map((declaration) => {
22
- if (!ts.isVariableDeclaration(declaration.compilerNode)) {
23
- throw new Error("Variable declaration not found");
24
- }
30
+ // Filter to only arrow function declarations within the statement
31
+ const arrowDeclarations = declarations.filter((declaration) => {
25
32
  const initializer = declaration.getInitializer();
26
- if (!initializer) {
27
- throw new Error("Initializer not found");
28
- }
29
- if (!ts.isArrowFunction(initializer.compilerNode)) {
30
- throw new Error("Arrow function not found");
31
- }
32
- const methodBlockNode = initializer.compilerNode.body;
33
- if (!methodBlockNode || !ts.isBlock(methodBlockNode)) {
34
- throw new Error("Method block not found");
33
+ return initializer && ts.isArrowFunction(initializer.compilerNode);
34
+ });
35
+ return arrowDeclarations.map((declaration) => {
36
+ const initializer = declaration.getInitializerOrThrow();
37
+ const compilerNode = initializer.compilerNode;
38
+ const arrowBody = compilerNode.body;
39
+ // Find the call expression - either from block's return statement or direct expression
40
+ let callExpression;
41
+ let methodBlockNode;
42
+ if (ts.isBlock(arrowBody)) {
43
+ // Old style: arrow function with block body
44
+ methodBlockNode = arrowBody;
45
+ const returnStatement = arrowBody.statements.find(ts.isReturnStatement);
46
+ if (!returnStatement) {
47
+ throw new Error("Return statement not found");
48
+ }
49
+ if (!returnStatement.expression) {
50
+ throw new Error("Call expression not found");
51
+ }
52
+ callExpression = returnStatement.expression;
35
53
  }
36
- const foundReturnStatement = methodBlockNode.statements.find((s) => s.kind === ts.SyntaxKind.ReturnStatement);
37
- if (!foundReturnStatement) {
38
- throw new Error("Return statement not found");
54
+ else {
55
+ // New style: arrow function with expression body (no block)
56
+ // The body is a call expression like: (options?.client ?? client).post<...>({...})
57
+ callExpression = arrowBody;
39
58
  }
40
- const returnStatement = foundReturnStatement;
41
- const foundCallExpression = returnStatement.expression;
42
- if (!foundCallExpression) {
43
- throw new Error("Call expression not found");
59
+ // Navigate to find the HTTP method name (get, post, put, delete, etc.)
60
+ let httpMethodName;
61
+ if (ts.isCallExpression(callExpression)) {
62
+ const expr = callExpression.expression;
63
+ if (ts.isPropertyAccessExpression(expr)) {
64
+ httpMethodName = expr.name.text;
65
+ }
44
66
  }
45
- const callExpression = foundCallExpression;
46
- const propertyAccessExpression = callExpression.expression;
47
- const httpMethodName = propertyAccessExpression.name.getText();
48
67
  if (!httpMethodName) {
49
68
  throw new Error("httpMethodName not found");
50
69
  }
@@ -56,7 +75,7 @@ export function getMethodsFromService(node) {
56
75
  }
57
76
  return [tsNode];
58
77
  };
59
- const children = getAllChildren(initializer.compilerNode);
78
+ const children = getAllChildren(variableStatement.compilerNode);
60
79
  // get all JSDoc comments
61
80
  // this should be an array of 1 or 0
62
81
  const jsDocs = children
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7nohe/openapi-react-query-codegen",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "OpenAPI React Query Codegen",
5
5
  "bin": {
6
6
  "openapi-rq": "dist/cli.mjs"
@@ -39,29 +39,29 @@
39
39
  "license": "MIT",
40
40
  "author": "Daiki Urata (@7nohe)",
41
41
  "dependencies": {
42
- "@hey-api/client-fetch": "0.4.0",
43
- "@hey-api/openapi-ts": "0.53.8",
42
+ "@hey-api/openapi-ts": "0.99.0",
44
43
  "cross-spawn": "^7.0.3"
45
44
  },
46
45
  "devDependencies": {
47
46
  "@biomejs/biome": "^1.9.3",
48
47
  "@types/cross-spawn": "^6.0.6",
49
48
  "@types/node": "^22.7.4",
49
+ "@types/semver": "^7.7.1",
50
50
  "@vitest/coverage-v8": "^1.5.0",
51
51
  "commander": "^12.0.0",
52
52
  "lefthook": "^1.6.10",
53
53
  "rimraf": "^5.0.5",
54
- "ts-morph": "^23.0.0",
55
- "typescript": "^5.5.4",
54
+ "ts-morph": "^28.0.0",
55
+ "typescript": "^6.0.3",
56
56
  "vitest": "^1.5.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "commander": "12.x",
60
- "ts-morph": "23.x",
61
- "typescript": "5.x"
60
+ "ts-morph": "28.x",
61
+ "typescript": "5.x || 6.x"
62
62
  },
63
63
  "engines": {
64
- "node": ">=14",
64
+ "node": ">=22.18.0",
65
65
  "pnpm": ">=9"
66
66
  },
67
67
  "scripts": {