@7nohe/openapi-react-query-codegen
Version:
OpenAPI React Query Codegen
210 lines (209 loc) • 9.41 kB
JavaScript
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, buildPrefetchInfiniteQueryFn, buildUseInfiniteQueryHook, buildUseQueryHook, buildUseSuspenseInfiniteQueryHook, buildUseSuspenseQueryHook, } from "./buildQueryHooks.mjs";
import { buildInfiniteQueryOptionsFn, buildQueryOptionsFn, } from "./buildQueryOptions.mjs";
import { buildCommonFileImports, buildHookFileImports, buildQueryOptionsFileImports, createGenerationProject, } from "./projectFactory.mjs";
/**
* Add one variable statement per operation, skipping the ones the builder
* declines. A builder returns null when the operation is out of its scope —
* every infinite-query builder returns null for non-paginatable operations,
* which is how the paginatable subset gets selected.
*/
function addStatements(sourceFile, operations, build) {
for (const op of operations) {
const statement = build(op);
if (statement) {
sourceFile.addVariableStatement(statement);
}
}
}
/**
* Generate the index.ts file content.
* The content is constant, so no ts-morph project is needed.
*/
function generateIndexFile() {
return `export * from "./common";\nexport * from "./queries";\nexport * from "./queryOptions";\n`;
}
/**
* Generate the common.ts file content.
*/
function generateCommonFile(operations, ctx) {
const project = createGenerationProject();
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.common}.ts`, undefined, { overwrite: true });
// Add imports
sourceFile.addImportDeclarations(buildCommonFileImports(ctx));
// Group operations by HTTP method
const getOperations = operations.filter((op) => op.httpMethod === "GET");
const mutationOperations = operations.filter((op) => ["POST", "PUT", "PATCH", "DELETE"].includes(op.httpMethod));
// Add query types and keys
for (const op of getOperations) {
sourceFile.addTypeAlias(buildDefaultResponseType(op));
sourceFile.addTypeAlias(buildQueryResultType(op));
sourceFile.addVariableStatement(buildQueryKeyConst(op));
sourceFile.addVariableStatement(buildQueryKeyFn(op));
}
// 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
for (const op of mutationOperations) {
sourceFile.addTypeAlias(buildMutationResultType(op));
sourceFile.addVariableStatement(buildMutationKeyConst(op));
sourceFile.addVariableStatement(buildMutationKeyFn(op));
}
return sourceFile.getFullText();
}
/**
* Generate the queries.ts file content.
*/
function generateQueriesFile(operations, ctx) {
const project = createGenerationProject();
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.queries}.ts`, undefined, { overwrite: true });
// Add imports
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
// Group operations
const getOperations = operations.filter((op) => op.httpMethod === "GET");
const mutationOperations = operations.filter((op) => ["POST", "PUT", "PATCH", "DELETE"].includes(op.httpMethod));
// Add useQuery hooks
for (const op of getOperations) {
sourceFile.addVariableStatement(buildUseQueryHook(op, ctx));
}
// Add useMutation hooks
for (const op of mutationOperations) {
sourceFile.addVariableStatement(buildUseMutationHook(op, ctx));
}
return sourceFile.getFullText();
}
/**
* 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
sourceFile.addImportDeclarations(buildQueryOptionsFileImports(ctx));
// Only GET operations have query options
const getOperations = operations.filter((op) => op.httpMethod === "GET");
for (const op of getOperations) {
sourceFile.addVariableStatement(buildQueryOptionsFn(op));
}
// Add infiniteQueryOptions factories
addStatements(sourceFile, getOperations, (op) => buildInfiniteQueryOptionsFn(op, ctx));
return sourceFile.getFullText();
}
/**
* Generate the suspense.ts file content.
*/
function generateSuspenseFile(operations, ctx) {
const project = createGenerationProject();
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, undefined, { overwrite: true });
// Add imports
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
// Only GET operations for suspense
const getOperations = operations.filter((op) => op.httpMethod === "GET");
// Add useSuspenseQuery hooks
for (const op of getOperations) {
sourceFile.addVariableStatement(buildUseSuspenseQueryHook(op, ctx));
}
// Add useSuspenseInfiniteQuery hooks
addStatements(sourceFile, getOperations, (op) => buildUseSuspenseInfiniteQueryHook(op, ctx));
return sourceFile.getFullText();
}
/**
* Generate the infiniteQueries.ts file content.
*/
function generateInfiniteQueriesFile(operations, ctx) {
const project = createGenerationProject();
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.infiniteQueries}.ts`, undefined, { overwrite: true });
// Add imports
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
// Only GET operations can be paginatable
const getOperations = operations.filter((op) => op.httpMethod === "GET");
// Add useInfiniteQuery hooks
addStatements(sourceFile, getOperations, (op) => buildUseInfiniteQueryHook(op, ctx));
return sourceFile.getFullText();
}
/**
* Generate the prefetch.ts file content.
*/
function generatePrefetchFile(operations, ctx) {
const project = createGenerationProject();
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, undefined, { overwrite: true });
// Add imports
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
// Only GET operations for prefetch
const getOperations = operations.filter((op) => op.httpMethod === "GET");
// Add prefetch functions
for (const op of getOperations) {
sourceFile.addVariableStatement(buildPrefetchFn(op));
}
// Add prefetchInfiniteQuery functions
addStatements(sourceFile, getOperations, (op) => buildPrefetchInfiniteQueryFn(op, ctx));
return sourceFile.getFullText();
}
/**
* Generate the ensureQueryData.ts file content.
*/
function generateEnsureQueryDataFile(operations, ctx) {
const project = createGenerationProject();
const sourceFile = project.createSourceFile(`${OpenApiRqFiles.ensureQueryData}.ts`, undefined, { overwrite: true });
// Add imports
sourceFile.addImportDeclarations(buildHookFileImports(ctx));
// Only GET operations for ensure
const getOperations = operations.filter((op) => op.httpMethod === "GET");
// Add ensureQueryData functions
for (const op of getOperations) {
sourceFile.addVariableStatement(buildEnsureQueryDataFn(op));
}
return sourceFile.getFullText();
}
/**
* Add the generated header comment to file content.
*/
function addHeaderComment(content, version) {
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
return comment + content;
}
/**
* Generate all files using ts-morph.
*/
export function generateAllFiles(operations, ctx) {
return [
{
name: `${OpenApiRqFiles.index}.ts`,
content: addHeaderComment(generateIndexFile(), ctx.version),
},
{
name: `${OpenApiRqFiles.common}.ts`,
content: addHeaderComment(generateCommonFile(operations, ctx), ctx.version),
},
{
name: `${OpenApiRqFiles.queries}.ts`,
content: addHeaderComment(generateQueriesFile(operations, ctx), ctx.version),
},
{
name: `${OpenApiRqFiles.queryOptions}.ts`,
content: addHeaderComment(generateQueryOptionsFile(operations, ctx), ctx.version),
},
{
name: `${OpenApiRqFiles.suspense}.ts`,
content: addHeaderComment(generateSuspenseFile(operations, ctx), ctx.version),
},
{
name: `${OpenApiRqFiles.infiniteQueries}.ts`,
content: addHeaderComment(generateInfiniteQueriesFile(operations, ctx), ctx.version),
},
{
name: `${OpenApiRqFiles.prefetch}.ts`,
content: addHeaderComment(generatePrefetchFile(operations, ctx), ctx.version),
},
{
name: `${OpenApiRqFiles.ensureQueryData}.ts`,
content: addHeaderComment(generateEnsureQueryDataFile(operations, ctx), ctx.version),
},
];
}