@hono-di/client 0.0.6
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.
- package/dist/index.cjs +194 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +192 -0
- package/dist/index.js.map +1 -0
- package/package.json +32 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tsMorph = require('ts-morph');
|
|
4
|
+
|
|
5
|
+
// src/generator.ts
|
|
6
|
+
var TypeGenerator = class {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
async generate() {
|
|
11
|
+
const project = new tsMorph.Project({
|
|
12
|
+
tsConfigFilePath: this.options.tsConfigFilePath
|
|
13
|
+
});
|
|
14
|
+
const controllers = this.findControllers(project);
|
|
15
|
+
const routes = this.extractRoutes(controllers, project);
|
|
16
|
+
return this.buildTypeDefinition(routes);
|
|
17
|
+
}
|
|
18
|
+
findControllers(project) {
|
|
19
|
+
const controllers = [];
|
|
20
|
+
const sourceFiles = project.getSourceFiles();
|
|
21
|
+
for (const sourceFile of sourceFiles) {
|
|
22
|
+
const classes = sourceFile.getClasses();
|
|
23
|
+
for (const cls of classes) {
|
|
24
|
+
if (this.hasDecorator(cls, "Controller")) {
|
|
25
|
+
controllers.push(cls);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return controllers;
|
|
30
|
+
}
|
|
31
|
+
extractRoutes(controllers, project) {
|
|
32
|
+
const routes = [];
|
|
33
|
+
for (const controller of controllers) {
|
|
34
|
+
const controllerPrefix = this.getDecoratorArgument(controller, "Controller") || "";
|
|
35
|
+
const methods = controller.getMethods();
|
|
36
|
+
for (const method of methods) {
|
|
37
|
+
const methodDecorator = this.getMethodDecorator(method);
|
|
38
|
+
if (methodDecorator) {
|
|
39
|
+
const methodPath = this.getDecoratorArgument(method, methodDecorator.getName()) || "/";
|
|
40
|
+
const httpMethod = methodDecorator.getName().toLowerCase();
|
|
41
|
+
const returnType = method.getReturnType().getText();
|
|
42
|
+
const fullPath = this.normalizePath(controllerPrefix, methodPath);
|
|
43
|
+
this.collectType(returnType, project);
|
|
44
|
+
routes.push({
|
|
45
|
+
path: fullPath,
|
|
46
|
+
method: httpMethod,
|
|
47
|
+
returnType,
|
|
48
|
+
methodName: method.getName(),
|
|
49
|
+
parameters: method.getParameters().map((p) => {
|
|
50
|
+
const typeText = p.getType().getText();
|
|
51
|
+
this.collectType(typeText, project);
|
|
52
|
+
return {
|
|
53
|
+
name: p.getName(),
|
|
54
|
+
type: typeText,
|
|
55
|
+
decorators: p.getDecorators().map((d) => ({
|
|
56
|
+
name: d.getName(),
|
|
57
|
+
arguments: d.getArguments().map((arg) => arg.getText().replace(/^['"]|['"]$/g, ""))
|
|
58
|
+
}))
|
|
59
|
+
};
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return routes;
|
|
66
|
+
}
|
|
67
|
+
extraTypes = /* @__PURE__ */ new Map();
|
|
68
|
+
collectType(typeText, project) {
|
|
69
|
+
const primitives = ["string", "number", "boolean", "any", "void", "null", "undefined", "Date"];
|
|
70
|
+
const isArray = typeText.endsWith("[]");
|
|
71
|
+
const baseType = isArray ? typeText.slice(0, -2) : typeText;
|
|
72
|
+
if (primitives.includes(baseType) || baseType.startsWith("{") || baseType.includes("<")) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (this.extraTypes.has(baseType)) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
79
|
+
const cls = sourceFile.getClass(baseType);
|
|
80
|
+
if (cls) {
|
|
81
|
+
const properties = cls.getProperties().map((p) => {
|
|
82
|
+
return `${p.getName()}: ${p.getType().getText()};`;
|
|
83
|
+
});
|
|
84
|
+
this.extraTypes.set(baseType, `export interface ${baseType} {
|
|
85
|
+
${properties.join("\n ")}
|
|
86
|
+
}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const intf = sourceFile.getInterface(baseType);
|
|
90
|
+
if (intf) {
|
|
91
|
+
const properties = intf.getProperties().map((p) => {
|
|
92
|
+
return `${p.getName()}: ${p.getType().getText()};`;
|
|
93
|
+
});
|
|
94
|
+
this.extraTypes.set(baseType, `export interface ${baseType} {
|
|
95
|
+
${properties.join("\n ")}
|
|
96
|
+
}`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
buildTypeDefinition(routes) {
|
|
102
|
+
let typeDef = `import { Hono } from 'hono';
|
|
103
|
+
`;
|
|
104
|
+
typeDef += `import type { InferRequestType, InferResponseType } from 'hono/client';
|
|
105
|
+
|
|
106
|
+
`;
|
|
107
|
+
for (const [name, def] of this.extraTypes) {
|
|
108
|
+
typeDef += `${def}
|
|
109
|
+
|
|
110
|
+
`;
|
|
111
|
+
}
|
|
112
|
+
typeDef += `export type AppType = Hono<any, {
|
|
113
|
+
`;
|
|
114
|
+
const routesByPath = {};
|
|
115
|
+
for (const route of routes) {
|
|
116
|
+
if (!routesByPath[route.path]) {
|
|
117
|
+
routesByPath[route.path] = [];
|
|
118
|
+
}
|
|
119
|
+
routesByPath[route.path].push(route);
|
|
120
|
+
}
|
|
121
|
+
for (const [path, pathRoutes] of Object.entries(routesByPath)) {
|
|
122
|
+
typeDef += ` '${path}': {
|
|
123
|
+
`;
|
|
124
|
+
for (const route of pathRoutes) {
|
|
125
|
+
let inputType = "";
|
|
126
|
+
const inputFields = [];
|
|
127
|
+
const bodyParam = route.parameters.find((p) => p.decorators.some((d) => d.name === "Body"));
|
|
128
|
+
if (bodyParam) {
|
|
129
|
+
inputFields.push(`json: ${bodyParam.type}`);
|
|
130
|
+
}
|
|
131
|
+
const queryParam = route.parameters.find((p) => p.decorators.some((d) => d.name === "Query"));
|
|
132
|
+
if (queryParam) {
|
|
133
|
+
inputFields.push(`query: ${queryParam.type}`);
|
|
134
|
+
}
|
|
135
|
+
const paramParams = route.parameters.filter((p) => p.decorators.some((d) => d.name === "Param"));
|
|
136
|
+
if (paramParams.length > 0) {
|
|
137
|
+
const paramFields = paramParams.map((p) => {
|
|
138
|
+
const decorator = p.decorators.find((d) => d.name === "Param");
|
|
139
|
+
const paramName = decorator.arguments[0] || p.name;
|
|
140
|
+
return `${paramName}: ${p.type}`;
|
|
141
|
+
});
|
|
142
|
+
inputFields.push(`param: { ${paramFields.join("; ")} }`);
|
|
143
|
+
}
|
|
144
|
+
inputType = `{ ${inputFields.join(", ")} }`;
|
|
145
|
+
typeDef += ` $${route.method}: {
|
|
146
|
+
`;
|
|
147
|
+
typeDef += ` input: ${inputType},
|
|
148
|
+
`;
|
|
149
|
+
typeDef += ` output: { json: ${route.returnType} },
|
|
150
|
+
`;
|
|
151
|
+
typeDef += ` outputFormat: 'json',
|
|
152
|
+
`;
|
|
153
|
+
typeDef += ` status: 200
|
|
154
|
+
`;
|
|
155
|
+
typeDef += ` },
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
typeDef += ` },
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
typeDef += `}>;
|
|
162
|
+
`;
|
|
163
|
+
return typeDef;
|
|
164
|
+
}
|
|
165
|
+
hasDecorator(node, name) {
|
|
166
|
+
return node.getDecorators().some((d) => d.getName() === name);
|
|
167
|
+
}
|
|
168
|
+
getMethodDecorator(method) {
|
|
169
|
+
const methods = ["Get", "Post", "Put", "Delete", "Patch"];
|
|
170
|
+
return method.getDecorators().find((d) => methods.includes(d.getName()));
|
|
171
|
+
}
|
|
172
|
+
getDecoratorArgument(node, name) {
|
|
173
|
+
const decorator = node.getDecorator(name);
|
|
174
|
+
if (!decorator) return void 0;
|
|
175
|
+
const args = decorator.getArguments();
|
|
176
|
+
if (args.length > 0) {
|
|
177
|
+
const text = args[0].getText();
|
|
178
|
+
return text.replace(/^['"]|['"]$/g, "");
|
|
179
|
+
}
|
|
180
|
+
return void 0;
|
|
181
|
+
}
|
|
182
|
+
normalizePath(prefix, path) {
|
|
183
|
+
const cleanPrefix = prefix ? prefix.replace(/^\/+/, "").replace(/\/+$/, "") : "";
|
|
184
|
+
const cleanPath = path ? path.replace(/^\/+/, "").replace(/\/+$/, "") : "";
|
|
185
|
+
let result = "";
|
|
186
|
+
if (cleanPrefix) result += `/${cleanPrefix}`;
|
|
187
|
+
if (cleanPath) result += `/${cleanPath}`;
|
|
188
|
+
return result || "/";
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
exports.TypeGenerator = TypeGenerator;
|
|
193
|
+
//# sourceMappingURL=index.cjs.map
|
|
194
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generator.ts"],"names":["Project"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Project, ClassDeclaration, MethodDeclaration, Decorator } from 'ts-morph';\nimport * as path from 'path';\n\nexport interface GeneratorOptions {\n tsConfigFilePath: string;\n output?: string;\n}\n\nexport class TypeGenerator {\n constructor(private readonly options: GeneratorOptions) { }\n\n async generate(): Promise<string> {\n const project = new Project({\n tsConfigFilePath: this.options.tsConfigFilePath,\n });\n\n const controllers = this.findControllers(project);\n const routes = this.extractRoutes(controllers, project);\n\n return this.buildTypeDefinition(routes);\n }\n\n private findControllers(project: Project): ClassDeclaration[] {\n const controllers: ClassDeclaration[] = [];\n const sourceFiles = project.getSourceFiles();\n\n for (const sourceFile of sourceFiles) {\n const classes = sourceFile.getClasses();\n for (const cls of classes) {\n if (this.hasDecorator(cls, 'Controller')) {\n controllers.push(cls);\n }\n }\n }\n return controllers;\n }\n\n private extractRoutes(controllers: ClassDeclaration[], project: Project) {\n const routes: any[] = [];\n\n for (const controller of controllers) {\n const controllerPrefix = this.getDecoratorArgument(controller, 'Controller') || '';\n const methods = controller.getMethods();\n\n for (const method of methods) {\n const methodDecorator = this.getMethodDecorator(method);\n if (methodDecorator) {\n const methodPath = this.getDecoratorArgument(method, methodDecorator.getName()) || '/';\n const httpMethod = methodDecorator.getName().toLowerCase(); // Get, Post -> get, post\n const returnType = method.getReturnType().getText();\n\n // Normalize path\n const fullPath = this.normalizePath(controllerPrefix, methodPath);\n\n // Collect return type\n this.collectType(returnType, project);\n\n routes.push({\n path: fullPath,\n method: httpMethod,\n returnType,\n methodName: method.getName(),\n parameters: method.getParameters().map(p => {\n const typeText = p.getType().getText();\n this.collectType(typeText, project);\n return {\n name: p.getName(),\n type: typeText,\n decorators: p.getDecorators().map(d => ({\n name: d.getName(),\n arguments: d.getArguments().map(arg => arg.getText().replace(/^['\"]|['\"]$/g, ''))\n }))\n };\n })\n });\n }\n }\n }\n return routes;\n }\n\n private extraTypes = new Map<string, string>();\n\n private collectType(typeText: string, project: Project) {\n // Simple heuristic: if it starts with uppercase and is not a primitive\n const primitives = ['string', 'number', 'boolean', 'any', 'void', 'null', 'undefined', 'Date'];\n const isArray = typeText.endsWith('[]');\n const baseType = isArray ? typeText.slice(0, -2) : typeText;\n\n if (primitives.includes(baseType) || baseType.startsWith('{') || baseType.includes('<')) {\n return;\n }\n\n if (this.extraTypes.has(baseType)) {\n return;\n }\n\n // Find the class or interface in the project\n for (const sourceFile of project.getSourceFiles()) {\n const cls = sourceFile.getClass(baseType);\n if (cls) {\n const properties = cls.getProperties().map(p => {\n return `${p.getName()}: ${p.getType().getText()};`;\n });\n this.extraTypes.set(baseType, `export interface ${baseType} {\\n ${properties.join('\\n ')}\\n}`);\n return;\n }\n\n const intf = sourceFile.getInterface(baseType);\n if (intf) {\n const properties = intf.getProperties().map(p => {\n return `${p.getName()}: ${p.getType().getText()};`;\n });\n this.extraTypes.set(baseType, `export interface ${baseType} {\\n ${properties.join('\\n ')}\\n}`);\n return;\n }\n }\n }\n\n private buildTypeDefinition(routes: any[]): string {\n let typeDef = `import { Hono } from 'hono';\\n`;\n typeDef += `import type { InferRequestType, InferResponseType } from 'hono/client';\\n\\n`;\n\n // Add extra types\n for (const [name, def] of this.extraTypes) {\n typeDef += `${def}\\n\\n`;\n }\n\n typeDef += `export type AppType = Hono<any, {\\n`;\n\n // Group routes by path\n const routesByPath: Record<string, any[]> = {};\n for (const route of routes) {\n if (!routesByPath[route.path]) {\n routesByPath[route.path] = [];\n }\n routesByPath[route.path].push(route);\n }\n\n for (const [path, pathRoutes] of Object.entries(routesByPath)) {\n typeDef += ` '${path}': {\\n`;\n for (const route of pathRoutes) {\n let inputType = '';\n const inputFields: string[] = [];\n\n // Handle Body\n const bodyParam = route.parameters.find((p: any) => p.decorators.some((d: any) => d.name === 'Body'));\n if (bodyParam) {\n inputFields.push(`json: ${bodyParam.type}`);\n }\n\n // Handle Query\n const queryParam = route.parameters.find((p: any) => p.decorators.some((d: any) => d.name === 'Query'));\n if (queryParam) {\n inputFields.push(`query: ${queryParam.type}`);\n }\n\n // Handle Param\n const paramParams = route.parameters.filter((p: any) => p.decorators.some((d: any) => d.name === 'Param'));\n if (paramParams.length > 0) {\n const paramFields = paramParams.map((p: any) => {\n const decorator = p.decorators.find((d: any) => d.name === 'Param');\n const paramName = decorator.arguments[0] || p.name;\n return `${paramName}: ${p.type}`;\n });\n inputFields.push(`param: { ${paramFields.join('; ')} }`);\n }\n\n inputType = `{ ${inputFields.join(', ')} }`;\n\n typeDef += ` $${route.method}: {\\n`;\n typeDef += ` input: ${inputType},\\n`;\n typeDef += ` output: { json: ${route.returnType} },\\n`;\n typeDef += ` outputFormat: 'json',\\n`;\n typeDef += ` status: 200\\n`;\n typeDef += ` },\\n`;\n }\n typeDef += ` },\\n`;\n }\n\n typeDef += `}>;\\n`;\n return typeDef;\n }\n\n private hasDecorator(node: ClassDeclaration | MethodDeclaration, name: string): boolean {\n return node.getDecorators().some(d => d.getName() === name);\n }\n\n private getMethodDecorator(method: MethodDeclaration): Decorator | undefined {\n const methods = ['Get', 'Post', 'Put', 'Delete', 'Patch'];\n return method.getDecorators().find(d => methods.includes(d.getName()));\n }\n\n private getDecoratorArgument(node: ClassDeclaration | MethodDeclaration, name: string): string | undefined {\n const decorator = node.getDecorator(name);\n if (!decorator) return undefined;\n const args = decorator.getArguments();\n if (args.length > 0) {\n const text = args[0].getText();\n // Remove quotes\n return text.replace(/^['\"]|['\"]$/g, '');\n }\n return undefined;\n }\n\n private normalizePath(prefix: string, path: string): string {\n const cleanPrefix = prefix ? prefix.replace(/^\\/+/, '').replace(/\\/+$/, '') : '';\n const cleanPath = path ? path.replace(/^\\/+/, '').replace(/\\/+$/, '') : '';\n let result = '';\n if (cleanPrefix) result += `/${cleanPrefix}`;\n if (cleanPath) result += `/${cleanPath}`;\n return result || '/';\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface GeneratorOptions {
|
|
2
|
+
tsConfigFilePath: string;
|
|
3
|
+
output?: string;
|
|
4
|
+
}
|
|
5
|
+
declare class TypeGenerator {
|
|
6
|
+
private readonly options;
|
|
7
|
+
constructor(options: GeneratorOptions);
|
|
8
|
+
generate(): Promise<string>;
|
|
9
|
+
private findControllers;
|
|
10
|
+
private extractRoutes;
|
|
11
|
+
private extraTypes;
|
|
12
|
+
private collectType;
|
|
13
|
+
private buildTypeDefinition;
|
|
14
|
+
private hasDecorator;
|
|
15
|
+
private getMethodDecorator;
|
|
16
|
+
private getDecoratorArgument;
|
|
17
|
+
private normalizePath;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { type GeneratorOptions, TypeGenerator };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface GeneratorOptions {
|
|
2
|
+
tsConfigFilePath: string;
|
|
3
|
+
output?: string;
|
|
4
|
+
}
|
|
5
|
+
declare class TypeGenerator {
|
|
6
|
+
private readonly options;
|
|
7
|
+
constructor(options: GeneratorOptions);
|
|
8
|
+
generate(): Promise<string>;
|
|
9
|
+
private findControllers;
|
|
10
|
+
private extractRoutes;
|
|
11
|
+
private extraTypes;
|
|
12
|
+
private collectType;
|
|
13
|
+
private buildTypeDefinition;
|
|
14
|
+
private hasDecorator;
|
|
15
|
+
private getMethodDecorator;
|
|
16
|
+
private getDecoratorArgument;
|
|
17
|
+
private normalizePath;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { type GeneratorOptions, TypeGenerator };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { Project } from 'ts-morph';
|
|
2
|
+
|
|
3
|
+
// src/generator.ts
|
|
4
|
+
var TypeGenerator = class {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
async generate() {
|
|
9
|
+
const project = new Project({
|
|
10
|
+
tsConfigFilePath: this.options.tsConfigFilePath
|
|
11
|
+
});
|
|
12
|
+
const controllers = this.findControllers(project);
|
|
13
|
+
const routes = this.extractRoutes(controllers, project);
|
|
14
|
+
return this.buildTypeDefinition(routes);
|
|
15
|
+
}
|
|
16
|
+
findControllers(project) {
|
|
17
|
+
const controllers = [];
|
|
18
|
+
const sourceFiles = project.getSourceFiles();
|
|
19
|
+
for (const sourceFile of sourceFiles) {
|
|
20
|
+
const classes = sourceFile.getClasses();
|
|
21
|
+
for (const cls of classes) {
|
|
22
|
+
if (this.hasDecorator(cls, "Controller")) {
|
|
23
|
+
controllers.push(cls);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return controllers;
|
|
28
|
+
}
|
|
29
|
+
extractRoutes(controllers, project) {
|
|
30
|
+
const routes = [];
|
|
31
|
+
for (const controller of controllers) {
|
|
32
|
+
const controllerPrefix = this.getDecoratorArgument(controller, "Controller") || "";
|
|
33
|
+
const methods = controller.getMethods();
|
|
34
|
+
for (const method of methods) {
|
|
35
|
+
const methodDecorator = this.getMethodDecorator(method);
|
|
36
|
+
if (methodDecorator) {
|
|
37
|
+
const methodPath = this.getDecoratorArgument(method, methodDecorator.getName()) || "/";
|
|
38
|
+
const httpMethod = methodDecorator.getName().toLowerCase();
|
|
39
|
+
const returnType = method.getReturnType().getText();
|
|
40
|
+
const fullPath = this.normalizePath(controllerPrefix, methodPath);
|
|
41
|
+
this.collectType(returnType, project);
|
|
42
|
+
routes.push({
|
|
43
|
+
path: fullPath,
|
|
44
|
+
method: httpMethod,
|
|
45
|
+
returnType,
|
|
46
|
+
methodName: method.getName(),
|
|
47
|
+
parameters: method.getParameters().map((p) => {
|
|
48
|
+
const typeText = p.getType().getText();
|
|
49
|
+
this.collectType(typeText, project);
|
|
50
|
+
return {
|
|
51
|
+
name: p.getName(),
|
|
52
|
+
type: typeText,
|
|
53
|
+
decorators: p.getDecorators().map((d) => ({
|
|
54
|
+
name: d.getName(),
|
|
55
|
+
arguments: d.getArguments().map((arg) => arg.getText().replace(/^['"]|['"]$/g, ""))
|
|
56
|
+
}))
|
|
57
|
+
};
|
|
58
|
+
})
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return routes;
|
|
64
|
+
}
|
|
65
|
+
extraTypes = /* @__PURE__ */ new Map();
|
|
66
|
+
collectType(typeText, project) {
|
|
67
|
+
const primitives = ["string", "number", "boolean", "any", "void", "null", "undefined", "Date"];
|
|
68
|
+
const isArray = typeText.endsWith("[]");
|
|
69
|
+
const baseType = isArray ? typeText.slice(0, -2) : typeText;
|
|
70
|
+
if (primitives.includes(baseType) || baseType.startsWith("{") || baseType.includes("<")) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (this.extraTypes.has(baseType)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
for (const sourceFile of project.getSourceFiles()) {
|
|
77
|
+
const cls = sourceFile.getClass(baseType);
|
|
78
|
+
if (cls) {
|
|
79
|
+
const properties = cls.getProperties().map((p) => {
|
|
80
|
+
return `${p.getName()}: ${p.getType().getText()};`;
|
|
81
|
+
});
|
|
82
|
+
this.extraTypes.set(baseType, `export interface ${baseType} {
|
|
83
|
+
${properties.join("\n ")}
|
|
84
|
+
}`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const intf = sourceFile.getInterface(baseType);
|
|
88
|
+
if (intf) {
|
|
89
|
+
const properties = intf.getProperties().map((p) => {
|
|
90
|
+
return `${p.getName()}: ${p.getType().getText()};`;
|
|
91
|
+
});
|
|
92
|
+
this.extraTypes.set(baseType, `export interface ${baseType} {
|
|
93
|
+
${properties.join("\n ")}
|
|
94
|
+
}`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
buildTypeDefinition(routes) {
|
|
100
|
+
let typeDef = `import { Hono } from 'hono';
|
|
101
|
+
`;
|
|
102
|
+
typeDef += `import type { InferRequestType, InferResponseType } from 'hono/client';
|
|
103
|
+
|
|
104
|
+
`;
|
|
105
|
+
for (const [name, def] of this.extraTypes) {
|
|
106
|
+
typeDef += `${def}
|
|
107
|
+
|
|
108
|
+
`;
|
|
109
|
+
}
|
|
110
|
+
typeDef += `export type AppType = Hono<any, {
|
|
111
|
+
`;
|
|
112
|
+
const routesByPath = {};
|
|
113
|
+
for (const route of routes) {
|
|
114
|
+
if (!routesByPath[route.path]) {
|
|
115
|
+
routesByPath[route.path] = [];
|
|
116
|
+
}
|
|
117
|
+
routesByPath[route.path].push(route);
|
|
118
|
+
}
|
|
119
|
+
for (const [path, pathRoutes] of Object.entries(routesByPath)) {
|
|
120
|
+
typeDef += ` '${path}': {
|
|
121
|
+
`;
|
|
122
|
+
for (const route of pathRoutes) {
|
|
123
|
+
let inputType = "";
|
|
124
|
+
const inputFields = [];
|
|
125
|
+
const bodyParam = route.parameters.find((p) => p.decorators.some((d) => d.name === "Body"));
|
|
126
|
+
if (bodyParam) {
|
|
127
|
+
inputFields.push(`json: ${bodyParam.type}`);
|
|
128
|
+
}
|
|
129
|
+
const queryParam = route.parameters.find((p) => p.decorators.some((d) => d.name === "Query"));
|
|
130
|
+
if (queryParam) {
|
|
131
|
+
inputFields.push(`query: ${queryParam.type}`);
|
|
132
|
+
}
|
|
133
|
+
const paramParams = route.parameters.filter((p) => p.decorators.some((d) => d.name === "Param"));
|
|
134
|
+
if (paramParams.length > 0) {
|
|
135
|
+
const paramFields = paramParams.map((p) => {
|
|
136
|
+
const decorator = p.decorators.find((d) => d.name === "Param");
|
|
137
|
+
const paramName = decorator.arguments[0] || p.name;
|
|
138
|
+
return `${paramName}: ${p.type}`;
|
|
139
|
+
});
|
|
140
|
+
inputFields.push(`param: { ${paramFields.join("; ")} }`);
|
|
141
|
+
}
|
|
142
|
+
inputType = `{ ${inputFields.join(", ")} }`;
|
|
143
|
+
typeDef += ` $${route.method}: {
|
|
144
|
+
`;
|
|
145
|
+
typeDef += ` input: ${inputType},
|
|
146
|
+
`;
|
|
147
|
+
typeDef += ` output: { json: ${route.returnType} },
|
|
148
|
+
`;
|
|
149
|
+
typeDef += ` outputFormat: 'json',
|
|
150
|
+
`;
|
|
151
|
+
typeDef += ` status: 200
|
|
152
|
+
`;
|
|
153
|
+
typeDef += ` },
|
|
154
|
+
`;
|
|
155
|
+
}
|
|
156
|
+
typeDef += ` },
|
|
157
|
+
`;
|
|
158
|
+
}
|
|
159
|
+
typeDef += `}>;
|
|
160
|
+
`;
|
|
161
|
+
return typeDef;
|
|
162
|
+
}
|
|
163
|
+
hasDecorator(node, name) {
|
|
164
|
+
return node.getDecorators().some((d) => d.getName() === name);
|
|
165
|
+
}
|
|
166
|
+
getMethodDecorator(method) {
|
|
167
|
+
const methods = ["Get", "Post", "Put", "Delete", "Patch"];
|
|
168
|
+
return method.getDecorators().find((d) => methods.includes(d.getName()));
|
|
169
|
+
}
|
|
170
|
+
getDecoratorArgument(node, name) {
|
|
171
|
+
const decorator = node.getDecorator(name);
|
|
172
|
+
if (!decorator) return void 0;
|
|
173
|
+
const args = decorator.getArguments();
|
|
174
|
+
if (args.length > 0) {
|
|
175
|
+
const text = args[0].getText();
|
|
176
|
+
return text.replace(/^['"]|['"]$/g, "");
|
|
177
|
+
}
|
|
178
|
+
return void 0;
|
|
179
|
+
}
|
|
180
|
+
normalizePath(prefix, path) {
|
|
181
|
+
const cleanPrefix = prefix ? prefix.replace(/^\/+/, "").replace(/\/+$/, "") : "";
|
|
182
|
+
const cleanPath = path ? path.replace(/^\/+/, "").replace(/\/+$/, "") : "";
|
|
183
|
+
let result = "";
|
|
184
|
+
if (cleanPrefix) result += `/${cleanPrefix}`;
|
|
185
|
+
if (cleanPath) result += `/${cleanPath}`;
|
|
186
|
+
return result || "/";
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
export { TypeGenerator };
|
|
191
|
+
//# sourceMappingURL=index.js.map
|
|
192
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/generator.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { Project, ClassDeclaration, MethodDeclaration, Decorator } from 'ts-morph';\nimport * as path from 'path';\n\nexport interface GeneratorOptions {\n tsConfigFilePath: string;\n output?: string;\n}\n\nexport class TypeGenerator {\n constructor(private readonly options: GeneratorOptions) { }\n\n async generate(): Promise<string> {\n const project = new Project({\n tsConfigFilePath: this.options.tsConfigFilePath,\n });\n\n const controllers = this.findControllers(project);\n const routes = this.extractRoutes(controllers, project);\n\n return this.buildTypeDefinition(routes);\n }\n\n private findControllers(project: Project): ClassDeclaration[] {\n const controllers: ClassDeclaration[] = [];\n const sourceFiles = project.getSourceFiles();\n\n for (const sourceFile of sourceFiles) {\n const classes = sourceFile.getClasses();\n for (const cls of classes) {\n if (this.hasDecorator(cls, 'Controller')) {\n controllers.push(cls);\n }\n }\n }\n return controllers;\n }\n\n private extractRoutes(controllers: ClassDeclaration[], project: Project) {\n const routes: any[] = [];\n\n for (const controller of controllers) {\n const controllerPrefix = this.getDecoratorArgument(controller, 'Controller') || '';\n const methods = controller.getMethods();\n\n for (const method of methods) {\n const methodDecorator = this.getMethodDecorator(method);\n if (methodDecorator) {\n const methodPath = this.getDecoratorArgument(method, methodDecorator.getName()) || '/';\n const httpMethod = methodDecorator.getName().toLowerCase(); // Get, Post -> get, post\n const returnType = method.getReturnType().getText();\n\n // Normalize path\n const fullPath = this.normalizePath(controllerPrefix, methodPath);\n\n // Collect return type\n this.collectType(returnType, project);\n\n routes.push({\n path: fullPath,\n method: httpMethod,\n returnType,\n methodName: method.getName(),\n parameters: method.getParameters().map(p => {\n const typeText = p.getType().getText();\n this.collectType(typeText, project);\n return {\n name: p.getName(),\n type: typeText,\n decorators: p.getDecorators().map(d => ({\n name: d.getName(),\n arguments: d.getArguments().map(arg => arg.getText().replace(/^['\"]|['\"]$/g, ''))\n }))\n };\n })\n });\n }\n }\n }\n return routes;\n }\n\n private extraTypes = new Map<string, string>();\n\n private collectType(typeText: string, project: Project) {\n // Simple heuristic: if it starts with uppercase and is not a primitive\n const primitives = ['string', 'number', 'boolean', 'any', 'void', 'null', 'undefined', 'Date'];\n const isArray = typeText.endsWith('[]');\n const baseType = isArray ? typeText.slice(0, -2) : typeText;\n\n if (primitives.includes(baseType) || baseType.startsWith('{') || baseType.includes('<')) {\n return;\n }\n\n if (this.extraTypes.has(baseType)) {\n return;\n }\n\n // Find the class or interface in the project\n for (const sourceFile of project.getSourceFiles()) {\n const cls = sourceFile.getClass(baseType);\n if (cls) {\n const properties = cls.getProperties().map(p => {\n return `${p.getName()}: ${p.getType().getText()};`;\n });\n this.extraTypes.set(baseType, `export interface ${baseType} {\\n ${properties.join('\\n ')}\\n}`);\n return;\n }\n\n const intf = sourceFile.getInterface(baseType);\n if (intf) {\n const properties = intf.getProperties().map(p => {\n return `${p.getName()}: ${p.getType().getText()};`;\n });\n this.extraTypes.set(baseType, `export interface ${baseType} {\\n ${properties.join('\\n ')}\\n}`);\n return;\n }\n }\n }\n\n private buildTypeDefinition(routes: any[]): string {\n let typeDef = `import { Hono } from 'hono';\\n`;\n typeDef += `import type { InferRequestType, InferResponseType } from 'hono/client';\\n\\n`;\n\n // Add extra types\n for (const [name, def] of this.extraTypes) {\n typeDef += `${def}\\n\\n`;\n }\n\n typeDef += `export type AppType = Hono<any, {\\n`;\n\n // Group routes by path\n const routesByPath: Record<string, any[]> = {};\n for (const route of routes) {\n if (!routesByPath[route.path]) {\n routesByPath[route.path] = [];\n }\n routesByPath[route.path].push(route);\n }\n\n for (const [path, pathRoutes] of Object.entries(routesByPath)) {\n typeDef += ` '${path}': {\\n`;\n for (const route of pathRoutes) {\n let inputType = '';\n const inputFields: string[] = [];\n\n // Handle Body\n const bodyParam = route.parameters.find((p: any) => p.decorators.some((d: any) => d.name === 'Body'));\n if (bodyParam) {\n inputFields.push(`json: ${bodyParam.type}`);\n }\n\n // Handle Query\n const queryParam = route.parameters.find((p: any) => p.decorators.some((d: any) => d.name === 'Query'));\n if (queryParam) {\n inputFields.push(`query: ${queryParam.type}`);\n }\n\n // Handle Param\n const paramParams = route.parameters.filter((p: any) => p.decorators.some((d: any) => d.name === 'Param'));\n if (paramParams.length > 0) {\n const paramFields = paramParams.map((p: any) => {\n const decorator = p.decorators.find((d: any) => d.name === 'Param');\n const paramName = decorator.arguments[0] || p.name;\n return `${paramName}: ${p.type}`;\n });\n inputFields.push(`param: { ${paramFields.join('; ')} }`);\n }\n\n inputType = `{ ${inputFields.join(', ')} }`;\n\n typeDef += ` $${route.method}: {\\n`;\n typeDef += ` input: ${inputType},\\n`;\n typeDef += ` output: { json: ${route.returnType} },\\n`;\n typeDef += ` outputFormat: 'json',\\n`;\n typeDef += ` status: 200\\n`;\n typeDef += ` },\\n`;\n }\n typeDef += ` },\\n`;\n }\n\n typeDef += `}>;\\n`;\n return typeDef;\n }\n\n private hasDecorator(node: ClassDeclaration | MethodDeclaration, name: string): boolean {\n return node.getDecorators().some(d => d.getName() === name);\n }\n\n private getMethodDecorator(method: MethodDeclaration): Decorator | undefined {\n const methods = ['Get', 'Post', 'Put', 'Delete', 'Patch'];\n return method.getDecorators().find(d => methods.includes(d.getName()));\n }\n\n private getDecoratorArgument(node: ClassDeclaration | MethodDeclaration, name: string): string | undefined {\n const decorator = node.getDecorator(name);\n if (!decorator) return undefined;\n const args = decorator.getArguments();\n if (args.length > 0) {\n const text = args[0].getText();\n // Remove quotes\n return text.replace(/^['\"]|['\"]$/g, '');\n }\n return undefined;\n }\n\n private normalizePath(prefix: string, path: string): string {\n const cleanPrefix = prefix ? prefix.replace(/^\\/+/, '').replace(/\\/+$/, '') : '';\n const cleanPath = path ? path.replace(/^\\/+/, '').replace(/\\/+$/, '') : '';\n let result = '';\n if (cleanPrefix) result += `/${cleanPrefix}`;\n if (cleanPath) result += `/${cleanPath}`;\n return result || '/';\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hono-di/client",
|
|
3
|
+
"version": "0.0.6",
|
|
4
|
+
"description": "Type-safe client generator for Hono-DI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"test": "bun test"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"ts-morph": "^21.0.1",
|
|
25
|
+
"glob": "^10.3.10"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"tsup": "^8.5.1",
|
|
29
|
+
"typescript": "^5.4.0",
|
|
30
|
+
"@types/node": "^22.10.6"
|
|
31
|
+
}
|
|
32
|
+
}
|