imean-service-engine 2.0.0 → 2.0.2
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.d.mts +77 -52
- package/dist/index.d.ts +77 -52
- package/dist/index.js +2078 -1945
- package/dist/index.mjs +2076 -1944
- package/package.json +9 -2
- package/.vscode/settings.json +0 -8
- package/src/core/checker.ts +0 -33
- package/src/core/decorators.test.ts +0 -96
- package/src/core/decorators.ts +0 -68
- package/src/core/engine.test.ts +0 -218
- package/src/core/engine.ts +0 -635
- package/src/core/errors.ts +0 -28
- package/src/core/factory.test.ts +0 -73
- package/src/core/factory.ts +0 -92
- package/src/core/logger.ts +0 -65
- package/src/core/testing.ts +0 -73
- package/src/core/types.ts +0 -191
- package/src/index.ts +0 -49
- package/src/metadata/README.md +0 -422
- package/src/metadata/metadata.test.ts +0 -369
- package/src/metadata/metadata.ts +0 -512
- package/src/plugins/action/action-plugin.test.ts +0 -660
- package/src/plugins/action/decorator.ts +0 -14
- package/src/plugins/action/index.ts +0 -4
- package/src/plugins/action/plugin.ts +0 -349
- package/src/plugins/action/types.ts +0 -49
- package/src/plugins/action/utils.test.ts +0 -196
- package/src/plugins/action/utils.ts +0 -111
- package/src/plugins/cache/adapter.test.ts +0 -689
- package/src/plugins/cache/adapter.ts +0 -324
- package/src/plugins/cache/cache-plugin.test.ts +0 -269
- package/src/plugins/cache/decorator.ts +0 -26
- package/src/plugins/cache/index.ts +0 -20
- package/src/plugins/cache/plugin.ts +0 -299
- package/src/plugins/cache/types.ts +0 -69
- package/src/plugins/client-code/client-code-plugin.test.ts +0 -511
- package/src/plugins/client-code/format.ts +0 -9
- package/src/plugins/client-code/generator.test.ts +0 -52
- package/src/plugins/client-code/generator.ts +0 -263
- package/src/plugins/client-code/index.ts +0 -15
- package/src/plugins/client-code/plugin.ts +0 -158
- package/src/plugins/client-code/types.ts +0 -52
- package/src/plugins/client-code/utils.ts +0 -164
- package/src/plugins/graceful-shutdown/graceful-shutdown-plugin.test.ts +0 -401
- package/src/plugins/graceful-shutdown/index.ts +0 -3
- package/src/plugins/graceful-shutdown/plugin.ts +0 -279
- package/src/plugins/graceful-shutdown/types.ts +0 -17
- package/src/plugins/rate-limit/rate-limit-plugin.example.ts +0 -171
- package/src/plugins/route/components/Layout.tsx +0 -42
- package/src/plugins/route/components/ServiceStatusPage.tsx +0 -141
- package/src/plugins/route/decorator.ts +0 -50
- package/src/plugins/route/index.ts +0 -16
- package/src/plugins/route/plugin.ts +0 -218
- package/src/plugins/route/route-plugin.test.ts +0 -759
- package/src/plugins/route/types.ts +0 -72
- package/src/plugins/schedule/README.md +0 -309
- package/src/plugins/schedule/decorator.ts +0 -25
- package/src/plugins/schedule/index.ts +0 -12
- package/src/plugins/schedule/mock-etcd.ts +0 -145
- package/src/plugins/schedule/plugin.ts +0 -164
- package/src/plugins/schedule/schedule-plugin.test.ts +0 -312
- package/src/plugins/schedule/scheduler.ts +0 -164
- package/src/plugins/schedule/types.ts +0 -94
- package/src/plugins/schedule/utils.test.ts +0 -163
- package/src/plugins/schedule/utils.ts +0 -41
- package/tests/integration/client.test.ts +0 -203
- package/tests/integration/dev-service.ts +0 -301
- package/tests/integration/generated/client.ts +0 -123
- package/tests/integration/start-service.ts +0 -21
- package/tsconfig.json +0 -27
- package/tsup.config.ts +0 -16
- package/vitest.config.ts +0 -19
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import { type z } from "zod";
|
|
2
|
-
import { formatCode } from "./format";
|
|
3
|
-
import type { ModuleInfo } from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 获取 Zod 类型的 TypeScript 类型字符串
|
|
7
|
-
*/
|
|
8
|
-
export function getZodTypeString(
|
|
9
|
-
schema: z.ZodType<any>,
|
|
10
|
-
defaultOptional: boolean = false
|
|
11
|
-
): string {
|
|
12
|
-
// 递归处理可空和可选类型
|
|
13
|
-
function processType(type: z.ZodType<any>): string {
|
|
14
|
-
if (!type) {
|
|
15
|
-
return "unknown";
|
|
16
|
-
}
|
|
17
|
-
// Zod 4.x: 使用 _def 访问内部定义,使用 as any 避免类型错误
|
|
18
|
-
const def = (type as any)._def as any;
|
|
19
|
-
|
|
20
|
-
// Zod 4.x: 使用 def.type 替代 def.typeName
|
|
21
|
-
const typeName = def.type;
|
|
22
|
-
|
|
23
|
-
// 处理可空类型
|
|
24
|
-
if (typeName === "nullable") {
|
|
25
|
-
return `${processType(def.innerType)} | null`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// 处理可选类型
|
|
29
|
-
if (typeName === "optional") {
|
|
30
|
-
return processType(def.innerType);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 处理 transform 类型 (ZodEffects/ZodPipe)
|
|
34
|
-
// Zod 4.x: transform 变成了 pipe 类型,使用 def.in
|
|
35
|
-
if (typeName === "pipe" && def.in) {
|
|
36
|
-
return processType(def.in);
|
|
37
|
-
}
|
|
38
|
-
// Zod 3.x: 使用 def.schema (兼容旧版本)
|
|
39
|
-
if (typeName === "effects" && def.schema) {
|
|
40
|
-
return processType(def.schema);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// 处理基础类型
|
|
44
|
-
// Zod 4.x: 只使用新的类型名称(如 "string", "number" 等)
|
|
45
|
-
switch (typeName) {
|
|
46
|
-
case "string": {
|
|
47
|
-
return "string";
|
|
48
|
-
}
|
|
49
|
-
case "number": {
|
|
50
|
-
return "number";
|
|
51
|
-
}
|
|
52
|
-
case "bigint": {
|
|
53
|
-
return "bigint";
|
|
54
|
-
}
|
|
55
|
-
case "boolean": {
|
|
56
|
-
return "boolean";
|
|
57
|
-
}
|
|
58
|
-
case "array": {
|
|
59
|
-
// Zod 4.x: 使用 def.element
|
|
60
|
-
const elementType = processType(def.element);
|
|
61
|
-
return `${elementType}[]`;
|
|
62
|
-
}
|
|
63
|
-
case "date": {
|
|
64
|
-
return "Date";
|
|
65
|
-
}
|
|
66
|
-
case "object": {
|
|
67
|
-
// Zod 4.x: shape 是一个对象,不再是函数
|
|
68
|
-
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
69
|
-
const props = Object.entries(shape)
|
|
70
|
-
.map(([key, value]) => {
|
|
71
|
-
if (key.includes("-")) {
|
|
72
|
-
key = `'${key}'`;
|
|
73
|
-
}
|
|
74
|
-
const fieldDef = (value as any)._def as any;
|
|
75
|
-
const fieldTypeName = fieldDef.type;
|
|
76
|
-
const isOptional = fieldTypeName === "optional";
|
|
77
|
-
const isDefault = defaultOptional && fieldTypeName === "default";
|
|
78
|
-
const fieldType = processType(
|
|
79
|
-
isOptional ? fieldDef.innerType : value
|
|
80
|
-
);
|
|
81
|
-
return `${key}${isOptional || isDefault ? "?" : ""}: ${fieldType}`;
|
|
82
|
-
})
|
|
83
|
-
.join("; ");
|
|
84
|
-
return `{ ${props} }`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
case "union": {
|
|
88
|
-
return def.options
|
|
89
|
-
.map((opt: z.ZodType<any>) => processType(opt))
|
|
90
|
-
.join(" | ");
|
|
91
|
-
}
|
|
92
|
-
case "null": {
|
|
93
|
-
return "null";
|
|
94
|
-
}
|
|
95
|
-
case "promise": {
|
|
96
|
-
return `Promise<${processType(def.type)}>`;
|
|
97
|
-
}
|
|
98
|
-
case "void": {
|
|
99
|
-
return "void";
|
|
100
|
-
}
|
|
101
|
-
case "record": {
|
|
102
|
-
// Zod 4.x: z.record(valueType) 时,只有 keyType(实际上是 valueType),keyType 默认为 string
|
|
103
|
-
// z.record(keyType, valueType) 时,keyType 和 valueType 都存在
|
|
104
|
-
if (def.valueType) {
|
|
105
|
-
const keyType = def.keyType ? processType(def.keyType) : "string";
|
|
106
|
-
return `Record<${keyType}, ${processType(def.valueType)}>`;
|
|
107
|
-
} else if (def.keyType) {
|
|
108
|
-
// z.record(valueType) 的情况,keyType 实际上是 valueType
|
|
109
|
-
return `Record<string, ${processType(def.keyType)}>`;
|
|
110
|
-
}
|
|
111
|
-
return "Record<string, any>";
|
|
112
|
-
}
|
|
113
|
-
case "map": {
|
|
114
|
-
return `Map<${processType(def.keyType)}, ${processType(
|
|
115
|
-
def.valueType
|
|
116
|
-
)}>`;
|
|
117
|
-
}
|
|
118
|
-
case "any": {
|
|
119
|
-
return "any";
|
|
120
|
-
}
|
|
121
|
-
case "unknown": {
|
|
122
|
-
return "unknown";
|
|
123
|
-
}
|
|
124
|
-
case "enum": {
|
|
125
|
-
// Zod 4.x: 使用 def.entries,entries 是一个对象
|
|
126
|
-
const values = def.entries ? Object.values(def.entries) : [];
|
|
127
|
-
return (
|
|
128
|
-
"(" +
|
|
129
|
-
values.map((opt: unknown) => `"${String(opt)}"`).join(" | ") +
|
|
130
|
-
")"
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
case "default": {
|
|
134
|
-
return processType(def.innerType);
|
|
135
|
-
}
|
|
136
|
-
default: {
|
|
137
|
-
if (type.safeParse(new Uint8Array()).success) {
|
|
138
|
-
return "Uint8Array";
|
|
139
|
-
}
|
|
140
|
-
return "unknown";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return processType(schema);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* 生成客户端代码
|
|
150
|
-
* @param modules 模块信息
|
|
151
|
-
* @returns 生成的客户端代码
|
|
152
|
-
*/
|
|
153
|
-
export async function generateClientCode(
|
|
154
|
-
modules: Record<string, ModuleInfo>
|
|
155
|
-
): Promise<string> {
|
|
156
|
-
const imports = [
|
|
157
|
-
"// 这个文件是自动生成的,请不要手动修改",
|
|
158
|
-
"",
|
|
159
|
-
'import { MicroserviceClient as BaseMicroserviceClient } from "imean-service-client";',
|
|
160
|
-
'export * from "imean-service-client";',
|
|
161
|
-
"",
|
|
162
|
-
].join("\n");
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* 将模块名转换为有效的 TypeScript 标识符
|
|
166
|
-
* 例如: "user-service" -> "UserService"
|
|
167
|
-
*/
|
|
168
|
-
function toPascalCase(moduleName: string): string {
|
|
169
|
-
return moduleName
|
|
170
|
-
.split("-")
|
|
171
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
172
|
-
.join("");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 将模块名转换为有效的 TypeScript 属性名(驼峰命名)
|
|
177
|
-
* 例如: "user-service" -> "userService"
|
|
178
|
-
*/
|
|
179
|
-
function toCamelCase(moduleName: string): string {
|
|
180
|
-
const parts = moduleName.split("-");
|
|
181
|
-
return (
|
|
182
|
-
parts[0] +
|
|
183
|
-
parts
|
|
184
|
-
.slice(1)
|
|
185
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
186
|
-
.join("")
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const interfaces = Object.entries(modules)
|
|
191
|
-
.map(([name, module]) => {
|
|
192
|
-
const methods = Object.entries(module.actions)
|
|
193
|
-
.map(([actionName, action]) => {
|
|
194
|
-
if (!action.params) {
|
|
195
|
-
throw new Error(`Missing params for action ${actionName}`);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// 使用 getZodTypeString 提取参数和返回值类型
|
|
199
|
-
// 优先使用 paramNames,如果没有则使用 param.description,最后使用 arg${index}
|
|
200
|
-
const paramNames = action.paramNames || [];
|
|
201
|
-
const params = action.params
|
|
202
|
-
.map((param: z.ZodType<any>, index: any) => {
|
|
203
|
-
const paramName =
|
|
204
|
-
paramNames[index] ||
|
|
205
|
-
param.description ||
|
|
206
|
-
`arg${index}`;
|
|
207
|
-
// 检查参数是否可选或有默认值
|
|
208
|
-
const paramDef = (param as any)._def as any;
|
|
209
|
-
const isOptional = param.isOptional();
|
|
210
|
-
const hasDefault = paramDef?.type === "default";
|
|
211
|
-
return `${paramName}${isOptional || hasDefault ? "?" : ""}: ${getZodTypeString(
|
|
212
|
-
param,
|
|
213
|
-
true
|
|
214
|
-
)}`;
|
|
215
|
-
})
|
|
216
|
-
.join(", ");
|
|
217
|
-
|
|
218
|
-
const returnType = action.returns
|
|
219
|
-
? getZodTypeString(action.returns)
|
|
220
|
-
: "void";
|
|
221
|
-
|
|
222
|
-
return `
|
|
223
|
-
/**
|
|
224
|
-
* ${action.description || ""}
|
|
225
|
-
*/
|
|
226
|
-
${actionName}: (${params}) => Promise<${
|
|
227
|
-
action.stream ? `AsyncIterable<${returnType}>` : returnType
|
|
228
|
-
}>;`;
|
|
229
|
-
})
|
|
230
|
-
.join("\n ");
|
|
231
|
-
|
|
232
|
-
const interfaceName = `${toPascalCase(name)}Module`;
|
|
233
|
-
return `export interface ${interfaceName} {
|
|
234
|
-
${methods}
|
|
235
|
-
}`;
|
|
236
|
-
})
|
|
237
|
-
.join("\n\n");
|
|
238
|
-
|
|
239
|
-
const clientClass = `export class MicroserviceClient extends BaseMicroserviceClient {
|
|
240
|
-
constructor(options: any) {
|
|
241
|
-
super(options);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
${Object.entries(modules)
|
|
245
|
-
.map(([name, module]) => {
|
|
246
|
-
const methods = Object.entries(module.actions)
|
|
247
|
-
.map(([actionName, action]) => {
|
|
248
|
-
return `${actionName}: { idempotent: ${!!action.idempotence}, stream: ${!!action.stream} }`;
|
|
249
|
-
})
|
|
250
|
-
.join(",\n ");
|
|
251
|
-
|
|
252
|
-
const propertyName = toCamelCase(name);
|
|
253
|
-
const interfaceName = `${toPascalCase(name)}Module`;
|
|
254
|
-
|
|
255
|
-
return `public readonly ${propertyName} = this.registerModule<${interfaceName}>("${name}", {
|
|
256
|
-
${methods}
|
|
257
|
-
});`;
|
|
258
|
-
})
|
|
259
|
-
.join("\n\n ")}
|
|
260
|
-
}`;
|
|
261
|
-
|
|
262
|
-
return await formatCode([imports, interfaces, clientClass].join("\n\n"));
|
|
263
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Client Code Plugin - 客户端代码生成插件
|
|
3
|
-
*
|
|
4
|
-
* 提供自动生成类型化客户端代码的功能,支持服务间互调
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export { ClientCodePlugin } from "./plugin";
|
|
8
|
-
export type { ClientCodePluginOptions } from "./plugin";
|
|
9
|
-
export type { ModuleInfo, ActionInfo } from "./types";
|
|
10
|
-
export { generateClientCode, getZodTypeString } from "./generator";
|
|
11
|
-
export {
|
|
12
|
-
convertHandlersToModuleInfo,
|
|
13
|
-
convertHandlersToModuleInfoWithMetadata,
|
|
14
|
-
} from "./utils";
|
|
15
|
-
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { Context } from "hono";
|
|
2
|
-
import { promises as fs } from "fs";
|
|
3
|
-
import { dirname } from "path";
|
|
4
|
-
import {
|
|
5
|
-
HandlerMetadata,
|
|
6
|
-
Microservice,
|
|
7
|
-
Plugin,
|
|
8
|
-
PluginPriority,
|
|
9
|
-
} from "../../core/types";
|
|
10
|
-
import logger from "../../core/logger";
|
|
11
|
-
import { convertHandlersToModuleInfoWithMetadata } from "./utils";
|
|
12
|
-
import { generateClientCode } from "./generator";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* ClientCodePlugin 配置选项
|
|
16
|
-
*/
|
|
17
|
-
export interface ClientCodePluginOptions {
|
|
18
|
-
/**
|
|
19
|
-
* 客户端代码保存路径(可选)
|
|
20
|
-
* 如果设置,将在生成代码后自动保存到该路径
|
|
21
|
-
* 通常用于开发阶段自动生成客户端代码用于调试或测试
|
|
22
|
-
*
|
|
23
|
-
* @example
|
|
24
|
-
* ```ts
|
|
25
|
-
* new ClientCodePlugin({ clientSavePath: "./generated/client.ts" })
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
clientSavePath?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* ClientCodePlugin - 客户端代码生成插件
|
|
33
|
-
* 收集所有 Action handlers,生成类型化的客户端代码,
|
|
34
|
-
* 并提供 /client.ts 路由供远程下载
|
|
35
|
-
*/
|
|
36
|
-
export class ClientCodePlugin implements Plugin {
|
|
37
|
-
public readonly name = "client-code-plugin";
|
|
38
|
-
public readonly priority = PluginPriority.ROUTE; // 路由插件优先级,在 ActionPlugin 之后
|
|
39
|
-
|
|
40
|
-
private engine!: Microservice;
|
|
41
|
-
private actionHandlers: HandlerMetadata[] = [];
|
|
42
|
-
private generatedCode: string | null = null;
|
|
43
|
-
private readonly clientSavePath?: string;
|
|
44
|
-
|
|
45
|
-
constructor(options?: ClientCodePluginOptions) {
|
|
46
|
-
this.clientSavePath = options?.clientSavePath;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 引擎初始化钩子
|
|
51
|
-
*/
|
|
52
|
-
onInit(engine: Microservice): void {
|
|
53
|
-
this.engine = engine;
|
|
54
|
-
logger.info("ClientCodePlugin initialized");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Handler加载钩子:收集所有 Action handlers
|
|
59
|
-
*/
|
|
60
|
-
onHandlerLoad(handlers: HandlerMetadata[]): void {
|
|
61
|
-
// 筛选出所有 type="action" 的 Handler
|
|
62
|
-
const actionHandlers = handlers.filter(
|
|
63
|
-
(handler) => handler.type === "action"
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
this.actionHandlers = actionHandlers;
|
|
67
|
-
logger.info(
|
|
68
|
-
`ClientCodePlugin collected ${actionHandlers.length} action handler(s)`
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* 引擎启动后钩子:注册客户端代码下载路由
|
|
74
|
-
*/
|
|
75
|
-
async onAfterStart(engine: Microservice): Promise<void> {
|
|
76
|
-
// 生成客户端代码
|
|
77
|
-
await this.generateCode();
|
|
78
|
-
|
|
79
|
-
// 注册路由:{prefix}/client.ts
|
|
80
|
-
const prefix = engine.options.prefix || "";
|
|
81
|
-
const clientPath = prefix ? `${prefix}/client.ts` : "/client.ts";
|
|
82
|
-
|
|
83
|
-
// 获取 Hono 实例
|
|
84
|
-
const hono = engine.getHono();
|
|
85
|
-
|
|
86
|
-
// 注册路由
|
|
87
|
-
hono.get(clientPath, async (ctx: Context) => {
|
|
88
|
-
// 如果代码还未生成,重新生成
|
|
89
|
-
if (!this.generatedCode) {
|
|
90
|
-
await this.generateCode();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 返回 TypeScript 代码
|
|
94
|
-
return ctx.text(this.generatedCode || "", 200, {
|
|
95
|
-
"Content-Type": "text/typescript; charset=utf-8",
|
|
96
|
-
"Content-Disposition": `attachment; filename="client.ts"`,
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
logger.info(`Client code available at ${clientPath}`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* 生成客户端代码
|
|
105
|
-
*/
|
|
106
|
-
private async generateCode(): Promise<void> {
|
|
107
|
-
try {
|
|
108
|
-
// 获取模块元数据映射函数
|
|
109
|
-
const getModuleMetadata = (moduleClass: any) => {
|
|
110
|
-
const modules = this.engine.getModules();
|
|
111
|
-
const moduleMetadata = modules.find((m) => m.clazz === moduleClass);
|
|
112
|
-
return moduleMetadata
|
|
113
|
-
? { name: moduleMetadata.name }
|
|
114
|
-
: undefined;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// 将 handlers 转换为 ModuleInfo 格式
|
|
118
|
-
const modules = convertHandlersToModuleInfoWithMetadata(
|
|
119
|
-
this.actionHandlers,
|
|
120
|
-
getModuleMetadata
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
// 生成代码
|
|
124
|
-
this.generatedCode = await generateClientCode(modules);
|
|
125
|
-
|
|
126
|
-
logger.debug(
|
|
127
|
-
`Generated client code for ${Object.keys(modules).length} module(s)`
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
// 如果设置了保存路径,保存代码到文件
|
|
131
|
-
if (this.clientSavePath && this.generatedCode) {
|
|
132
|
-
await this.saveCodeToFile(this.clientSavePath, this.generatedCode);
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
logger.error("Failed to generate client code", error);
|
|
136
|
-
this.generatedCode = "// Error: Failed to generate client code";
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* 保存代码到文件
|
|
142
|
-
*/
|
|
143
|
-
private async saveCodeToFile(path: string, code: string): Promise<void> {
|
|
144
|
-
try {
|
|
145
|
-
// 确保目录存在
|
|
146
|
-
const dir = dirname(path);
|
|
147
|
-
await fs.mkdir(dir, { recursive: true });
|
|
148
|
-
|
|
149
|
-
// 写入文件
|
|
150
|
-
await fs.writeFile(path, code, "utf-8");
|
|
151
|
-
logger.info(`Client code saved to ${path}`);
|
|
152
|
-
} catch (error) {
|
|
153
|
-
logger.error(`Failed to save client code to ${path}`, error);
|
|
154
|
-
// 不抛出错误,避免影响主流程
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Action 信息(用于生成客户端代码)
|
|
5
|
-
*/
|
|
6
|
-
export interface ActionInfo {
|
|
7
|
-
/**
|
|
8
|
-
* 动作描述
|
|
9
|
-
*/
|
|
10
|
-
description?: string;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* 参数校验 Schema(使用 zod)
|
|
14
|
-
*/
|
|
15
|
-
params: z.ZodTypeAny[];
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* 返回值校验 Schema(使用 zod)
|
|
19
|
-
*/
|
|
20
|
-
returns?: z.ZodTypeAny;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 是否流式返回(默认 false)
|
|
24
|
-
*/
|
|
25
|
-
stream?: boolean;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* 是否幂等(默认 false)
|
|
29
|
-
*/
|
|
30
|
-
idempotence?: boolean;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 参数名称数组(从方法定义中提取)
|
|
34
|
-
*/
|
|
35
|
-
paramNames?: string[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 模块信息(用于生成客户端代码)
|
|
40
|
-
*/
|
|
41
|
-
export interface ModuleInfo {
|
|
42
|
-
/**
|
|
43
|
-
* 模块名称
|
|
44
|
-
*/
|
|
45
|
-
name: string;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 模块的所有 actions
|
|
49
|
-
*/
|
|
50
|
-
actions: Record<string, ActionInfo>;
|
|
51
|
-
}
|
|
52
|
-
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import { HandlerMetadata } from "../../core/types";
|
|
2
|
-
import { ActionOptions } from "../action/types";
|
|
3
|
-
import { ModuleInfo, ActionInfo } from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 从函数中提取参数名称
|
|
7
|
-
* @param func 函数对象
|
|
8
|
-
* @returns 参数名称数组
|
|
9
|
-
*/
|
|
10
|
-
function extractParamNames(func: Function): string[] {
|
|
11
|
-
if (!func || typeof func !== "function") {
|
|
12
|
-
return [];
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
// 获取函数的字符串表示
|
|
17
|
-
const funcStr = func.toString();
|
|
18
|
-
|
|
19
|
-
// 匹配函数参数部分
|
|
20
|
-
// 支持以下格式:
|
|
21
|
-
// function name(arg1, arg2) { ... }
|
|
22
|
-
// (arg1, arg2) => { ... }
|
|
23
|
-
// async function name(arg1, arg2) { ... }
|
|
24
|
-
// async (arg1, arg2) => { ... }
|
|
25
|
-
const match = funcStr.match(
|
|
26
|
-
/(?:async\s+)?(?:function\s+\w*\s*)?\(([^)]*)\)|(?:async\s+)?\(([^)]*)\)\s*=>/
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
if (!match) {
|
|
30
|
-
return [];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 获取参数部分(可能是 match[1] 或 match[2])
|
|
34
|
-
const paramsStr = match[1] || match[2] || "";
|
|
35
|
-
|
|
36
|
-
if (!paramsStr.trim()) {
|
|
37
|
-
return [];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 分割参数并清理
|
|
41
|
-
return paramsStr
|
|
42
|
-
.split(",")
|
|
43
|
-
.map((param) => {
|
|
44
|
-
// 移除注释、默认值、类型注解等
|
|
45
|
-
// 例如: "arg1: string = 'default'" -> "arg1"
|
|
46
|
-
// 例如: "arg2 /* comment */" -> "arg2"
|
|
47
|
-
return param
|
|
48
|
-
.replace(/\/\*.*?\*\//g, "") // 移除块注释
|
|
49
|
-
.replace(/\/\/.*$/g, "") // 移除行注释
|
|
50
|
-
.replace(/:\s*[^=,]+/g, "") // 移除类型注解
|
|
51
|
-
.replace(/\s*=\s*[^,]+/g, "") // 移除默认值
|
|
52
|
-
.trim();
|
|
53
|
-
})
|
|
54
|
-
.filter((name) => name.length > 0);
|
|
55
|
-
} catch (error) {
|
|
56
|
-
// 如果解析失败,返回空数组
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 将 HandlerMetadata 数组转换为 ModuleInfo 格式
|
|
63
|
-
* @param handlers Action handlers
|
|
64
|
-
* @returns 模块信息映射
|
|
65
|
-
*/
|
|
66
|
-
export function convertHandlersToModuleInfo(
|
|
67
|
-
handlers: HandlerMetadata[]
|
|
68
|
-
): Record<string, ModuleInfo> {
|
|
69
|
-
const modules: Record<string, ModuleInfo> = {};
|
|
70
|
-
|
|
71
|
-
// 筛选出所有 type="action" 的 handlers
|
|
72
|
-
const actionHandlers = handlers.filter(
|
|
73
|
-
(handler) => handler.type === "action"
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
for (const handler of actionHandlers) {
|
|
77
|
-
const actionOptions = handler.options as ActionOptions;
|
|
78
|
-
const moduleClass = handler.module;
|
|
79
|
-
const methodName = handler.methodName;
|
|
80
|
-
|
|
81
|
-
// 获取模块名称(从模块元数据中获取,这里需要从引擎获取)
|
|
82
|
-
// 暂时使用类名作为模块名(后续会从引擎获取真实模块名)
|
|
83
|
-
const moduleName = moduleClass.name.toLowerCase().replace(/service$/, "");
|
|
84
|
-
|
|
85
|
-
// 初始化模块信息
|
|
86
|
-
if (!modules[moduleName]) {
|
|
87
|
-
modules[moduleName] = {
|
|
88
|
-
name: moduleName,
|
|
89
|
-
actions: {},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 解析方法参数名称
|
|
94
|
-
const paramNames = extractParamNames(handler.method);
|
|
95
|
-
|
|
96
|
-
// 添加 action 信息
|
|
97
|
-
modules[moduleName].actions[methodName] = {
|
|
98
|
-
description: actionOptions.description,
|
|
99
|
-
params: actionOptions.params || [],
|
|
100
|
-
returns: actionOptions.returns,
|
|
101
|
-
stream: actionOptions.stream || false,
|
|
102
|
-
idempotence: actionOptions.idempotence || false,
|
|
103
|
-
paramNames,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return modules;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 从引擎获取模块信息并转换为 ModuleInfo 格式
|
|
112
|
-
* @param handlers Action handlers
|
|
113
|
-
* @param getModuleMetadata 获取模块元数据的函数
|
|
114
|
-
* @returns 模块信息映射
|
|
115
|
-
*/
|
|
116
|
-
export function convertHandlersToModuleInfoWithMetadata(
|
|
117
|
-
handlers: HandlerMetadata[],
|
|
118
|
-
getModuleMetadata: (moduleClass: any) => { name: string } | undefined
|
|
119
|
-
): Record<string, ModuleInfo> {
|
|
120
|
-
const modules: Record<string, ModuleInfo> = {};
|
|
121
|
-
|
|
122
|
-
// 筛选出所有 type="action" 的 handlers
|
|
123
|
-
const actionHandlers = handlers.filter(
|
|
124
|
-
(handler) => handler.type === "action"
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
for (const handler of actionHandlers) {
|
|
128
|
-
const actionOptions = handler.options as ActionOptions;
|
|
129
|
-
const moduleClass = handler.module;
|
|
130
|
-
const methodName = handler.methodName;
|
|
131
|
-
|
|
132
|
-
// 从引擎获取模块元数据
|
|
133
|
-
const moduleMetadata = getModuleMetadata(moduleClass);
|
|
134
|
-
if (!moduleMetadata) {
|
|
135
|
-
continue; // 跳过没有元数据的模块
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const moduleName = moduleMetadata.name;
|
|
139
|
-
|
|
140
|
-
// 初始化模块信息
|
|
141
|
-
if (!modules[moduleName]) {
|
|
142
|
-
modules[moduleName] = {
|
|
143
|
-
name: moduleName,
|
|
144
|
-
actions: {},
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 解析方法参数名称
|
|
149
|
-
const paramNames = extractParamNames(handler.method);
|
|
150
|
-
|
|
151
|
-
// 添加 action 信息
|
|
152
|
-
modules[moduleName].actions[methodName] = {
|
|
153
|
-
description: actionOptions.description,
|
|
154
|
-
params: actionOptions.params || [],
|
|
155
|
-
returns: actionOptions.returns,
|
|
156
|
-
stream: actionOptions.stream || false,
|
|
157
|
-
idempotence: actionOptions.idempotence || false,
|
|
158
|
-
paramNames,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return modules;
|
|
163
|
-
}
|
|
164
|
-
|