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.1
to
3.0.0-beta.2
+76
dist/tsmorph/buildQueryOptions.mjs
import { StructureKind, VariableDeclarationKind, } from "ts-morph";
import { buildClientOptionsParam, buildNestedNextPageType, getDataTypeName, } from "./buildQueryHooks.mjs";
/**
* Build a queryOptions factory for a GET operation.
* The factory centralizes queryKey and queryFn so they can be reused with
* every TanStack Query utility (useQuery, useQueries, prefetchQuery,
* ensureQueryData, setQueryData, ...) with full type safety.
* Example:
* export const findPetsOptions = (clientOptions: Options<FindPetsData, true> = {}, queryKey?: Array<unknown>) =>
* queryOptions({
* queryKey: Common.UseFindPetsKeyFn(clientOptions, queryKey),
* queryFn: () => findPets({ ...clientOptions }).then(response => response.data),
* });
*/
export function buildQueryOptionsFn(op, ctx) {
const fnName = `${op.methodName}Options`;
const clientOptionsParam = buildClientOptionsParam(op, ctx);
const queryFn = `() => ${op.methodName}({ ...clientOptions }).then(response => response.data)`;
const body = `queryOptions({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn} })`;
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: `(${clientOptionsParam}, queryKey?: Array<unknown>) => ${body}`,
},
],
};
}
/**
* Build an infiniteQueryOptions factory for a paginatable GET operation.
* Uses the dedicated infinite query key and page-less options type.
* Example:
* export const findPaginatedPetsInfiniteOptions = (clientOptions: Common.FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>) =>
* infiniteQueryOptions({
* queryKey: Common.UseFindPaginatedPetsInfiniteKeyFn(clientOptions, queryKey),
* queryFn: ({ pageParam }) => findPaginatedPets({ ...clientOptions, query: { ...clientOptions.query, page: pageParam } } as Options<FindPaginatedPetsData, true>).then(response => response.data),
* initialPageParam: 1,
* getNextPageParam: (response) => (response as { nextPage: number }).nextPage,
* });
*/
export function buildInfiniteQueryOptionsFn(op, ctx) {
if (!op.isPaginatable) {
return null;
}
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} })`;
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: `(${clientOptionsParam}, queryKey?: Array<unknown>) => ${body}`,
},
],
};
}
+1
-0

@@ -8,2 +8,3 @@ export const defaultOutputPath = "openapi";

queries: "queries",
queryOptions: "queryOptions",
infiniteQueries: "infiniteQueries",

@@ -10,0 +11,0 @@ common: "common",

@@ -120,1 +120,65 @@ import { StructureKind, VariableDeclarationKind, } from "ts-morph";

}
/**
* Build the client options type for infinite queries.
* The page parameter is excluded because TanStack Query supplies it via the
* pageParam mechanism (#140).
* Example:
* export type FindPaginatedPetsInfiniteClientOptions = Omit<Options<FindPaginatedPetsData, true>, "query"> &
* { query?: Omit<NonNullable<FindPaginatedPetsData["query"]>, "page"> };
*/
export function buildInfiniteClientOptionsType(op, ctx) {
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
? `${op.capitalizedMethodName}Data`
: "unknown";
const type = dataTypeName === "unknown"
? "Options<unknown, true>"
: `Omit<Options<${dataTypeName}, true>, "query"> & { query?: Omit<NonNullable<${dataTypeName}["query"]>, "${ctx.pageParam}"> }`;
return {
kind: StructureKind.TypeAlias,
isExported: true,
name: `${op.capitalizedMethodName}InfiniteClientOptions`,
type,
};
}
/**
* 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";
*/
export function buildInfiniteQueryKeyConst(op) {
return {
kind: StructureKind.VariableStatement,
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: `use${op.capitalizedMethodName}InfiniteKey`,
initializer: `"${op.capitalizedMethodName}Infinite"`,
},
],
};
}
/**
* Build the infinite query key function.
* Example: export const UseFindPaginatedPetsInfiniteKeyFn = (clientOptions: FindPaginatedPetsInfiniteClientOptions = {}, queryKey?: Array<unknown>) =>
* [useFindPaginatedPetsInfiniteKey, ...(queryKey ?? [clientOptions])];
*/
export function buildInfiniteQueryKeyFn(op) {
const defaultValue = op.allParamsOptional ? " = {}" : "";
const params = [
`clientOptions: ${op.capitalizedMethodName}InfiniteClientOptions${defaultValue}`,
"queryKey?: Array<unknown>",
];
return {
kind: StructureKind.VariableStatement,
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: `Use${op.capitalizedMethodName}InfiniteKeyFn`,
initializer: `(${params.join(", ")}) => [use${op.capitalizedMethodName}InfiniteKey, ...(queryKey ?? [clientOptions])]`,
},
],
};
}
+17
-8

@@ -43,5 +43,14 @@ import { StructureKind, VariableDeclarationKind, } from "ts-morph";

/**
* Resolve the generated Data type name for an operation, falling back to
* unknown when the operation has no generated Data type.
*/
export function getDataTypeName(op, ctx) {
return ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
? `${op.capitalizedMethodName}Data`
: "unknown";
}
/**
* Build the client options parameter string.
*/
function buildClientOptionsParam(op, ctx) {
export function buildClientOptionsParam(op, ctx) {
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)

@@ -124,3 +133,3 @@ ? `${op.capitalizedMethodName}Data`

*/
function buildNestedNextPageType(nextPageParam) {
export function buildNestedNextPageType(nextPageParam) {
const segments = nextPageParam.split(".");

@@ -141,9 +150,9 @@ return segments.reduceRight((acc, segment) => {

const baseDataType = `Common.${op.capitalizedMethodName}DefaultResponse`;
const dataTypeName = ctx.modelNames.includes(`${op.capitalizedMethodName}Data`)
? `${op.capitalizedMethodName}Data`
: "unknown";
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: Options<${dataTypeName}, true>${defaultValue}`;
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 } }).then(response => response.data as TData) as TData`;
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

@@ -154,3 +163,3 @@ const nestedType = buildNestedNextPageType(ctx.nextPageParam);

const infiniteOptions = `initialPageParam: "${ctx.initialPageParam}", ${getNextPageParam}`;
const body = `useInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}KeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
const body = `useInfiniteQuery({ queryKey: Common.Use${op.capitalizedMethodName}InfiniteKeyFn(clientOptions, queryKey), queryFn: ${queryFn}, ${infiniteOptions}, ...options })`;
return {

@@ -157,0 +166,0 @@ kind: StructureKind.VariableStatement,

import { StructureKind, } from "ts-morph";
import { OpenApiRqFiles } from "../constants.mjs";
import { buildDefaultResponseType, buildMutationKeyConst, buildMutationKeyFn, buildMutationResultType, buildQueryKeyConst, buildQueryKeyFn, buildQueryResultType, } from "./buildCommon.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 { buildAxiosErrorImport, buildClientImport, buildCommonImport, buildModelImport, buildQueryImport, buildServiceImport, createGenerationProject, } from "./projectFactory.mjs";
import { buildInfiniteQueryOptionsFn, buildQueryOptionsFn, } from "./buildQueryOptions.mjs";
import { buildAxiosErrorImport, buildClientImport, buildCommonImport, buildModelImport, buildQueryImport, buildQueryOptionsImport, buildServiceImport, createGenerationProject, } from "./projectFactory.mjs";
/**

@@ -46,2 +47,6 @@ * Build imports for common.ts file.

},
{
kind: StructureKind.ExportDeclaration,
moduleSpecifier: "./queryOptions",
},
];

@@ -69,2 +74,8 @@ sourceFile.addExportDeclarations(exports);

}
// Add dedicated infinite query types and keys for paginatable operations
for (const op of getOperations.filter((o) => o.isPaginatable)) {
sourceFile.addTypeAlias(buildInfiniteClientOptionsType(op, ctx));
sourceFile.addVariableStatement(buildInfiniteQueryKeyConst(op));
sourceFile.addVariableStatement(buildInfiniteQueryKeyFn(op));
}
// Add mutation types and keys

@@ -100,2 +111,33 @@ for (const op of mutationOperations) {

/**
* Generate the queryOptions.ts file content.
*/
function generateQueryOptionsFile(operations, ctx) {
const project = createGenerationProject();
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.queryOptions}.ts`, undefined, { overwrite: true });
// Add imports
const imports = [
buildCommonImport(),
buildQueryOptionsImport(),
buildClientImport(ctx),
buildServiceImport(ctx),
];
const modelImport = buildModelImport(ctx);
if (modelImport) {
imports.push(modelImport);
}
sourceFile.addImportDeclarations(imports);
// Only GET operations have query options
const getOperations = operations.filter((op) => op.httpMethod === "GET");
for (const op of getOperations) {
sourceFile.addVariableStatement(buildQueryOptionsFn(op, ctx));
}
for (const op of getOperations) {
const infiniteOptions = buildInfiniteQueryOptionsFn(op, ctx);
if (infiniteOptions) {
sourceFile.addVariableStatement(infiniteOptions);
}
}
return sourceFile.getFullText();
}
/**
* Generate the suspense.ts file content.

@@ -192,2 +234,6 @@ */

{
name: `${OpenApiRqFiles.queryOptions}.ts`,
content: addHeaderComment(generateQueryOptionsFile(operations, ctx), ctx.version),
},
{
name: `${OpenApiRqFiles.suspense}.ts`,

@@ -194,0 +240,0 @@ content: addHeaderComment(generateSuspenseFile(operations, ctx), ctx.version),

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

/**
* Build import structure for the queryOptions/infiniteQueryOptions helpers.
*/
export function buildQueryOptionsImport() {
return {
kind: StructureKind.ImportDeclaration,
moduleSpecifier: "@tanstack/react-query",
namedImports: [{ name: "queryOptions" }, { name: "infiniteQueryOptions" }],
};
}
/**
* Build import structure for services.

@@ -58,0 +68,0 @@ */

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

@@ -5,0 +5,0 @@ "bin": {