Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@
"license": "MIT",
"author": "Daiki Urata (@7nohe)",
"dependencies": {
"@hey-api/client-fetch": "0.4.0",
"@hey-api/openapi-ts": "0.53.8",
"@hey-api/openapi-ts": "0.90.1",
"cross-spawn": "^7.0.3"
},
"devDependencies": {
Expand All @@ -59,13 +58,13 @@
"commander": "^12.0.0",
"lefthook": "^1.6.10",
"rimraf": "^5.0.5",
"ts-morph": "^23.0.0",
"typescript": "^5.5.4",
"ts-morph": "^27.0.2",
"typescript": "^5.9.3",
"vitest": "^1.5.0"
},
"peerDependencies": {
"commander": "12.x",
"ts-morph": "23.x",
"ts-morph": "27.x",
"typescript": "5.x"
},
"packageManager": "pnpm@9.6.0",
Expand Down
518 changes: 307 additions & 211 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/common.mts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const getNameFromVariable = (variable: VariableDeclaration) => {
export type FunctionDescription = {
node: SourceFile;
method: VariableDeclaration;
methodBlock: ts.Block;
methodBlock?: ts.Block;
httpMethodName: string;
jsDoc: string;
isDeprecated: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/constants.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const defaultOutputPath = "openapi";
export const queriesOutputPath = "queries";
export const requestsOutputPath = "requests";

export const serviceFileName = "services.gen";
export const serviceFileName = "sdk.gen";
export const modelsFileName = "types.gen";

export const OpenApiRqFiles = {
Expand Down
20 changes: 11 additions & 9 deletions src/createExports.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { UserConfig } from "@hey-api/openapi-ts";
import type { Project } from "ts-morph";
import ts from "typescript";
import type { LimitedUserConfig } from "./cli.mjs";
import { capitalizeFirstLetter } from "./common.mjs";
import { modelsFileName } from "./constants.mjs";
import { createPrefetchOrEnsure } from "./createPrefetchOrEnsure.mjs";
Expand All @@ -17,7 +17,7 @@ export const createExports = ({
initialPageParam,
}: {
service: Service;
client: UserConfig["client"];
client: LimitedUserConfig["client"];
project: Project;
pageParam: string;
nextPageParam: string;
Expand Down Expand Up @@ -52,13 +52,15 @@ export const createExports = ({
m.kind === ts.SyntaxKind.PropertySignature &&
m.name?.getText() === "query",
);
if (
query &&
((query as ts.PropertySignature).type as ts.TypeLiteralNode).members
.map((m) => m.name?.getText())
.includes(pageParam)
) {
paginatableMethods.push(methodDataNames[key]);
if (query) {
const queryType = (query as ts.PropertySignature).type;
const members =
queryType && ts.isTypeLiteralNode(queryType)
? queryType.members
: undefined;
if (members?.map((m) => m.name?.getText()).includes(pageParam)) {
paginatableMethods.push(methodDataNames[key]);
}
}
}
}
Expand Down
10 changes: 3 additions & 7 deletions src/createImports.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { posix } from "node:path";
import type { UserConfig } from "@hey-api/openapi-ts";
import type { Project } from "ts-morph";
import ts from "typescript";
import type { LimitedUserConfig } from "./cli.mjs";
import { modelsFileName, serviceFileName } from "./constants.mjs";

const { join } = posix;
Expand All @@ -11,7 +11,7 @@ export const createImports = ({
client,
}: {
project: Project;
client: UserConfig["client"];
client: LimitedUserConfig["client"];
}) => {
const modelsFile = project
.getSourceFiles()
Expand Down Expand Up @@ -49,11 +49,7 @@ export const createImports = ({
),
]),
),
ts.factory.createStringLiteral(
client === "@hey-api/client-axios"
? "@hey-api/client-axios"
: "@hey-api/client-fetch",
),
ts.factory.createStringLiteral(join("../requests", "client")),
undefined,
),
ts.factory.createImportDeclaration(
Expand Down
6 changes: 3 additions & 3 deletions src/createSource.mts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { join } from "node:path";
import type { UserConfig } from "@hey-api/openapi-ts";
import { Project } from "ts-morph";
import ts from "typescript";
import type { LimitedUserConfig } from "./cli.mjs";
import { OpenApiRqFiles } from "./constants.mjs";
import { createExports } from "./createExports.mjs";
import { createImports } from "./createImports.mjs";
Expand All @@ -15,7 +15,7 @@ const createSourceFile = async ({
initialPageParam,
}: {
outputPath: string;
client: UserConfig["client"];
client: LimitedUserConfig["client"];
pageParam: string;
nextPageParam: string;
initialPageParam: string;
Expand Down Expand Up @@ -136,7 +136,7 @@ export const createSource = async ({
initialPageParam,
}: {
outputPath: string;
client: UserConfig["client"];
client: LimitedUserConfig["client"];
version: string;
pageParam: string;
nextPageParam: string;
Expand Down
4 changes: 2 additions & 2 deletions src/createUseMutation.mts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { UserConfig } from "@hey-api/openapi-ts";
import ts from "typescript";
import type { LimitedUserConfig } from "./cli.mjs";
import {
BuildCommonTypeName,
EqualsOrGreaterThanToken,
Expand Down Expand Up @@ -47,7 +47,7 @@ export const createUseMutation = ({
}: {
functionDescription: FunctionDescription;
modelNames: string[];
client: UserConfig["client"];
client: LimitedUserConfig["client"];
}) => {
const methodName = getNameFromVariable(method);
const mutationKey = createQueryKeyFromMethod({ method });
Expand Down
6 changes: 3 additions & 3 deletions src/createUseQuery.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { UserConfig } from "@hey-api/openapi-ts";
import type { VariableDeclaration } from "ts-morph";
import ts from "typescript";
import type { LimitedUserConfig } from "./cli.mjs";
import {
BuildCommonTypeName,
EqualsOrGreaterThanToken,
Expand All @@ -24,7 +24,7 @@ const createApiResponseType = ({
client,
}: {
methodName: string;
client: UserConfig["client"];
client: LimitedUserConfig["client"];
}) => {
/** Awaited<ReturnType<typeof myClass.myMethod>> */
const awaitedResponseDataType = ts.factory.createIndexedAccessTypeNode(
Expand Down Expand Up @@ -481,7 +481,7 @@ export const createUseQuery = ({
modelNames,
}: {
functionDescription: FunctionDescription;
client: UserConfig["client"];
client: LimitedUserConfig["client"];
pageParam: string;
nextPageParam: string;
initialPageParam: string;
Expand Down
36 changes: 13 additions & 23 deletions src/generate.mts
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,22 @@ export async function generate(options: LimitedUserConfig, version: string) {
const openApiOutputPath = buildRequestsOutputPath(options.output);
const formattedOptions = formatOptions(options);

// Map old client option to new plugins system
const clientPlugin =
formattedOptions.client === "@hey-api/client-axios"
? "@hey-api/client-axios"
: "@hey-api/client-fetch";
const plugins: UserConfig["plugins"] = [
clientPlugin,
"@hey-api/typescript",
"@hey-api/sdk",
];

const config: UserConfig = {
client: formattedOptions.client,
debug: formattedOptions.debug,
dryRun: false,
exportCore: true,
output: {
format: formattedOptions.format,
lint: formattedOptions.lint,
path: openApiOutputPath,
},
input: formattedOptions.input,
schemas: {
export: !formattedOptions.noSchemas,
type: formattedOptions.schemaType,
},
services: {
export: true,
asClass: false,
operationId: !formattedOptions.noOperationId,
},
types: {
dates: formattedOptions.useDateType,
export: true,
enums: formattedOptions.enums,
},
useOptions: true,
output: openApiOutputPath,
plugins,
};
await createClient(config);
const source = await createSource({
Expand Down
72 changes: 47 additions & 25 deletions src/service.mts
Original file line number Diff line number Diff line change
Expand Up @@ -26,41 +26,63 @@ export async function getServices(project: Project): Promise<Service> {

export function getMethodsFromService(node: SourceFile): FunctionDescription[] {
const variableStatements = node.getVariableStatements();
// Filter to only exported variable statements that contain arrow functions
const exportedStatements = variableStatements.filter((statement) => {
if (!statement.isExported()) return false;
const declarations = statement.getDeclarations();
return declarations.some((decl) => {
const initializer = decl.getInitializer();
return (
initializer && ts.isArrowFunction(initializer.compilerNode as ts.Node)
);
});
});

// The first variable statement is `const client = createClient(createConfig())`, so we skip it
return variableStatements.splice(1).flatMap((variableStatement) => {
return exportedStatements.flatMap((variableStatement) => {
const declarations = variableStatement.getDeclarations();
return declarations.map((declaration) => {
if (!ts.isVariableDeclaration(declaration.compilerNode)) {
throw new Error("Variable declaration not found");
}
const initializer = declaration.getInitializer();
if (!initializer) {
throw new Error("Initializer not found");
}
if (!ts.isArrowFunction(initializer.compilerNode)) {
const arrowFunction = initializer.compilerNode as ts.ArrowFunction;
if (!ts.isArrowFunction(arrowFunction)) {
throw new Error("Arrow function not found");
}
const methodBlockNode = initializer.compilerNode.body;
if (!methodBlockNode || !ts.isBlock(methodBlockNode)) {
throw new Error("Method block not found");
}
const foundReturnStatement = methodBlockNode.statements.find(
(s) => s.kind === ts.SyntaxKind.ReturnStatement,
);
if (!foundReturnStatement) {
throw new Error("Return statement not found");
}
const returnStatement = foundReturnStatement as ts.ReturnStatement;
const foundCallExpression = returnStatement.expression;
if (!foundCallExpression) {
throw new Error("Call expression not found");
const arrowBody = arrowFunction.body;

// Find the call expression - either from block's return statement or direct expression
let callExpression: ts.CallExpression;
let methodBlockNode: ts.Block | undefined;

if (ts.isBlock(arrowBody)) {
// Old style: arrow function with block body
methodBlockNode = arrowBody;
const foundReturnStatement = arrowBody.statements.find(
(s) => s.kind === ts.SyntaxKind.ReturnStatement,
);
if (!foundReturnStatement) {
throw new Error("Return statement not found");
}
const returnStatement = foundReturnStatement as ts.ReturnStatement;
if (!returnStatement.expression) {
throw new Error("Call expression not found");
}
callExpression = returnStatement.expression as ts.CallExpression;
} else {
// New style: arrow function with expression body (no block)
// The body is a call expression like: (options?.client ?? client).post<...>({...})
callExpression = arrowBody as ts.CallExpression;
}
const callExpression = foundCallExpression as ts.CallExpression;

const propertyAccessExpression =
callExpression.expression as ts.PropertyAccessExpression;
const httpMethodName = propertyAccessExpression.name.getText();
// Navigate to find the HTTP method name (get, post, put, delete, etc.)
let httpMethodName: string | undefined;
if (ts.isCallExpression(callExpression)) {
const expr = callExpression.expression;
if (ts.isPropertyAccessExpression(expr)) {
httpMethodName = expr.name.getText();
}
}

if (!httpMethodName) {
throw new Error("httpMethodName not found");
Expand All @@ -75,7 +97,7 @@ export function getMethodsFromService(node: SourceFile): FunctionDescription[] {
return [tsNode];
};

const children = getAllChildren(initializer.compilerNode);
const children = getAllChildren(arrowFunction);
// get all JSDoc comments
// this should be an array of 1 or 0
const jsDocs = children
Expand Down
Loading