New:Socket for Asana Is Now Available.Learn more โ†’
Get Started

@7nohe/openapi-react-query-codegen

Package Overview
Dependencies
Maintainers
1
Versions
70
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@7nohe/openapi-react-query-codegen - npm Package Compare versions

Comparing version
3.0.0-beta.2
to
3.0.0-beta.3
+11
-6
dist/tsmorph/buildCommon.mjs

@@ -144,5 +144,7 @@ import { StructureKind, VariableDeclarationKind, } from "ts-morph";

* Build the infinite query key constant.
* Kept distinct from the plain query key so cached InfiniteData never
* collides with plain query data for the same operation (#140).
* Example: export const useFindPaginatedPetsInfiniteKey = "FindPaginatedPetsInfinite";
* Shares the plain query key as its first segment so a single
* `invalidateQueries({ queryKey: [useXKey] })` matches both the plain and the
* infinite cache entries of an operation (#174), while the extra "infinite"
* segment keeps cached InfiniteData from colliding with plain query data (#140).
* Example: export const useFindPaginatedPetsInfiniteKey = [useFindPaginatedPetsKey, "infinite"] as const;
*/

@@ -157,3 +159,3 @@ export function buildInfiniteQueryKeyConst(op) {

name: `use${op.capitalizedMethodName}InfiniteKey`,
initializer: `"${op.capitalizedMethodName}Infinite"`,
initializer: `[use${op.capitalizedMethodName}Key, "infinite"] as const`,
},

@@ -165,4 +167,7 @@ ],

* Build the infinite query key function.
* The custom queryKey argument only replaces the params segment โ€” the
* hierarchical [opKey, "infinite"] prefix is always preserved so
* prefix-based invalidation keeps working.
* Example: export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>) =>
* [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])];
* [...useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])];
*/

@@ -182,3 +187,3 @@ export function buildInfiniteQueryKeyFn(op) {

name: `Use${op.capitalizedMethodName}InfiniteKeyFn`,
initializer: `(${params.join(", ")}) => [use${op.capitalizedMethodName}InfiniteKey, ...(queryKey ?? [clientOptions])]`,
initializer: `(${params.join(", ")}) => [...use${op.capitalizedMethodName}InfiniteKey, ...(queryKey ?? [clientOptions])]`,
},

@@ -185,0 +190,0 @@ ],

import { StructureKind, VariableDeclarationKind, } from "ts-morph";
import { SDK_CALL_ARGS } from "./buildQueryHooks.mjs";
/**

@@ -36,3 +37,3 @@ * Get the error type string based on client type.

const optionsType = `Options<${dataTypeName}, true>`;
const mutationFn = `clientOptions => ${op.methodName}(clientOptions) as unknown as Promise<TData>`;
const mutationFn = `clientOptions => ${op.methodName}(${SDK_CALL_ARGS}) as unknown as Promise<TData>`;
const body = `useMutation<TData, TError, ${optionsType}, TContext>({ mutationKey: Common.Use${op.capitalizedMethodName}KeyFn(mutationKey), mutationFn: ${mutationFn}, ...options })`;

@@ -39,0 +40,0 @@ return {

@@ -17,28 +17,2 @@ import { StructureKind, VariableDeclarationKind, } from "ts-morph";

/**
* Get the data type based on hook type.
*/
function getDataTypeDefault(op, hookType) {
const baseType = `Common.${op.capitalizedMethodName}DefaultResponse`;
if (hookType === "useSuspenseQuery") {
return `NonNullable<${baseType}>`;
}
if (hookType === "useInfiniteQuery") {
return `InfiniteData<${baseType}>`;
}
return baseType;
}
/**
* Get the options type name.
*/
function getOptionsTypeName(hookType) {
switch (hookType) {
case "useSuspenseQuery":
return "UseSuspenseQueryOptions";
case "useInfiniteQuery":
return "UseInfiniteQueryOptions";
default:
return "UseQueryOptions";
}
}
/**
* Resolve the generated Data type name for an operation, falling back to

@@ -53,8 +27,13 @@ * unknown when the operation has no generated Data type.

/**
* SDK call arguments shared by every generated queryFn/mutationFn.
* throwOnError: true forces the SDK call to reject on error responses; the
* hey-api runtime default is false, which would resolve undefined data and
* swallow the error instead of surfacing it to TanStack Query (#172).
*/
export const SDK_CALL_ARGS = "{ ...clientOptions, throwOnError: true }";
/**
* Build the client options parameter string.
*/
export function buildClientOptionsParam(op, ctx) {
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
? `${op.capitalizedMethodName}Data`
: "unknown";
const dataTypeName = getDataTypeName(op, ctx);
const hasParams = op.parameters.length > 0;

@@ -68,2 +47,58 @@ if (!hasParams) {

/**
* Build the clientOptions parameter typed with the page-less infinite
* options type โ€” the page parameter is supplied by TanStack Query's
* pageParam mechanism.
*/
export function buildInfiniteClientOptionsParam(op) {
const defaultValue = op.allParamsOptional ? " = {}" : "";
return `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`;
}
/**
* Build the paginated SDK call shared by every infinite query builder.
*/
export function buildPagedQueryFn(op, ctx, castTData) {
const dataTypeName = getDataTypeName(op, ctx);
const thenClause = castTData
? ".then(response => response.data as TData) as TData"
: ".then(response => response.data)";
return `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number }, throwOnError: true } as Options<${dataTypeName}, true>)${thenClause}`;
}
/**
* Format the initialPageParam literal. Emits a numeric literal when possible
* so the inferred pageParam type matches what getNextPageParam returns.
*/
export function formatInitialPageParam(ctx) {
return /^-?\d+$/.test(ctx.initialPageParam)
? ctx.initialPageParam
: JSON.stringify(ctx.initialPageParam);
}
/**
* Build the nested type for getNextPageParam.
* E.g., "meta.next" becomes "{ meta: { next: number } }"
*/
export function buildNestedNextPageType(nextPageParam) {
const segments = nextPageParam.split(".");
return segments.reduceRight((acc, segment) => {
return `{ ${segment}: ${acc} }`;
}, "number");
}
/**
* Build the getNextPageParam expression. The parameter is annotated because
* not every TanStack entry point contextually types it (prefetchInfiniteQuery
* does not, which would fail noImplicitAny).
*/
export function buildGetNextPageParamExpr(ctx) {
const nestedType = buildNestedNextPageType(ctx.nextPageParam);
return `(response: unknown) => (response as ${nestedType}).${ctx.nextPageParam}`;
}
/**
* Build an options type where the pagination fields TanStack Query marks as
* required become optional overrides: the generator supplies them, and
* callers may replace them for custom pagination schemes (#156, #146).
*/
export function buildOverridableInfiniteOptionsType(optionsTypeName) {
const instantiated = `${optionsTypeName}<TData, TError>`;
return `Omit<${instantiated}, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam"> & Partial<Pick<${instantiated}, "initialPageParam" | "getNextPageParam">>`;
}
/**
* Build useQuery hook.

@@ -77,3 +112,3 @@ * Example:

* queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey),
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data as TData) as TData,
* queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data as TData) as TData,
* ...options

@@ -85,8 +120,5 @@ * });

const errorType = getErrorType(op, ctx);
const dataTypeDefault = getDataTypeDefault(op, "useQuery");
const dataTypeDefault = `Common.${op.capitalizedMethodName}DefaultResponse`;
const clientOptionsParam = buildClientOptionsParam(op, ctx);
const hasParams = op.parameters.length > 0;
// Build the queryFn body
const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data as TData) as TData`;
const body = `useQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;

@@ -113,7 +145,5 @@ return {

const errorType = getErrorType(op, ctx);
const dataTypeDefault = getDataTypeDefault(op, "useSuspenseQuery");
const dataTypeDefault = `NonNullable<Common.${op.capitalizedMethodName}DefaultResponse>`;
const clientOptionsParam = buildClientOptionsParam(op, ctx);
const hasParams = op.parameters.length > 0;
const callArgs = hasParams ? "{ ...clientOptions }" : "{ ...clientOptions }";
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data as TData) as TData`;
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data as TData) as TData`;
const body = `useSuspenseQuery<TData, TError>({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ...options })`;

@@ -135,34 +165,26 @@ return {

/**
* Build the nested type for getNextPageParam.
* E.g., "meta.next" becomes "{ meta: { next: number } }"
* Build a useInfiniteQuery / useSuspenseInfiniteQuery hook. Both variants
* share the infinite query key (and therefore the cache); they differ only
* in the TanStack hook called, the options type, and the NonNullable TData
* default of the suspense variant.
*/
export function buildNestedNextPageType(nextPageParam) {
const segments = nextPageParam.split(".");
return segments.reduceRight((acc, segment) => {
return `{ ${segment}: ${acc} }`;
}, "number");
}
/**
* Build useInfiniteQuery hook.
*/
export function buildUseInfiniteQueryHook(op, ctx) {
function buildInfiniteHook(op, ctx, suspense) {
if (!op.isPaginatable) {
return null;
}
const hookName = `use${op.capitalizedMethodName}Infinite`;
const hookCall = suspense ? "useSuspenseInfiniteQuery" : "useInfiniteQuery";
const optionsTypeName = suspense
? "UseSuspenseInfiniteQueryOptions"
: "UseInfiniteQueryOptions";
const hookName = suspense
? `use${op.capitalizedMethodName}SuspenseInfinite`
: `use${op.capitalizedMethodName}Infinite`;
const errorType = getErrorType(op, ctx);
const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`;
const dataTypeName = getDataTypeName(op, ctx);
// Infinite queries take a dedicated options type that excludes the page
// parameter โ€” it is supplied by TanStack Query's pageParam mechanism
const defaultValue = op.allParamsOptional ? " = {}" : "";
const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`;
// Build the queryFn with pageParam handling
const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam as number } } as Options<${dataTypeName}, true>).then(response => response.data as TData) as TData`;
// Build getNextPageParam with nested type
const nestedType = buildNestedNextPageType(ctx.nextPageParam);
const getNextPageParam = `getNextPageParam: (response) => (response as ${nestedType}).${ctx.nextPageParam}`;
// initialPageParam is a string literal
const infiniteOptions = `initialPageParam: "${ctx.initialPageParam}", ${getNextPageParam}`;
const body = `useInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
const dataTypeDefault = suspense
? `InfiniteData<NonNullable<${baseDataType}>>`
: `InfiniteData<${baseDataType}>`;
const queryFn = buildPagedQueryFn(op, ctx, true);
const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`;
const body = `${hookCall}({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
return {

@@ -177,3 +199,3 @@ kind: StructureKind.VariableStatement,

name: hookName,
initializer: `<TData = InfiniteData<${baseDataType}>, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${clientOptionsParam}, queryKey?: TQueryKey, options?: Omit<UseInfiniteQueryOptions<TData, TError>, "queryKey" | "queryFn">) => ${body}`,
initializer: `<TData = ${dataTypeDefault}, TError = ${errorType}, TQueryKey extends Array<unknown> = unknown[]>(${buildInfiniteClientOptionsParam(op)}, queryKey?: TQueryKey, options?: ${buildOverridableInfiniteOptionsType(optionsTypeName)}) => ${body}`,
},

@@ -184,8 +206,21 @@ ],

/**
* Build useInfiniteQuery hook.
*/
export function buildUseInfiniteQueryHook(op, ctx) {
return buildInfiniteHook(op, ctx, false);
}
/**
* Build useSuspenseInfiniteQuery hook.
*/
export function buildUseSuspenseInfiniteQueryHook(op, ctx) {
return buildInfiniteHook(op, ctx, true);
}
/**
* Build prefetch function.
* Example:
* export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}) =>
* export const prefetchUseFindPets = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}, options?: Omit<FetchQueryOptions<Common.FindPetsDefaultResponse>, "queryKey" | "queryFn">) =>
* queryClient.prefetchQuery({
* queryKey: Common.UseFindPetsKeyFn(clientOptions),
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
* queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data),
* ...options
* });

@@ -195,13 +230,5 @@ */

const fnName = `prefetchUse${op.capitalizedMethodName}`;
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
? `${op.capitalizedMethodName}Data`
: "unknown";
const hasParams = op.parameters.length > 0;
const defaultValue = op.allParamsOptional ? " = {}" : "";
const clientOptionsParam = hasParams
? `clientOptions: Options<${dataTypeName}, true>${defaultValue}`
: `clientOptions: Options<${dataTypeName}, true> = {}`;
const callArgs = "{ ...clientOptions }";
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`;
const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`;
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`;
const optionsParam = `options?: Omit<FetchQueryOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn">`;
const body = `queryClient.prefetchQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`;
return {

@@ -216,3 +243,3 @@ kind: StructureKind.VariableStatement,

name: fnName,
initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`,
initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op, ctx)}, ${optionsParam}) => ${body}`,
},

@@ -223,8 +250,44 @@ ],

/**
* Build prefetchInfiniteQuery function for a paginatable operation.
* Example:
* export const prefetchUseFindPaginatedPetsInfinite = (queryClient: QueryClient, clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, options?: Omit<FetchInfiniteQueryOptions<Common.FindPaginatedPetsDefaultResponse>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">) =>
* queryClient.prefetchInfiniteQuery({
* queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions),
* queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data),
* initialPageParam: 1,
* getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage,
* ...options
* });
*/
export function buildPrefetchInfiniteQueryFn(op, ctx) {
if (!op.isPaginatable) {
return null;
}
const fnName = `prefetchUse${op.capitalizedMethodName}Infinite`;
const queryFn = buildPagedQueryFn(op, ctx, false);
const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`;
const optionsParam = `options?: Omit<FetchInfiniteQueryOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn" | "initialPageParam" | "getNextPageParam">`;
const body = `queryClient.prefetchInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
return {
kind: StructureKind.VariableStatement,
// Copy the operation's JSDoc (description and @deprecated) from the SDK function
leadingTrivia: op.jsDoc ? `${op.jsDoc}\n` : undefined,
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: fnName,
initializer: `(queryClient: QueryClient, ${buildInfiniteClientOptionsParam(op)}, ${optionsParam}) => ${body}`,
},
],
};
}
/**
* Build ensureQueryData function.
* Example:
* export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}) =>
* export const ensureUseFindPetsData = (queryClient: QueryClient, clientOptions: Options<FindPetsData, true> = {}, options?: Omit<EnsureQueryDataOptions<Common.FindPetsDefaultResponse>, "queryKey" | "queryFn">) =>
* queryClient.ensureQueryData({
* queryKey: Common.UseFindPetsKeyFn(clientOptions),
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data)
* queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data),
* ...options
* });

@@ -234,13 +297,5 @@ */

const fnName = `ensureUse${op.capitalizedMethodName}Data`;
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
? `${op.capitalizedMethodName}Data`
: "unknown";
const hasParams = op.parameters.length > 0;
const defaultValue = op.allParamsOptional ? " = {}" : "";
const clientOptionsParam = hasParams
? `clientOptions: Options<${dataTypeName}, true>${defaultValue}`
: `clientOptions: Options<${dataTypeName}, true> = {}`;
const callArgs = "{ ...clientOptions }";
const queryFn = `() => ${op.methodName}(${callArgs}).then(response => response.data)`;
const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn} })`;
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`;
const optionsParam = `options?: Omit<EnsureQueryDataOptions<Common.${op.capitalizedMethodName}DefaultResponse>, "queryKey" | "queryFn">`;
const body = `queryClient.ensureQueryData({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions), queryFn: ${queryFn}, ...options })`;
return {

@@ -255,3 +310,3 @@ kind: StructureKind.VariableStatement,

name: fnName,
initializer: `(queryClient: QueryClient, ${clientOptionsParam}) => ${body}`,
initializer: `(queryClient: QueryClient, ${buildClientOptionsParam(op, ctx)}, ${optionsParam}) => ${body}`,
},

@@ -258,0 +313,0 @@ ],

import { StructureKind, VariableDeclarationKind, } from "ts-morph";
import { buildClientOptionsParam, buildNestedNextPageType, getDataTypeName, } from "./buildQueryHooks.mjs";
import { buildClientOptionsParam, buildGetNextPageParamExpr, buildInfiniteClientOptionsParam, buildPagedQueryFn, formatInitialPageParam, SDK_CALL_ARGS, } from "./buildQueryHooks.mjs";
/**

@@ -12,3 +12,3 @@ * Build a queryOptions factory for a GET operation.

* queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey),
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data),
* queryFn: () => findPets({ ...clientOptions, throwOnError: true }).then(response => response.data),
* });

@@ -19,3 +19,3 @@ */

const clientOptionsParam = buildClientOptionsParam(op, ctx);
const queryFn = `() => ${op.methodName}({ ...clientOptions }).then(response => response.data)`;
const queryFn = `() => ${op.methodName}(${SDK_CALL_ARGS}).then(response => response.data)`;
const body = `queryOptions({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn} })`;

@@ -43,5 +43,5 @@ return {

* queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey),
* queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam } } as Options<FindPaginatedPetsData, true>).then(response => response.data),
* queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam as number }, throwOnError: true } as Options<FindPaginatedPetsData, true>).then(response => response.data),
* initialPageParam: 1,
* getNextPageParam: (response) => (response as { nextPage: number }).nextPage,
* getNextPageParam: (response: unknown) => (response as { nextPage: number }).nextPage,
* });

@@ -54,14 +54,5 @@ */

const fnName = `${op.methodName}InfiniteOptions`;
const dataTypeName = getDataTypeName(op, ctx);
const defaultValue = op.allParamsOptional ? " = {}" : "";
const clientOptionsParam = `clientOptions: Common.${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`;
const queryFn = `({ pageParam }) => ${op.methodName}({ ...clientOptions, query: { ...clientOptions.query, ${ctx.pageParam}: pageParam } } as Options<${dataTypeName}, true>).then(response => response.data)`;
// Emit a numeric literal when possible so the inferred pageParam type
// matches what getNextPageParam returns
const initialPageParam = /^-?\d+$/.test(ctx.initialPageParam)
? ctx.initialPageParam
: JSON.stringify(ctx.initialPageParam);
const nestedType = buildNestedNextPageType(ctx.nextPageParam);
const getNextPageParam = `(response) => (response as ${nestedType}).${ctx.nextPageParam}`;
const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, initialPageParam: ${initialPageParam}, getNextPageParam: ${getNextPageParam} })`;
const queryFn = buildPagedQueryFn(op, ctx, false);
const infiniteOptions = `initialPageParam: ${formatInitialPageParam(ctx)}, getNextPageParam: ${buildGetNextPageParamExpr(ctx)}`;
const body = `infiniteQueryOptions({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions} })`;
return {

@@ -76,3 +67,3 @@ kind: StructureKind.VariableStatement,

name: fnName,
initializer: `(${clientOptionsParam}, queryKey?: Array<unknown>) => ${body}`,
initializer: `(${buildInfiniteClientOptionsParam(op)}, queryKey?: Array<unknown>) => ${body}`,
},

@@ -79,0 +70,0 @@ ],

@@ -1,6 +0,5 @@

import { StructureKind, } from "ts-morph";
import { OpenApiRqFiles } from "../constants.mjs";
import { buildDefaultResponseType, buildInfiniteClientOptionsType, buildInfiniteQueryKeyConst, buildInfiniteQueryKeyFn, buildMutationKeyConst, buildMutationKeyFn, buildMutationResultType, buildQueryKeyConst, buildQueryKeyFn, buildQueryResultType, } from "./buildCommon.mjs";
import { buildUseMutationHook } from "./buildMutationHooks.mjs";
import { buildEnsureQueryDataFn, buildPrefetchFn, buildUseInfiniteQueryHook, buildUseQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs";
import { buildEnsureQueryDataFn, buildPrefetchFn, buildPrefetchInfiniteQueryFn, buildUseInfiniteQueryHook, buildUseQueryHook, buildUseSuspenseInfiniteQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs";
import { buildInfiniteQueryOptionsFn, buildQueryOptionsFn, } from "./buildQueryOptions.mjs";

@@ -34,22 +33,6 @@ import { buildAxiosErrorImport, buildClientImport, buildCommonImport, buildModelImport, buildQueryImport, buildQueryOptionsImport, buildServiceImport, createGenerationProject, } from "./projectFactory.mjs";

* Generate the index.ts file content.
* The content is constant, so no ts-morph project is needed.
*/
function generateIndexFile(ctx) {
const project = createGenerationProject();
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.index}.ts`, undefined, { overwrite: true });
const exports = [
{
kind: StructureKind.ExportDeclaration,
moduleSpecifier: "./common",
},
{
kind: StructureKind.ExportDeclaration,
moduleSpecifier: "./queries",
},
{
kind: StructureKind.ExportDeclaration,
moduleSpecifier: "./queryOptions",
},
];
sourceFile.addExportDeclarations(exports);
return sourceFile.getFullText();
function generateIndexFile() {
return `export * from "./common";\nexport * from "./queries";\nexport * from "./queryOptions";\n`;
}

@@ -154,2 +137,9 @@ /**

}
// Add useSuspenseInfiniteQuery hooks for paginatable operations
for (const op of getOperations.filter((o) => o.isPaginatable)) {
const hook = buildUseSuspenseInfiniteQueryHook(op, ctx);
if (hook) {
sourceFile.addVariableStatement(hook);
}
}
return sourceFile.getFullText();

@@ -190,2 +180,9 @@ }

}
// Add prefetchInfiniteQuery functions for paginatable operations
for (const op of getOperations.filter((o) => o.isPaginatable)) {
const fn = buildPrefetchInfiniteQueryFn(op, ctx);
if (fn) {
sourceFile.addVariableStatement(fn);
}
}
return sourceFile.getFullText();

@@ -223,3 +220,3 @@ }

name: `${OpenApiRqFiles.index}.ts`,
content: addHeaderComment(generateIndexFile(ctx), ctx.version),
content: addHeaderComment(generateIndexFile(), ctx.version),
},

@@ -226,0 +223,0 @@ {

@@ -0,5 +1,5 @@

export * from "./buildCommon.mjs";
export * from "./buildMutationHooks.mjs";
export * from "./buildQueryHooks.mjs";
export { generateAllFiles } from "./generateFiles.mjs";
export { createGenerationProject } from "./projectFactory.mjs";
export * from "./buildCommon.mjs";
export * from "./buildQueryHooks.mjs";
export * from "./buildMutationHooks.mjs";

@@ -44,2 +44,3 @@ import { IndentationText, NewLineKind, Project, QuoteKind, StructureKind, } from "ts-morph";

{ name: "useInfiniteQuery" },
{ name: "useSuspenseInfiniteQuery" },
{ name: "useMutation" },

@@ -49,2 +50,3 @@ { name: "UseQueryResult" },

{ name: "UseInfiniteQueryOptions" },
{ name: "UseSuspenseInfiniteQueryOptions" },
{ name: "UseMutationOptions" },

@@ -54,2 +56,5 @@ { name: "UseMutationResult" },

{ name: "InfiniteData" },
{ name: "FetchQueryOptions", isTypeOnly: true },
{ name: "FetchInfiniteQueryOptions", isTypeOnly: true },
{ name: "EnsureQueryDataOptions", isTypeOnly: true },
],

@@ -56,0 +61,0 @@ };

{
"name": "@7nohe/openapi-react-query-codegen",
"version": "3.0.0-beta.2",
"version": "3.0.0-beta.3",
"description": "OpenAPI React Query Codegen",

@@ -46,16 +46,18 @@ "bin": {

"devDependencies": {
"@biomejs/biome": "^1.9.3",
"@biomejs/biome": "^2.5.4",
"@types/cross-spawn": "^6.0.6",
"@types/node": "^22.7.4",
"@types/node": "^22.20.1",
"@types/semver": "^7.7.1",
"@vitest/coverage-v8": "^1.5.0",
"commander": "^12.0.0",
"lefthook": "^1.6.10",
"rimraf": "^5.0.5",
"@vitest/coverage-v8": "^4.1.10",
"commander": "^15.0.0",
"lefthook": "^2.1.10",
"rimraf": "^6.1.3",
"ts-morph": "^28.0.0",
"typescript": "^6.0.3",
"vitest": "^1.5.0"
"vite": "^7",
"vitest": "^4.1.10"
},
"peerDependencies": {
"commander": "12.x",
"@tanstack/react-query": "^5.0.0",
"commander": "12.x || 13.x || 14.x || 15.x",
"ts-morph": "28.x",

@@ -62,0 +64,0 @@ "typescript": "5.x || 6.x"

# OpenAPI React Query Codegen
> Code generator for creating [React Query (also known as TanStack Query)](https://tanstack.com/query) hooks based on your OpenAPI schema.
> Code generator for [TanStack Query (React Query)](https://tanstack.com/query) based on your OpenAPI schema โ€” `queryOptions` factories following the official TanStack Query v5 pattern, plus ready-to-use hooks, prefetch, ensure, suspense, and infinite query helpers.
[![npm version](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen.svg)](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen)
๐Ÿ“– **[Documentation](https://openapi-react-query-codegen.vercel.app)** ยท [Migrating to v3](https://openapi-react-query-codegen.vercel.app/guides/migrating-to-v3/)
## Features
- Generates custom react hooks that use React Query's `useQuery`, `useSuspenseQuery`, `useMutation` and `useInfiniteQuery` hooks
- Generates custom functions that use React Query's `ensureQueryData` and `prefetchQuery` functions
- Generates query keys and functions for query caching
- Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts)
- **`queryOptions` / `infiniteQueryOptions` factories** for every GET operation โ€” the [TanStack Query v5 recommended pattern](https://tanstack.com/query/latest/docs/framework/react/guides/query-options), composable with `useQuery`, `useQueries`, `useSuspenseQuery`, `prefetchQuery`, `ensureQueryData`, and `setQueryData` with full type safety
- **Custom hooks**: `useQuery`, `useSuspenseQuery`, `useMutation`, `useInfiniteQuery`, and `useSuspenseInfiniteQuery` variants per operation
- **SSR helpers**: `prefetchQuery`, `prefetchInfiniteQuery`, and `ensureQueryData` functions per operation โ€” ready for Next.js App Router hydration
- **Hierarchical query keys** with exported key constants and functions: invalidate one exact query, all infinite pages of an operation, or every cache entry of an operation with a single prefix
- **Pure TypeScript clients** generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts) (fetch and axios)
## Quick start
```bash
npm install -D @7nohe/openapi-react-query-codegen
npx openapi-rq -i ./petstore.yaml
```
```tsx
import { useQuery } from "@tanstack/react-query";
import { findPetsOptions } from "./openapi/queries";
function Pets() {
const { data } = useQuery(findPetsOptions({ query: { limit: 10 } }));
// ...or use the generated hook directly: useFindPets({ query: { limit: 10 } })
}
```
See the [documentation](https://openapi-react-query-codegen.vercel.app) for CLI options, SSR recipes, and infinite query usage.
## How it compares
| | This library | @hey-api tanstack-query plugin | Orval |
|---|---|---|---|
| `queryOptions` / `infiniteQueryOptions` factories (TanStack v5 pattern) | โœ… | โœ… | โŒ |
| Ready-to-use hooks (`useQuery` / suspense / infinite variants) | โœ… | โŒ (options only) | โœ… |
| SSR helpers (`prefetchQuery` / `prefetchInfiniteQuery` / `ensureQueryData`) | โœ… | โŒ | Partial (`usePrefetch`) |
| Hierarchical query keys for granular invalidation | โœ… | โœ… (tags) | Partial |
| Stable release line | โœ… SemVer | pre-1.0, frequent breaking changes | โœ… |
| MSW mock generation | โŒ (out of scope) | โŒ | โœ… |
| Vue / Solid / Svelte / Angular | โŒ React-focused | โœ… | โœ… |
**Scope**: this library is deliberately React-focused and does not generate API mocks โ€” use Orval if MSW mocks are your priority, or hey-api's own plugin if you need non-React frameworks.
## Stability policy
This library builds on [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts), which is pre-1.0 and moves fast. We **pin the exact hey-api version** and absorb its breaking changes for you: hey-api upgrades land here only after our full snapshot-test suite passes, and are released as minor versions. Your generated API surface follows SemVer โ€” breaking output changes only happen in major versions, with a migration guide.
## Requirements
- Node.js 22.18+
- `@tanstack/react-query` 5.x (peer dependency)
- `typescript` 5.x or 6.x, `ts-morph` 28.x, `commander` 12โ€“15 (peer dependencies)
## License
MIT