@7nohe/openapi-react-query-codegen 3.0.1 → 3.0.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.
@@ -1,4 +1,4 @@
1
- import type { Project } from "ts-morph";
1
+ import { type Project } from "ts-morph";
2
2
  import type { GenerationContext, OperationInfo } from "./types.mjs";
3
3
  /**
4
4
  * Parse operations from the OpenAPI-generated service file and return normalized DTOs.
@@ -1,3 +1,4 @@
1
+ import { Node, } from "ts-morph";
1
2
  import ts from "typescript";
2
3
  import { capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromVariable, getShortType, getVariableArrowFunctionParameters, } from "./common.mjs";
3
4
  import { modelsFileName, serviceFileName } from "./constants.mjs";
@@ -36,8 +37,25 @@ function extractParameters(method, pageParam) {
36
37
  });
37
38
  }
38
39
  /**
39
- * Get paginatable methods by checking if their Data type has the pageParam in query property.
40
+ * Read the operation's Data type name from the first type argument of the
41
+ * SDK options parameter (`Options<XData, ThrowOnError>`).
42
+ * See OperationInfo.dataTypeName for why the name is read from the signature
43
+ * instead of derived from the method name (#213).
44
+ */
45
+ function getDataTypeNameFromSignature(optionsParam) {
46
+ const typeNode = optionsParam?.getTypeNode();
47
+ if (!typeNode || !Node.isTypeReference(typeNode))
48
+ return undefined;
49
+ if (typeNode.getTypeName().getText() !== "Options")
50
+ return undefined;
51
+ const [dataTypeArg] = typeNode.getTypeArguments();
52
+ return dataTypeArg?.getText();
53
+ }
54
+ /**
55
+ * Get paginatable Data types by checking if they have the pageParam in their query property.
40
56
  * Uses TypeScript compiler API for accurate AST traversal.
57
+ * The map is keyed by the Data type name (e.g., "FindPetsData") so callers can
58
+ * look it up with the name read from the SDK signature.
41
59
  */
42
60
  function getPaginatableMethods(project, pageParam) {
43
61
  const modelsFile = project
@@ -70,13 +88,9 @@ function getPaginatableMethods(project, pageParam) {
70
88
  continue;
71
89
  const pageParamNode = queryType.members.find((m) => ts.isPropertySignature(m) && m.name?.getText() === pageParam);
72
90
  if (pageParamNode) {
73
- // Extract method name from Data type name (e.g., "FindPetsData" -> "findPets")
74
- const methodName = key.slice(0, -4); // Remove "Data" suffix
75
- // Convert first letter to lowercase
76
- const methodNameLower = methodName.charAt(0).toLowerCase() + methodName.slice(1);
77
91
  const pageParamType = pageParamNode.type?.getText(modelsFile.compilerNode);
78
92
  const resolvedType = typeChecker.getTypeAtLocation(pageParamNode.type ?? pageParamNode);
79
- paginatableMethods.set(methodNameLower, {
93
+ paginatableMethods.set(key, {
80
94
  type: pageParamType ?? "unknown",
81
95
  typeKind: getPageParamTypeKind(resolvedType),
82
96
  });
@@ -100,11 +114,15 @@ export async function parseOperations(project, pageParam) {
100
114
  // via extractPropertiesFromObjectParam for type alias properties (path, url).
101
115
  const sdkParams = getVariableArrowFunctionParameters(desc.method);
102
116
  const allParamsOptional = sdkParams.length === 0 || sdkParams[0].isOptional();
103
- const pageParamInfo = paginatableMethods.get(methodName);
117
+ const dataTypeName = getDataTypeNameFromSignature(sdkParams[0]);
118
+ const pageParamInfo = dataTypeName
119
+ ? paginatableMethods.get(dataTypeName)
120
+ : undefined;
104
121
  const isPaginatable = httpMethod === "GET" && pageParamInfo !== undefined;
105
122
  return {
106
123
  methodName,
107
124
  capitalizedMethodName: capitalizeFirstLetter(methodName),
125
+ dataTypeName,
108
126
  httpMethod,
109
127
  jsDoc: desc.jsDoc,
110
128
  isDeprecated: desc.isDeprecated,
@@ -30,7 +30,7 @@ export declare function buildMutationKeyConst(op: OperationInfo): VariableStatem
30
30
  * Example: export const UseFindPetsKeyFn = (clientOptions: Options<FindPetsData, true> = {}, queryKey?: Array<unknown>) =>
31
31
  * [useFindPetsKey, ...(queryKey ?? [clientOptions])];
32
32
  */
33
- export declare function buildQueryKeyFn(op: OperationInfo, ctx: GenerationContext): VariableStatementStructure;
33
+ export declare function buildQueryKeyFn(op: OperationInfo): VariableStatementStructure;
34
34
  /**
35
35
  * Build mutation key function.
36
36
  * Example: export const UseAddPetKeyFn = (mutationKey?: Array<unknown>) =>
@@ -1,4 +1,5 @@
1
1
  import { StructureKind, VariableDeclarationKind, } from "ts-morph";
2
+ import { getDataTypeName } from "./operationNames.mjs";
2
3
  /**
3
4
  * Build the default response type alias.
4
5
  * Example: export type FindPetsDefaultResponse = Awaited<ReturnType<typeof findPets>>["data"];
@@ -78,10 +79,8 @@ export function buildMutationKeyConst(op) {
78
79
  * Example: export const UseFindPetsKeyFn = (clientOptions: Options<FindPetsData, true> = {}, queryKey?: Array<unknown>) =>
79
80
  * [useFindPetsKey, ...(queryKey ?? [clientOptions])];
80
81
  */
81
- export function buildQueryKeyFn(op, ctx) {
82
- const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
83
- ? `${op.capitalizedMethodName}Data`
84
- : "unknown";
82
+ export function buildQueryKeyFn(op) {
83
+ const dataTypeName = getDataTypeName(op);
85
84
  const params = [];
86
85
  const defaultValue = op.allParamsOptional ? " = {}" : "";
87
86
  params.push(`clientOptions: Options<${dataTypeName}, true>${defaultValue}`);
@@ -126,12 +125,8 @@ export function buildMutationKeyFn(op) {
126
125
  * { query?: Omit<NonNullable<FindPaginatedPetsData["query"]>, "page"> };
127
126
  */
128
127
  export function buildInfiniteClientOptionsType(op, ctx) {
129
- const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
130
- ? `${op.capitalizedMethodName}Data`
131
- : "unknown";
132
- const type = dataTypeName === "unknown"
133
- ? "Options<unknown, true>"
134
- : `Omit<Options<${dataTypeName}, true>, "query"> & { query?: Omit<NonNullable<${dataTypeName}["query"]>, "${ctx.pageParam}"> }`;
128
+ const dataTypeName = getDataTypeName(op);
129
+ const type = `Omit<Options<${dataTypeName}, true>, "query"> & { query?: Omit<NonNullable<${dataTypeName}["query"]>, "${ctx.pageParam}"> }`;
135
130
  return {
136
131
  kind: StructureKind.TypeAlias,
137
132
  isExported: true,
@@ -1,19 +1,6 @@
1
1
  import { StructureKind, VariableDeclarationKind, } from "ts-morph";
2
2
  import { SDK_CALL_ARGS } from "./buildQueryHooks.mjs";
3
- /**
4
- * Get the error type string based on client type.
5
- */
6
- function getErrorType(op, ctx) {
7
- const errorTypeName = `${op.capitalizedMethodName}Error`;
8
- // Operations without error responses have no generated Error type
9
- const errorType = ctx.modelNames.includes(errorTypeName)
10
- ? errorTypeName
11
- : "unknown";
12
- if (ctx.client === "@hey-api/client-axios") {
13
- return `AxiosError<${errorType}>`;
14
- }
15
- return errorType;
16
- }
3
+ import { getDataTypeName, getErrorType } from "./operationNames.mjs";
17
4
  /**
18
5
  * Build useMutation hook.
19
6
  * Example:
@@ -30,10 +17,7 @@ export function buildUseMutationHook(op, ctx) {
30
17
  const hookName = `use${op.capitalizedMethodName}`;
31
18
  const errorType = getErrorType(op, ctx);
32
19
  const dataTypeDefault = `Common.${op.capitalizedMethodName}MutationResult`;
33
- const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
34
- ? `${op.capitalizedMethodName}Data`
35
- : "unknown";
36
- const optionsType = `Options<${dataTypeName}, true>`;
20
+ const optionsType = `Options<${getDataTypeName(op)}, true>`;
37
21
  const mutationFn = `clientOptions => ${op.methodName}(${SDK_CALL_ARGS}) as unknown as Promise<TData>`;
38
22
  const body = `useMutation<TData, TError, ${optionsType}, TContext>({ mutationKey: Common.Use${op.capitalizedMethodName}KeyFn(mutationKey), mutationFn: ${mutationFn}, ...options })`;
39
23
  return {
@@ -1,10 +1,5 @@
1
1
  import { type VariableStatementStructure } from "ts-morph";
2
2
  import type { GenerationContext, OperationInfo } from "../types.mjs";
3
- /**
4
- * Resolve the generated Data type name for an operation, falling back to
5
- * unknown when the operation has no generated Data type.
6
- */
7
- export declare function getDataTypeName(op: OperationInfo, ctx: GenerationContext): string;
8
3
  /**
9
4
  * SDK call arguments shared by every generated queryFn/mutationFn.
10
5
  * throwOnError: true forces the SDK call to reject on error responses; the
@@ -19,17 +14,28 @@ export declare function getPageParamType(op: OperationInfo): string;
19
14
  /**
20
15
  * Build the client options parameter string.
21
16
  */
22
- export declare function buildClientOptionsParam(op: OperationInfo, ctx: GenerationContext): string;
17
+ export declare function buildClientOptionsParam(op: OperationInfo): string;
23
18
  /**
24
19
  * Build the clientOptions parameter typed with the page-less infinite
25
20
  * options type — the page parameter is supplied by TanStack Query's
26
21
  * pageParam mechanism.
27
22
  */
28
23
  export declare function buildInfiniteClientOptionsParam(op: OperationInfo): string;
24
+ /**
25
+ * The type of a single page. TanStack Query instantiates the infinite options
26
+ * with this as TQueryFnData, so it is what `getNextPageParam` receives as
27
+ * `lastPage` (#203). NonNullable because `throwOnError: true` means a resolved
28
+ * page is always present.
29
+ */
30
+ export declare function getPageType(op: OperationInfo): string;
29
31
  /**
30
32
  * Build the paginated SDK call shared by every infinite query builder.
33
+ * The resolved response is narrowed to the page type so it matches the
34
+ * TQueryFnData the infinite options are instantiated with (#203). Spelled as
35
+ * a cast rather than `!` because generated code is linted downstream, and a
36
+ * non-null assertion trips biome's noNonNullAssertion.
31
37
  */
32
- export declare function buildPagedQueryFn(op: OperationInfo, ctx: GenerationContext, castTData: boolean): string;
38
+ export declare function buildPagedQueryFn(op: OperationInfo, ctx: GenerationContext): string;
33
39
  /**
34
40
  * Format the initialPageParam literal. Emits `undefined` when the caller opted
35
41
  * to omit it (#177); otherwise a numeric literal when possible so the inferred
@@ -51,8 +57,15 @@ export declare function buildGetNextPageParamExpr(ctx: GenerationContext, op?: O
51
57
  * Build an options type where the pagination fields TanStack Query marks as
52
58
  * required become optional overrides: the generator supplies them, and
53
59
  * callers may replace them for custom pagination schemes (#156, #146).
60
+ *
61
+ * The first type argument is TQueryFnData — a single page — not TData (#203).
62
+ * Passing TData there made `getNextPageParam` receive the aggregated
63
+ * `InfiniteData<...>` as `lastPage`, so any custom pagination logic failed to
64
+ * compile. Only the first three type arguments are written: TanStack Query
65
+ * dropped TQueryData from `UseInfiniteQueryOptions` within the v5 line, so
66
+ * positions beyond TData are not stable across the supported peer range.
54
67
  */
55
- export declare function buildOverridableInfiniteOptionsType(optionsTypeName: string): string;
68
+ export declare function buildOverridableInfiniteOptionsType(optionsTypeName: string, pageType: string): string;
56
69
  /**
57
70
  * Build useQuery hook.
58
71
  * Example:
@@ -89,14 +102,14 @@ export declare function buildUseSuspenseInfiniteQueryHook(op: OperationInfo, ctx
89
102
  * ...options
90
103
  * });
91
104
  */
92
- export declare function buildPrefetchFn(op: OperationInfo, ctx: GenerationContext): VariableStatementStructure;
105
+ export declare function buildPrefetchFn(op: OperationInfo): VariableStatementStructure;
93
106
  /**
94
107
  * Build prefetchInfiniteQuery function for a paginatable operation.
95
108
  * Example:
96
- * export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit<FetchInfiniteQueryOptions<Common.FindPaginatedPetsDefaultResponse>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) =>
109
+ * export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit<FetchInfiniteQueryOptions<NonNullable<Common.FindPaginatedPetsDefaultResponse>>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial<Pick<FetchInfiniteQueryOptions<NonNullable<Common.FindPaginatedPetsDefaultResponse>>, "initialPageParam">> & { getNextPageParam?: GetNextPageParamFunction<unknown, NonNullable<Common.FindPaginatedPetsDefaultResponse>> }) =>
97
110
  * queryClient.prefetchInfiniteQuery({
98
111
  * queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions),
99
- * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data),
112
+ * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data as NonNullable<Common.FindPaginatedPetsDefaultResponse>),
100
113
  * initialPageParam: 1,
101
114
  * getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage,
102
115
  * ...options
@@ -113,4 +126,4 @@ export declare function buildPrefetchInfiniteQueryFn(op: OperationInfo, ctx: Gen
113
126
  * ...options
114
127
  * });
115
128
  */
116
- export declare function buildEnsureQueryDataFn(op: OperationInfo, ctx: GenerationContext): VariableStatementStructure;
129
+ export declare function buildEnsureQueryDataFn(op: OperationInfo): VariableStatementStructure;
@@ -1,27 +1,5 @@
1
1
  import { StructureKind, VariableDeclarationKind, } from "ts-morph";
2
- /**
3
- * Get the error type string based on client type.
4
- */
5
- function getErrorType(op, ctx) {
6
- const errorTypeName = `${op.capitalizedMethodName}Error`;
7
- // Operations without error responses have no generated Error type
8
- const errorType = ctx.modelNames.includes(errorTypeName)
9
- ? errorTypeName
10
- : "unknown";
11
- if (ctx.client === "@hey-api/client-axios") {
12
- return `AxiosError<${errorType}>`;
13
- }
14
- return errorType;
15
- }
16
- /**
17
- * Resolve the generated Data type name for an operation, falling back to
18
- * unknown when the operation has no generated Data type.
19
- */
20
- export function getDataTypeName(op, ctx) {
21
- return ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
22
- ? `${op.capitalizedMethodName}Data`
23
- : "unknown";
24
- }
2
+ import { getDataTypeName, getErrorType } from "./operationNames.mjs";
25
3
  /**
26
4
  * SDK call arguments shared by every generated queryFn/mutationFn.
27
5
  * throwOnError: true forces the SDK call to reject on error responses; the
@@ -38,8 +16,8 @@ export function getPageParamType(op) {
38
16
  /**
39
17
  * Build the client options parameter string.
40
18
  */
41
- export function buildClientOptionsParam(op, ctx) {
42
- const dataTypeName = getDataTypeName(op, ctx);
19
+ export function buildClientOptionsParam(op) {
20
+ const dataTypeName = getDataTypeName(op);
43
21
  const hasParams = op.parameters.length > 0;
44
22
  if (!hasParams) {
45
23
  return `clientOptions: Options<${dataTypeName}, true> = {}`;
@@ -56,14 +34,25 @@ export function buildInfiniteClientOptionsParam(op) {
56
34
  const defaultValue = op.allParamsOptional ? " = {}" : "";
57
35
  return `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`;
58
36
  }
37
+ /**
38
+ * The type of a single page. TanStack Query instantiates the infinite options
39
+ * with this as TQueryFnData, so it is what `getNextPageParam` receives as
40
+ * `lastPage` (#203). NonNullable because `throwOnError: true` means a resolved
41
+ * page is always present.
42
+ */
43
+ export function getPageType(op) {
44
+ return `NonNullable<Common.${op.capitalizedMethodName}DefaultResponse>`;
45
+ }
59
46
  /**
60
47
  * Build the paginated SDK call shared by every infinite query builder.
48
+ * The resolved response is narrowed to the page type so it matches the
49
+ * TQueryFnData the infinite options are instantiated with (#203). Spelled as
50
+ * a cast rather than `!` because generated code is linted downstream, and a
51
+ * non-null assertion trips biome's noNonNullAssertion.
61
52
  */
62
- export function buildPagedQueryFn(op, ctx, castTData) {
63
- const dataTypeName = getDataTypeName(op, ctx);
64
- const thenClause = castTData
65
- ? ".then(response => response.data as TData) as TData"
66
- : ".then(response => response.data)";
53
+ export function buildPagedQueryFn(op, ctx) {
54
+ const dataTypeName = getDataTypeName(op);
55
+ const thenClause = `.then(response => response.data as ${getPageType(op)})`;
67
56
  const pageParamType = getPageParamType(op);
68
57
  // When the initial page param is omitted, the first request must send no
69
58
  // page param at all, so spread it in only once TanStack Query provides one.
@@ -116,9 +105,16 @@ export function buildGetNextPageParamExpr(ctx, op) {
116
105
  * Build an options type where the pagination fields TanStack Query marks as
117
106
  * required become optional overrides: the generator supplies them, and
118
107
  * callers may replace them for custom pagination schemes (#156, #146).
108
+ *
109
+ * The first type argument is TQueryFnData — a single page — not TData (#203).
110
+ * Passing TData there made `getNextPageParam` receive the aggregated
111
+ * `InfiniteData<...>` as `lastPage`, so any custom pagination logic failed to
112
+ * compile. Only the first three type arguments are written: TanStack Query
113
+ * dropped TQueryData from `UseInfiniteQueryOptions` within the v5 line, so
114
+ * positions beyond TData are not stable across the supported peer range.
119
115
  */
120
- export function buildOverridableInfiniteOptionsType(optionsTypeName) {
121
- const instantiated = `${optionsTypeName}<TData, TError>`;
116
+ export function buildOverridableInfiniteOptionsType(optionsTypeName, pageType) {
117
+ const instantiated = `${optionsTypeName}<${pageType}, TError, TData>`;
122
118
  return `Omit<${instantiated}, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial<Pick<${instantiated}, "initialPageParam" | "getNextPageParam">>`;
123
119
  }
124
120
  /**
@@ -138,7 +134,7 @@ export function buildUseQueryHook(op, ctx) {
138
134
  const hookName = `use${op.capitalizedMethodName}`;
139
135
  const errorType = getErrorType(op, ctx);
140
136
  const dataTypeDefault = `Common.${op.capitalizedMethodName}DefaultResponse`;
141
- const clientOptionsParam = buildClientOptionsParam(op, ctx);
137
+ const clientOptionsParam = buildClientOptionsParam(op);
142
138
  const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data as TData) as TData`;
143
139
  const body = `useQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
144
140
  return {
@@ -162,7 +158,7 @@ export function buildUseSuspenseQueryHook(op, ctx) {
162
158
  const hookName = `use${op.capitalizedMethodName}Suspense`;
163
159
  const errorType = getErrorType(op, ctx);
164
160
  const dataTypeDefault = `NonNullable<Common.${op.capitalizedMethodName}DefaultResponse>`;
165
- const clientOptionsParam = buildClientOptionsParam(op, ctx);
161
+ const clientOptionsParam = buildClientOptionsParam(op);
166
162
  const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data as TData) as TData`;
167
163
  const body = `useSuspenseQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;
168
164
  return {
@@ -201,7 +197,7 @@ function buildInfiniteHook(op, ctx, suspense) {
201
197
  const dataTypeDefault = suspense
202
198
  ? `InfiniteData<NonNullable<${baseDataType}>>`
203
199
  : `InfiniteData<${baseDataType}>`;
204
- const queryFn = buildPagedQueryFn(op, ctx, true);
200
+ const queryFn = buildPagedQueryFn(op, ctx);
205
201
  const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx, op)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx, op)}`;
206
202
  const body = `${hookCall}({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
207
203
  return {
@@ -213,7 +209,7 @@ function buildInfiniteHook(op, ctx, suspense) {
213
209
  declarations: [
214
210
  {
215
211
  name: hookName,
216
- initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${buildInfiniteClientOptionsParam(op)}, queryKey?: TQueryKey, options?: ${buildOverridableInfiniteOptionsType(optionsTypeName)}) => ${body}`,
212
+ initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${buildInfiniteClientOptionsParam(op)}, queryKey?: TQueryKey, options?: ${buildOverridableInfiniteOptionsType(optionsTypeName, getPageType(op))}) => ${body}`,
217
213
  },
218
214
  ],
219
215
  };
@@ -240,7 +236,7 @@ export function buildUseSuspenseInfiniteQueryHook(op, ctx) {
240
236
  * ...options
241
237
  * });
242
238
  */
243
- export function buildPrefetchFn(op, ctx) {
239
+ export function buildPrefetchFn(op) {
244
240
  const fnName = `prefetchUse${op.capitalizedMethodName}`;
245
241
  const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data)`;
246
242
  const optionsParam = `options?: Omit<FetchQueryOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn">`;
@@ -254,7 +250,7 @@ export function buildPrefetchFn(op, ctx) {
254
250
  declarations: [
255
251
  {
256
252
  name: fnName,
257
- initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op, ctx)}, ${optionsParam}) => ${body}`,
253
+ initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op)}, ${optionsParam}) => ${body}`,
258
254
  },
259
255
  ],
260
256
  };
@@ -262,10 +258,10 @@ export function buildPrefetchFn(op, ctx) {
262
258
  /**
263
259
  * Build prefetchInfiniteQuery function for a paginatable operation.
264
260
  * Example:
265
- * export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit<FetchInfiniteQueryOptions<Common.FindPaginatedPetsDefaultResponse>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) =>
261
+ * export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit<FetchInfiniteQueryOptions<NonNullable<Common.FindPaginatedPetsDefaultResponse>>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial<Pick<FetchInfiniteQueryOptions<NonNullable<Common.FindPaginatedPetsDefaultResponse>>, "initialPageParam">> & { getNextPageParam?: GetNextPageParamFunction<unknown, NonNullable<Common.FindPaginatedPetsDefaultResponse>> }) =>
266
262
  * queryClient.prefetchInfiniteQuery({
267
263
  * queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions),
268
- * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data),
264
+ * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data as NonNullable<Common.FindPaginatedPetsDefaultResponse>),
269
265
  * initialPageParam: 1,
270
266
  * getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage,
271
267
  * ...options
@@ -276,9 +272,17 @@ export function buildPrefetchInfiniteQueryFn(op, ctx) {
276
272
  return null;
277
273
  }
278
274
  const fnName = `prefetchUse${op.capitalizedMethodName}Infinite`;
279
- const queryFn = buildPagedQueryFn(op, ctx, false);
275
+ const queryFn = buildPagedQueryFn(op, ctx);
280
276
  const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx, op)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx, op)}`;
281
- const optionsParam = `options?: Omit<FetchInfiniteQueryOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">`;
277
+ // The pagination fields are re-added as optional overrides: `...options` is
278
+ // spread after the defaults, so replacing them works at runtime and must not
279
+ // be forbidden by the type (#203). getNextPageParam is spelled out rather
280
+ // than Picked, because FetchInfiniteQueryOptions only exposes it on the
281
+ // union member that also requires `pages`.
282
+ const pageType = getPageType(op);
283
+ const instantiated = `FetchInfiniteQueryOptions<${pageType}>`;
284
+ const overrides = `Partial<Pick<${instantiated}, "initialPageParam">> & { getNextPageParam?: GetNextPageParamFunction<unknown, ${pageType}> }`;
285
+ const optionsParam = `options?: Omit<${instantiated}, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & ${overrides}`;
282
286
  const body = `queryClient.prefetchInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
283
287
  return {
284
288
  kind: StructureKind.VariableStatement,
@@ -304,7 +308,7 @@ export function buildPrefetchInfiniteQueryFn(op, ctx) {
304
308
  * ...options
305
309
  * });
306
310
  */
307
- export function buildEnsureQueryDataFn(op, ctx) {
311
+ export function buildEnsureQueryDataFn(op) {
308
312
  const fnName = `ensureUse${op.capitalizedMethodName}Data`;
309
313
  const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data)`;
310
314
  const optionsParam = `options?: Omit<EnsureQueryDataOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn">`;
@@ -318,7 +322,7 @@ export function buildEnsureQueryDataFn(op, ctx) {
318
322
  declarations: [
319
323
  {
320
324
  name: fnName,
321
- initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op, ctx)}, ${optionsParam}) => ${body}`,
325
+ initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op)}, ${optionsParam}) => ${body}`,
322
326
  },
323
327
  ],
324
328
  };
@@ -12,17 +12,18 @@ import type { GenerationContext, OperationInfo } from "../types.mjs";
12
12
  * queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data),
13
13
  * });
14
14
  */
15
- export declare function buildQueryOptionsFn(op: OperationInfo, ctx: GenerationContext): VariableStatementStructure;
15
+ export declare function buildQueryOptionsFn(op: OperationInfo): VariableStatementStructure;
16
16
  /**
17
17
  * Build an infiniteQueryOptions factory for a paginatable GET operation.
18
18
  * Uses the dedicated infinite query key and page-less options type.
19
19
  * Example:
20
- * export const findPaginatedPetsInfiniteOptions = (clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>) =>
20
+ * export const findPaginatedPetsInfiniteOptions = (clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>, options?: Partial<Pick<UseInfiniteQueryOptions<NonNullable<Common.FindPaginatedPetsDefaultResponse>>, "initialPageParam" | "getNextPageParam">>) =>
21
21
  * infiniteQueryOptions({
22
22
  * queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey),
23
- * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data),
23
+ * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data as NonNullable<Common.FindPaginatedPetsDefaultResponse>),
24
24
  * initialPageParam: 1,
25
25
  * getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage,
26
+ * ...options,
26
27
  * });
27
28
  */
28
29
  export declare function buildInfiniteQueryOptionsFn(op: OperationInfo, ctx: GenerationContext): VariableStatementStructure | null;
@@ -1,5 +1,5 @@
1
1
  import { StructureKind, VariableDeclarationKind, } from "ts-morph";
2
- import { buildClientOptionsParam, buildGetNextPageParamExpr, buildInfiniteClientOptionsParam, buildPagedQueryFn, formatInitialPageParam, QUERY_SDK_CALL_ARGS, } from "./buildQueryHooks.mjs";
2
+ import { buildClientOptionsParam, buildGetNextPageParamExpr, buildInfiniteClientOptionsParam, buildPagedQueryFn, formatInitialPageParam, getPageType, QUERY_SDK_CALL_ARGS, } from "./buildQueryHooks.mjs";
3
3
  /**
4
4
  * Build a queryOptions factory for a GET operation.
5
5
  * The factory centralizes queryKey and queryFn so they can be reused with
@@ -12,9 +12,9 @@ import { buildClientOptionsParam, buildGetNextPageParamExpr, buildInfiniteClient
12
12
  * queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data),
13
13
  * });
14
14
  */
15
- export function buildQueryOptionsFn(op, ctx) {
15
+ export function buildQueryOptionsFn(op) {
16
16
  const fnName = `${op.methodName}Options`;
17
- const clientOptionsParam = buildClientOptionsParam(op, ctx);
17
+ const clientOptionsParam = buildClientOptionsParam(op);
18
18
  const queryFn = `({ signal }) => ${op.methodName}(${QUERY_SDK_CALL_ARGS}).then(response => response.data)`;
19
19
  const body = `queryOptions({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn} })`;
20
20
  return {
@@ -35,12 +35,13 @@ export function buildQueryOptionsFn(op, ctx) {
35
35
  * Build an infiniteQueryOptions factory for a paginatable GET operation.
36
36
  * Uses the dedicated infinite query key and page-less options type.
37
37
  * Example:
38
- * export const findPaginatedPetsInfiniteOptions = (clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>) =>
38
+ * export const findPaginatedPetsInfiniteOptions = (clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>, options?: Partial<Pick<UseInfiniteQueryOptions<NonNullable<Common.FindPaginatedPetsDefaultResponse>>, "initialPageParam" | "getNextPageParam">>) =>
39
39
  * infiniteQueryOptions({
40
40
  * queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey),
41
- * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data),
41
+ * queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data as NonNullable<Common.FindPaginatedPetsDefaultResponse>),
42
42
  * initialPageParam: 1,
43
43
  * getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage,
44
+ * ...options,
44
45
  * });
45
46
  */
46
47
  export function buildInfiniteQueryOptionsFn(op, ctx) {
@@ -48,9 +49,13 @@ export function buildInfiniteQueryOptionsFn(op, ctx) {
48
49
  return null;
49
50
  }
50
51
  const fnName = `${op.methodName}InfiniteOptions`;
51
- const queryFn = buildPagedQueryFn(op, ctx, false);
52
+ const queryFn = buildPagedQueryFn(op, ctx);
52
53
  const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx, op)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx, op)}`;
53
- const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions} })`;
54
+ // Only the pagination fields are overridable here: the factory's return type
55
+ // is what every downstream consumer infers from, and a wider options type
56
+ // (select, placeholderData, ...) would make that inference ambiguous (#203).
57
+ const optionsParam = `options?: Partial<Pick<UseInfiniteQueryOptions<${getPageType(op)}>, "initialPageParam" | "getNextPageParam">>`;
58
+ const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
54
59
  return {
55
60
  kind: StructureKind.VariableStatement,
56
61
  // Copy the operation's JSDoc (description and @deprecated) from the SDK function
@@ -60,7 +65,7 @@ export function buildInfiniteQueryOptionsFn(op, ctx) {
60
65
  declarations: [
61
66
  {
62
67
  name: fnName,
63
- initializer: `(${buildInfiniteClientOptionsParam(op)}, queryKey?: Array<unknown>) => ${body}`,
68
+ initializer: `(${buildInfiniteClientOptionsParam(op)}, queryKey?: Array<unknown>, ${optionsParam}) => ${body}`,
64
69
  },
65
70
  ],
66
71
  };
@@ -3,30 +3,20 @@ import { buildDefaultResponseType, buildInfiniteClientOptionsType, buildInfinite
3
3
  import { buildUseMutationHook } from "./buildMutationHooks.mjs";
4
4
  import { buildEnsureQueryDataFn, buildPrefetchFn, buildPrefetchInfiniteQueryFn, buildUseInfiniteQueryHook, buildUseQueryHook, buildUseSuspenseInfiniteQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs";
5
5
  import { buildInfiniteQueryOptionsFn, buildQueryOptionsFn, } from "./buildQueryOptions.mjs";
6
- import { buildAxiosErrorImport, buildClientImport, buildCommonImport, buildModelImport, buildQueryImport, buildQueryOptionsImport, buildServiceImport, createGenerationProject, } from "./projectFactory.mjs";
6
+ import { buildCommonFileImports, buildHookFileImports, buildQueryOptionsFileImports, createGenerationProject, } from "./projectFactory.mjs";
7
7
  /**
8
- * Build imports for common.ts file.
8
+ * Add one variable statement per operation, skipping the ones the builder
9
+ * declines. A builder returns null when the operation is out of its scope —
10
+ * every infinite-query builder returns null for non-paginatable operations,
11
+ * which is how the paginatable subset gets selected.
9
12
  */
10
- function buildCommonFileImports(ctx) {
11
- const imports = [
12
- buildClientImport(ctx),
13
- buildQueryImport(),
14
- buildServiceImport(ctx),
15
- ];
16
- const modelImport = buildModelImport(ctx);
17
- if (modelImport) {
18
- imports.push(modelImport);
19
- }
20
- if (ctx.client === "@hey-api/client-axios") {
21
- imports.push(buildAxiosErrorImport());
13
+ function addStatements(sourceFile, operations, build) {
14
+ for (const op of operations) {
15
+ const statement = build(op);
16
+ if (statement) {
17
+ sourceFile.addVariableStatement(statement);
18
+ }
22
19
  }
23
- return imports;
24
- }
25
- /**
26
- * Build imports for hook files (queries, suspense, infinite, prefetch, ensure).
27
- */
28
- function buildHookFileImports(ctx) {
29
- return [buildCommonImport(), ...buildCommonFileImports(ctx)];
30
20
  }
31
21
  /**
32
22
  * Generate the index.ts file content.
@@ -51,7 +41,7 @@ function generateCommonFile(operations, ctx) {
51
41
  sourceFile.addTypeAlias(buildDefaultResponseType(op));
52
42
  sourceFile.addTypeAlias(buildQueryResultType(op));
53
43
  sourceFile.addVariableStatement(buildQueryKeyConst(op));
54
- sourceFile.addVariableStatement(buildQueryKeyFn(op, ctx));
44
+ sourceFile.addVariableStatement(buildQueryKeyFn(op));
55
45
  }
56
46
  // Add dedicated infinite query types and keys for paginatable operations
57
47
  for (const op of getOperations.filter((o) => o.isPaginatable)) {
@@ -95,28 +85,14 @@ function generateQueryOptionsFile(operations, ctx) {
95
85
  const project = createGenerationProject();
96
86
  const sourceFile = project.createSourceFile(`${OpenApiRqFiles.queryOptions}.ts`, undefined, { overwrite: true });
97
87
  // Add imports
98
- const imports = [
99
- buildCommonImport(),
100
- buildQueryOptionsImport(),
101
- buildClientImport(ctx),
102
- buildServiceImport(ctx),
103
- ];
104
- const modelImport = buildModelImport(ctx);
105
- if (modelImport) {
106
- imports.push(modelImport);
107
- }
108
- sourceFile.addImportDeclarations(imports);
88
+ sourceFile.addImportDeclarations(buildQueryOptionsFileImports(ctx));
109
89
  // Only GET operations have query options
110
90
  const getOperations = operations.filter((op) => op.httpMethod === "GET");
111
91
  for (const op of getOperations) {
112
- sourceFile.addVariableStatement(buildQueryOptionsFn(op, ctx));
113
- }
114
- for (const op of getOperations) {
115
- const infiniteOptions = buildInfiniteQueryOptionsFn(op, ctx);
116
- if (infiniteOptions) {
117
- sourceFile.addVariableStatement(infiniteOptions);
118
- }
92
+ sourceFile.addVariableStatement(buildQueryOptionsFn(op));
119
93
  }
94
+ // Add infiniteQueryOptions factories
95
+ addStatements(sourceFile, getOperations, (op) => buildInfiniteQueryOptionsFn(op, ctx));
120
96
  return sourceFile.getFullText();
121
97
  }
122
98
  /**
@@ -133,13 +109,8 @@ function generateSuspenseFile(operations, ctx) {
133
109
  for (const op of getOperations) {
134
110
  sourceFile.addVariableStatement(buildUseSuspenseQueryHook(op, ctx));
135
111
  }
136
- // Add useSuspenseInfiniteQuery hooks for paginatable operations
137
- for (const op of getOperations.filter((o) => o.isPaginatable)) {
138
- const hook = buildUseSuspenseInfiniteQueryHook(op, ctx);
139
- if (hook) {
140
- sourceFile.addVariableStatement(hook);
141
- }
142
- }
112
+ // Add useSuspenseInfiniteQuery hooks
113
+ addStatements(sourceFile, getOperations, (op) => buildUseSuspenseInfiniteQueryHook(op, ctx));
143
114
  return sourceFile.getFullText();
144
115
  }
145
116
  /**
@@ -150,15 +121,10 @@ function generateInfiniteQueriesFile(operations, ctx) {
150
121
  const sourceFile = project.createSourceFile(`${OpenApiRqFiles.infiniteQueries}.ts`, undefined, { overwrite: true });
151
122
  // Add imports
152
123
  sourceFile.addImportDeclarations(buildHookFileImports(ctx));
153
- // Only paginatable GET operations
154
- const paginatableOperations = operations.filter((op) => op.httpMethod === "GET" && op.isPaginatable);
124
+ // Only GET operations can be paginatable
125
+ const getOperations = operations.filter((op) => op.httpMethod === "GET");
155
126
  // Add useInfiniteQuery hooks
156
- for (const op of paginatableOperations) {
157
- const hook = buildUseInfiniteQueryHook(op, ctx);
158
- if (hook) {
159
- sourceFile.addVariableStatement(hook);
160
- }
161
- }
127
+ addStatements(sourceFile, getOperations, (op) => buildUseInfiniteQueryHook(op, ctx));
162
128
  return sourceFile.getFullText();
163
129
  }
164
130
  /**
@@ -173,15 +139,10 @@ function generatePrefetchFile(operations, ctx) {
173
139
  const getOperations = operations.filter((op) => op.httpMethod === "GET");
174
140
  // Add prefetch functions
175
141
  for (const op of getOperations) {
176
- sourceFile.addVariableStatement(buildPrefetchFn(op, ctx));
177
- }
178
- // Add prefetchInfiniteQuery functions for paginatable operations
179
- for (const op of getOperations.filter((o) => o.isPaginatable)) {
180
- const fn = buildPrefetchInfiniteQueryFn(op, ctx);
181
- if (fn) {
182
- sourceFile.addVariableStatement(fn);
183
- }
142
+ sourceFile.addVariableStatement(buildPrefetchFn(op));
184
143
  }
144
+ // Add prefetchInfiniteQuery functions
145
+ addStatements(sourceFile, getOperations, (op) => buildPrefetchInfiniteQueryFn(op, ctx));
185
146
  return sourceFile.getFullText();
186
147
  }
187
148
  /**
@@ -196,7 +157,7 @@ function generateEnsureQueryDataFile(operations, ctx) {
196
157
  const getOperations = operations.filter((op) => op.httpMethod === "GET");
197
158
  // Add ensureQueryData functions
198
159
  for (const op of getOperations) {
199
- sourceFile.addVariableStatement(buildEnsureQueryDataFn(op, ctx));
160
+ sourceFile.addVariableStatement(buildEnsureQueryDataFn(op));
200
161
  }
201
162
  return sourceFile.getFullText();
202
163
  }
@@ -0,0 +1,16 @@
1
+ import type { GenerationContext, OperationInfo } from "../types.mjs";
2
+ /**
3
+ * Resolve the generated Data type name for an operation, falling back to
4
+ * unknown when the SDK signature exposes no Data type.
5
+ * See OperationInfo.dataTypeName for why the name is read from the signature
6
+ * instead of derived from the method name (#213).
7
+ */
8
+ export declare function getDataTypeName(op: OperationInfo): string;
9
+ /**
10
+ * Get the error type string based on client type.
11
+ * The Error type shares its stem with the Data type — hey-api mints both from
12
+ * the operationId (see OperationInfo.dataTypeName, #213) — so the stem comes
13
+ * from `dataTypeName` rather than the method name. The modelNames probe stays
14
+ * because operations without error responses have no generated Error type.
15
+ */
16
+ export declare function getErrorType(op: OperationInfo, ctx: GenerationContext): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Resolve the generated Data type name for an operation, falling back to
3
+ * unknown when the SDK signature exposes no Data type.
4
+ * See OperationInfo.dataTypeName for why the name is read from the signature
5
+ * instead of derived from the method name (#213).
6
+ */
7
+ export function getDataTypeName(op) {
8
+ return op.dataTypeName ?? "unknown";
9
+ }
10
+ /**
11
+ * Get the error type string based on client type.
12
+ * The Error type shares its stem with the Data type — hey-api mints both from
13
+ * the operationId (see OperationInfo.dataTypeName, #213) — so the stem comes
14
+ * from `dataTypeName` rather than the method name. The modelNames probe stays
15
+ * because operations without error responses have no generated Error type.
16
+ */
17
+ export function getErrorType(op, ctx) {
18
+ const stem = op.dataTypeName
19
+ ? op.dataTypeName.replace(/Data$/, "")
20
+ : op.capitalizedMethodName;
21
+ const errorTypeName = `${stem}Error`;
22
+ const errorType = ctx.modelNames.includes(errorTypeName)
23
+ ? errorTypeName
24
+ : "unknown";
25
+ if (ctx.client === "@hey-api/client-axios") {
26
+ return `AxiosError<${errorType}>`;
27
+ }
28
+ return errorType;
29
+ }
@@ -45,3 +45,9 @@ export declare function buildCommonFileImports(ctx: GenerationContext): ImportDe
45
45
  * Build all imports needed for hook files (queries, suspense, infinite).
46
46
  */
47
47
  export declare function buildHookFileImports(ctx: GenerationContext): ImportDeclarationStructure[];
48
+ /**
49
+ * Build all imports needed for the queryOptions file.
50
+ * Narrower than the hook file imports: queryOptions only needs the
51
+ * queryOptions/infiniteQueryOptions helpers, not the TanStack hooks.
52
+ */
53
+ export declare function buildQueryOptionsFileImports(ctx: GenerationContext): ImportDeclarationStructure[];
@@ -53,6 +53,7 @@ export function buildQueryImport() {
53
53
  { name: "InfiniteData", isTypeOnly: true },
54
54
  { name: "FetchQueryOptions", isTypeOnly: true },
55
55
  { name: "FetchInfiniteQueryOptions", isTypeOnly: true },
56
+ { name: "GetNextPageParamFunction", isTypeOnly: true },
56
57
  { name: "EnsureQueryDataOptions", isTypeOnly: true },
57
58
  ],
58
59
  };
@@ -64,7 +65,11 @@ export function buildQueryOptionsImport() {
64
65
  return {
65
66
  kind: StructureKind.ImportDeclaration,
66
67
  moduleSpecifier: "@tanstack/react-query",
67
- namedImports: [{ name: "queryOptions" }, { name: "infiniteQueryOptions" }],
68
+ namedImports: [
69
+ { name: "queryOptions" },
70
+ { name: "infiniteQueryOptions" },
71
+ { name: "UseInfiniteQueryOptions", isTypeOnly: true },
72
+ ],
68
73
  };
69
74
  }
70
75
  /**
@@ -137,3 +142,21 @@ export function buildCommonFileImports(ctx) {
137
142
  export function buildHookFileImports(ctx) {
138
143
  return [buildCommonImport(), ...buildCommonFileImports(ctx)];
139
144
  }
145
+ /**
146
+ * Build all imports needed for the queryOptions file.
147
+ * Narrower than the hook file imports: queryOptions only needs the
148
+ * queryOptions/infiniteQueryOptions helpers, not the TanStack hooks.
149
+ */
150
+ export function buildQueryOptionsFileImports(ctx) {
151
+ const imports = [
152
+ buildCommonImport(),
153
+ buildQueryOptionsImport(),
154
+ buildClientImport(ctx),
155
+ buildServiceImport(ctx),
156
+ ];
157
+ const modelImport = buildModelImport(ctx);
158
+ if (modelImport) {
159
+ imports.push(modelImport);
160
+ }
161
+ return imports;
162
+ }
package/dist/types.d.mts CHANGED
@@ -7,6 +7,20 @@ export interface OperationInfo {
7
7
  methodName: string;
8
8
  /** Capitalized method name (e.g., "FindPets") */
9
9
  capitalizedMethodName: string;
10
+ /**
11
+ * Generated Data type name read from the SDK function's own
12
+ * `Options<XData, ThrowOnError>` signature (e.g., "FindPetsData").
13
+ * Not derivable from the method name: for digit-leading operationIds
14
+ * hey-api prefixes the function but strips the digits from the type (#213).
15
+ * Undefined when the SDK signature exposes no Data type.
16
+ *
17
+ * The rule this encodes: names hey-api owns (Data, Error) must be read from
18
+ * the SDK signature or share this field's stem; names this codegen mints
19
+ * itself (DefaultResponse, MutationResult, key fns) may be derived from
20
+ * capitalizedMethodName because they anchor back to the SDK via
21
+ * `typeof methodName`.
22
+ */
23
+ dataTypeName?: string;
10
24
  /** HTTP method (e.g., "GET", "POST", "PUT", "PATCH", "DELETE") */
11
25
  httpMethod: string;
12
26
  /** JSDoc comment string (if present) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@7nohe/openapi-react-query-codegen",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "OpenAPI React Query Codegen",
5
5
  "bin": {
6
6
  "openapi-rq": "dist/cli.mjs"
@@ -1,42 +0,0 @@
1
- import { type VariableStatementStructure } from "ts-morph";
2
- import type { GenerationContext, OperationInfo } from "../types.mjs";
3
- /**
4
- * Build query key constant name (e.g., "findPetsQueryKey").
5
- */
6
- export declare function getQueryKeyName(op: OperationInfo): string;
7
- /**
8
- * Build mutation key constant name (e.g., "addPetMutationKey").
9
- */
10
- export declare function getMutationKeyName(op: OperationInfo): string;
11
- /**
12
- * Build query key fn name (e.g., "FindPetsQueryKeyFn").
13
- */
14
- export declare function getQueryKeyFnName(op: OperationInfo): string;
15
- /**
16
- * Build mutation key fn name (e.g., "AddPetMutationKeyFn").
17
- */
18
- export declare function getMutationKeyFnName(op: OperationInfo): string;
19
- /**
20
- * Build the query key constant export.
21
- * Example: export const findPetsQueryKey = "FindPets";
22
- */
23
- export declare function buildQueryKeyExport(op: OperationInfo): VariableStatementStructure;
24
- /**
25
- * Build the mutation key constant export.
26
- * Example: export const addPetMutationKey = "AddPet";
27
- */
28
- export declare function buildMutationKeyExport(op: OperationInfo): VariableStatementStructure;
29
- /**
30
- * Build the query key function export.
31
- * Example:
32
- * export const FindPetsQueryKeyFn = (clientOptions: Options<FindPetsData, true>, queryKey?: Array<unknown>) =>
33
- * [findPetsQueryKey, ...(queryKey ?? [clientOptions])] as const;
34
- */
35
- export declare function buildQueryKeyFnExport(op: OperationInfo, ctx: GenerationContext): VariableStatementStructure;
36
- /**
37
- * Build the mutation key function export.
38
- * Example:
39
- * export const AddPetMutationKeyFn = (mutationKey?: Array<unknown>) =>
40
- * [addPetMutationKey, ...(mutationKey ?? [])] as const;
41
- */
42
- export declare function buildMutationKeyFnExport(op: OperationInfo): VariableStatementStructure;
@@ -1,110 +0,0 @@
1
- import { StructureKind, VariableDeclarationKind, } from "ts-morph";
2
- /**
3
- * Build query key constant name (e.g., "findPetsQueryKey").
4
- */
5
- export function getQueryKeyName(op) {
6
- return `${op.methodName}QueryKey`;
7
- }
8
- /**
9
- * Build mutation key constant name (e.g., "addPetMutationKey").
10
- */
11
- export function getMutationKeyName(op) {
12
- return `${op.methodName}MutationKey`;
13
- }
14
- /**
15
- * Build query key fn name (e.g., "FindPetsQueryKeyFn").
16
- */
17
- export function getQueryKeyFnName(op) {
18
- return `${op.capitalizedMethodName}QueryKeyFn`;
19
- }
20
- /**
21
- * Build mutation key fn name (e.g., "AddPetMutationKeyFn").
22
- */
23
- export function getMutationKeyFnName(op) {
24
- return `${op.capitalizedMethodName}MutationKeyFn`;
25
- }
26
- /**
27
- * Build the query key constant export.
28
- * Example: export const findPetsQueryKey = "FindPets";
29
- */
30
- export function buildQueryKeyExport(op) {
31
- return {
32
- kind: StructureKind.VariableStatement,
33
- isExported: true,
34
- declarationKind: VariableDeclarationKind.Const,
35
- declarations: [
36
- {
37
- name: getQueryKeyName(op),
38
- initializer: `"${op.capitalizedMethodName}"`,
39
- },
40
- ],
41
- };
42
- }
43
- /**
44
- * Build the mutation key constant export.
45
- * Example: export const addPetMutationKey = "AddPet";
46
- */
47
- export function buildMutationKeyExport(op) {
48
- return {
49
- kind: StructureKind.VariableStatement,
50
- isExported: true,
51
- declarationKind: VariableDeclarationKind.Const,
52
- declarations: [
53
- {
54
- name: getMutationKeyName(op),
55
- initializer: `"${op.capitalizedMethodName}"`,
56
- },
57
- ],
58
- };
59
- }
60
- /**
61
- * Build the query key function export.
62
- * Example:
63
- * export const FindPetsQueryKeyFn = (clientOptions: Options<FindPetsData, true>, queryKey?: Array<unknown>) =>
64
- * [findPetsQueryKey, ...(queryKey ?? [clientOptions])] as const;
65
- */
66
- export function buildQueryKeyFnExport(op, ctx) {
67
- const hasParams = op.parameters.length > 0;
68
- const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
69
- ? `${op.capitalizedMethodName}Data`
70
- : "unknown";
71
- const params = [];
72
- if (hasParams) {
73
- const defaultValue = op.allParamsOptional ? " = {}" : "";
74
- params.push(`clientOptions: Options<${dataTypeName}, true>${defaultValue}`);
75
- }
76
- params.push("queryKey?: Array<unknown>");
77
- const fallbackArray = hasParams ? "[clientOptions]" : "[]";
78
- const body = `[${getQueryKeyName(op)}, ...(queryKey ?? ${fallbackArray})] as const`;
79
- return {
80
- kind: StructureKind.VariableStatement,
81
- isExported: true,
82
- declarationKind: VariableDeclarationKind.Const,
83
- declarations: [
84
- {
85
- name: getQueryKeyFnName(op),
86
- initializer: `(${params.join(", ")}) => ${body}`,
87
- },
88
- ],
89
- };
90
- }
91
- /**
92
- * Build the mutation key function export.
93
- * Example:
94
- * export const AddPetMutationKeyFn = (mutationKey?: Array<unknown>) =>
95
- * [addPetMutationKey, ...(mutationKey ?? [])] as const;
96
- */
97
- export function buildMutationKeyFnExport(op) {
98
- const body = `[${getMutationKeyName(op)}, ...(mutationKey ?? [])] as const`;
99
- return {
100
- kind: StructureKind.VariableStatement,
101
- isExported: true,
102
- declarationKind: VariableDeclarationKind.Const,
103
- declarations: [
104
- {
105
- name: getMutationKeyFnName(op),
106
- initializer: `(mutationKey?: Array<unknown>) => ${body}`,
107
- },
108
- ],
109
- };
110
- }