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.
Files changed (72) hide show
  1. package/dist/index.d.mts +77 -52
  2. package/dist/index.d.ts +77 -52
  3. package/dist/index.js +2078 -1945
  4. package/dist/index.mjs +2076 -1944
  5. package/package.json +9 -2
  6. package/.vscode/settings.json +0 -8
  7. package/src/core/checker.ts +0 -33
  8. package/src/core/decorators.test.ts +0 -96
  9. package/src/core/decorators.ts +0 -68
  10. package/src/core/engine.test.ts +0 -218
  11. package/src/core/engine.ts +0 -635
  12. package/src/core/errors.ts +0 -28
  13. package/src/core/factory.test.ts +0 -73
  14. package/src/core/factory.ts +0 -92
  15. package/src/core/logger.ts +0 -65
  16. package/src/core/testing.ts +0 -73
  17. package/src/core/types.ts +0 -191
  18. package/src/index.ts +0 -49
  19. package/src/metadata/README.md +0 -422
  20. package/src/metadata/metadata.test.ts +0 -369
  21. package/src/metadata/metadata.ts +0 -512
  22. package/src/plugins/action/action-plugin.test.ts +0 -660
  23. package/src/plugins/action/decorator.ts +0 -14
  24. package/src/plugins/action/index.ts +0 -4
  25. package/src/plugins/action/plugin.ts +0 -349
  26. package/src/plugins/action/types.ts +0 -49
  27. package/src/plugins/action/utils.test.ts +0 -196
  28. package/src/plugins/action/utils.ts +0 -111
  29. package/src/plugins/cache/adapter.test.ts +0 -689
  30. package/src/plugins/cache/adapter.ts +0 -324
  31. package/src/plugins/cache/cache-plugin.test.ts +0 -269
  32. package/src/plugins/cache/decorator.ts +0 -26
  33. package/src/plugins/cache/index.ts +0 -20
  34. package/src/plugins/cache/plugin.ts +0 -299
  35. package/src/plugins/cache/types.ts +0 -69
  36. package/src/plugins/client-code/client-code-plugin.test.ts +0 -511
  37. package/src/plugins/client-code/format.ts +0 -9
  38. package/src/plugins/client-code/generator.test.ts +0 -52
  39. package/src/plugins/client-code/generator.ts +0 -263
  40. package/src/plugins/client-code/index.ts +0 -15
  41. package/src/plugins/client-code/plugin.ts +0 -158
  42. package/src/plugins/client-code/types.ts +0 -52
  43. package/src/plugins/client-code/utils.ts +0 -164
  44. package/src/plugins/graceful-shutdown/graceful-shutdown-plugin.test.ts +0 -401
  45. package/src/plugins/graceful-shutdown/index.ts +0 -3
  46. package/src/plugins/graceful-shutdown/plugin.ts +0 -279
  47. package/src/plugins/graceful-shutdown/types.ts +0 -17
  48. package/src/plugins/rate-limit/rate-limit-plugin.example.ts +0 -171
  49. package/src/plugins/route/components/Layout.tsx +0 -42
  50. package/src/plugins/route/components/ServiceStatusPage.tsx +0 -141
  51. package/src/plugins/route/decorator.ts +0 -50
  52. package/src/plugins/route/index.ts +0 -16
  53. package/src/plugins/route/plugin.ts +0 -218
  54. package/src/plugins/route/route-plugin.test.ts +0 -759
  55. package/src/plugins/route/types.ts +0 -72
  56. package/src/plugins/schedule/README.md +0 -309
  57. package/src/plugins/schedule/decorator.ts +0 -25
  58. package/src/plugins/schedule/index.ts +0 -12
  59. package/src/plugins/schedule/mock-etcd.ts +0 -145
  60. package/src/plugins/schedule/plugin.ts +0 -164
  61. package/src/plugins/schedule/schedule-plugin.test.ts +0 -312
  62. package/src/plugins/schedule/scheduler.ts +0 -164
  63. package/src/plugins/schedule/types.ts +0 -94
  64. package/src/plugins/schedule/utils.test.ts +0 -163
  65. package/src/plugins/schedule/utils.ts +0 -41
  66. package/tests/integration/client.test.ts +0 -203
  67. package/tests/integration/dev-service.ts +0 -301
  68. package/tests/integration/generated/client.ts +0 -123
  69. package/tests/integration/start-service.ts +0 -21
  70. package/tsconfig.json +0 -27
  71. package/tsup.config.ts +0 -16
  72. package/vitest.config.ts +0 -19
@@ -1,635 +0,0 @@
1
- import { serve } from "@hono/node-server";
2
- import { Hono } from "hono";
3
- import * as net from "net";
4
- import { getClassesByKey, getClassMetadata } from "../metadata/metadata";
5
- import { getAllHandlerMetadata } from "./decorators";
6
- import { DuplicateModuleError, PluginNameRequiredError } from "./errors";
7
- import logger from "./logger";
8
- import {
9
- Class,
10
- HandlerMetadata,
11
- HandlerWrapper,
12
- MicroserviceOptions,
13
- ModuleMetadata,
14
- Plugin,
15
- PluginPriority,
16
- } from "./types";
17
-
18
- // 注意:MODULE_METADATA_KEY 已移除,每个工厂实例使用唯一的 key
19
-
20
- /**
21
- * 微服务引擎实现类
22
- * @internal 此类型仅供内部使用,用户应通过 Factory.create() 创建引擎实例
23
- */
24
- export class Microservice<ModuleOptions = Record<string, any>> {
25
- // 存储已注册的模块
26
- private modules: ModuleMetadata<ModuleOptions>[] = [];
27
- // 存储已注册的插件(按注册顺序)
28
- protected plugins: Plugin<Record<string, any>>[] = [];
29
- // 插件注册表(以name为键,用于覆盖逻辑)
30
- protected pluginRegistry: Map<string, Plugin<Record<string, any>>> =
31
- new Map();
32
- // 模块实例缓存(单例管理)
33
- private moduleInstances: Map<Class, any> = new Map();
34
- // 引擎配置(冻结,只读)
35
- public readonly options: Readonly<MicroserviceOptions>;
36
- // 是否已启动
37
- private started: boolean = false;
38
- // 模块元数据键(用于通过双向访问查找模块)
39
- private readonly moduleMetadataKey: symbol;
40
-
41
- // 实际使用的端口(启动后设置)
42
- private actualPort: number | null = null;
43
- // Hono 实例(由引擎统一管理)
44
- private hono: Hono = new Hono();
45
- // HTTP 服务器实例
46
- private server: any = null;
47
-
48
- constructor(options: MicroserviceOptions, moduleMetadataKey: symbol) {
49
- // 合并默认配置并冻结
50
- this.options = Object.freeze({
51
- hostname: "0.0.0.0",
52
- ...options,
53
- });
54
- this.moduleMetadataKey = moduleMetadataKey;
55
- }
56
-
57
- /**
58
- * 获取 Hono 实例(供插件使用)
59
- */
60
- getHono(): Hono {
61
- return this.hono;
62
- }
63
-
64
- /**
65
- * 内部注册插件方法(供构造函数和子类使用)
66
- * @protected
67
- */
68
- protected registerPlugin<T>(plugin: Plugin<T>): void {
69
- // 校验插件名称
70
- if (!plugin.name || plugin.name.trim() === "") {
71
- throw new PluginNameRequiredError();
72
- }
73
-
74
- // 处理插件覆盖逻辑
75
- const existingPlugin = this.pluginRegistry.get(plugin.name);
76
- if (existingPlugin) {
77
- // 移除旧插件
78
- const oldIndex = this.plugins.indexOf(existingPlugin);
79
- if (oldIndex >= 0) {
80
- this.plugins.splice(oldIndex, 1);
81
- }
82
- logger.info(`Override plugin: ${plugin.name}`);
83
- }
84
-
85
- // 注册新插件(使用类型断言,因为运行时兼容)
86
- this.pluginRegistry.set(plugin.name, plugin as Plugin<Record<string, any>>);
87
- this.plugins.push(plugin as Plugin<Record<string, any>>);
88
- }
89
-
90
- /**
91
- * 加载并注册所有模块(通过唯一的 key 查找)
92
- * 在引擎启动时调用,使用双向访问机制查找所有被装饰的类
93
- *
94
- * 注意:
95
- * - 每个引擎实例使用唯一的 moduleMetadataKey,实现隔离
96
- * - 模块类是静态的,可以被多个引擎实例共享
97
- * - 每个引擎实例会创建独立的模块实例,互不影响
98
- */
99
- private loadModules(): void {
100
- // 清空已注册的模块(每个引擎实例独立管理)
101
- this.modules = [];
102
-
103
- // 通过唯一的 key 获取所有被装饰的类(使用双向访问)
104
- const moduleClasses = getClassesByKey(this.moduleMetadataKey);
105
-
106
- for (const moduleClass of moduleClasses) {
107
- // 从元数据中获取模块信息
108
- const metadata = getClassMetadata(moduleClass, this.moduleMetadataKey);
109
- const moduleName = metadata.name || moduleClass.name;
110
-
111
- // 检查模块名是否已注册(在同一引擎实例中,不允许同名模块)
112
- // 注意:不同引擎实例可以注册同名模块,因为它们创建的是不同的实例
113
- const existingModule = this.modules.find((m) => m.name === moduleName);
114
- if (existingModule) {
115
- throw new DuplicateModuleError(moduleName);
116
- }
117
-
118
- // 注册模块到引擎
119
- const moduleMetadata: ModuleMetadata<ModuleOptions> = {
120
- name: moduleName,
121
- clazz: moduleClass,
122
- options: (metadata.options || {}) as ModuleOptions,
123
- };
124
-
125
- this.modules.push(moduleMetadata);
126
- }
127
- }
128
-
129
- /**
130
- * 加载Handler元数据(平铺结构)
131
- * 每个装饰器都是独立的HandlerMetadata条目
132
- * 为每个方法创建包装链管理器,提供简单的 wrap API
133
- */
134
- private loadHandlerMetadata(): HandlerMetadata[] {
135
- const handlers: HandlerMetadata[] = [];
136
- // 为每个方法维护包装链(key: moduleName.methodName)
137
- const wrapperChains = new Map<string, HandlerWrapper[]>();
138
- // 保存原始方法引用(key: moduleName.methodName)
139
- const originalMethods = new Map<string, Function>();
140
-
141
- for (const module of this.modules) {
142
- // 重要:先实例化模块类,触发装饰器的 addInitializer,收集元数据
143
- // 这确保了 Stage 3 装饰器的元数据能够被正确收集
144
- if (!this.moduleInstances.has(module.clazz)) {
145
- this.get(module.clazz);
146
- }
147
-
148
- // 获取模块类的所有Handler元数据
149
- const allMetadata = getAllHandlerMetadata(module.clazz);
150
-
151
- for (const [methodName, metadataList] of allMetadata.entries()) {
152
- const method = (module.clazz.prototype as any)[methodName];
153
- const methodNameStr = String(methodName);
154
- const methodKey = `${module.clazz.name}.${methodNameStr}`;
155
-
156
- // 保存原始方法
157
- if (!originalMethods.has(methodKey)) {
158
- originalMethods.set(methodKey, method);
159
- wrapperChains.set(methodKey, []);
160
- }
161
-
162
- // 将每个装饰器元数据转换为独立的HandlerMetadata条目
163
- for (const meta of metadataList) {
164
- const chain = wrapperChains.get(methodKey)!;
165
-
166
- handlers.push({
167
- ...meta,
168
- method: originalMethods.get(methodKey)!,
169
- methodName: methodNameStr,
170
- module: module.clazz,
171
- // 提供简单的 wrap API:插件只需要调用这个方法
172
- wrap: (wrapper: HandlerWrapper) => {
173
- chain.push(wrapper);
174
- },
175
- });
176
- }
177
- }
178
- }
179
-
180
- // 保存包装链和原始方法,供 applyWrapperChains 使用
181
- (this as any).__wrapperChains__ = wrapperChains;
182
- (this as any).__originalMethods__ = originalMethods;
183
-
184
- return handlers;
185
- }
186
-
187
- /**
188
- * 应用所有包装链到原型
189
- * 在所有插件执行完 onHandlerLoad 后调用
190
- */
191
- private applyWrapperChains(): void {
192
- const wrapperChains = (this as any).__wrapperChains__ as Map<
193
- string,
194
- HandlerWrapper[]
195
- >;
196
- const originalMethods = (this as any).__originalMethods__ as Map<
197
- string,
198
- Function
199
- >;
200
-
201
- if (!wrapperChains || !originalMethods) return;
202
-
203
- // 为每个方法应用包装链
204
- for (const [methodKey, chain] of wrapperChains.entries()) {
205
- if (chain.length === 0) continue;
206
-
207
- // 解析方法键
208
- const [moduleName, methodName] = methodKey.split(".");
209
- const module = this.modules.find((m) => m.clazz.name === moduleName);
210
- if (!module) continue;
211
-
212
- const originalMethod = originalMethods.get(methodKey)!;
213
- const prototype = module.clazz.prototype;
214
-
215
- // 从后往前构建包装链(最后一个包装器在最外层)
216
- let wrappedMethod = originalMethod;
217
-
218
- for (let i = chain.length - 1; i >= 0; i--) {
219
- const wrapper = chain[i];
220
- const next = wrappedMethod;
221
-
222
- wrappedMethod = async function (this: any, ...args: any[]) {
223
- return wrapper(() => next.apply(this, args), this, ...args);
224
- } as Function;
225
- }
226
-
227
- // 更新原型
228
- prototype[methodName] = wrappedMethod;
229
- }
230
- }
231
-
232
- /**
233
- * 检查端口是否可用
234
- */
235
- private async isPortAvailable(
236
- port: number,
237
- hostname: string
238
- ): Promise<boolean> {
239
- return new Promise((resolve) => {
240
- const server = net.createServer();
241
- server.unref();
242
- server.listen(port, hostname, () => {
243
- server.once("close", () => resolve(true));
244
- server.close();
245
- });
246
- server.on("error", () => resolve(false));
247
- });
248
- }
249
-
250
- /**
251
- * 寻找一个随机的可用端口
252
- */
253
- private getRandomPort(hostname: string): Promise<number> {
254
- return new Promise((resolve, reject) => {
255
- const server = net.createServer();
256
- server.unref(); // 防止 server.listen 保持进程活动
257
- server.on("error", reject);
258
- server.listen(0, hostname, () => {
259
- const port = (server.address() as net.AddressInfo).port;
260
- server.close((err) => {
261
- if (err) {
262
- return reject(err);
263
- }
264
- resolve(port);
265
- });
266
- });
267
- });
268
- }
269
-
270
- /**
271
- * 启动引擎
272
- * @param requestedPort 启动端口(可选,默认0,表示随机端口)
273
- * @returns 实际使用的端口号
274
- */
275
- async start(requestedPort: number = 0): Promise<number> {
276
- if (this.started) {
277
- throw new Error("Engine is already started");
278
- }
279
-
280
- try {
281
- // 1. 确定实际使用的端口
282
- const { port, hostname } = await this.determinePort(requestedPort);
283
-
284
- // 2. 初始化模块和插件
285
- this.initializeModulesAndPlugins();
286
-
287
- // 3. 处理 Handler 元数据和插件包装
288
- this.processHandlers();
289
-
290
- // 4. 执行插件启动钩子
291
- await this.executePluginStartHooks();
292
-
293
- // 5. 启动 HTTP 服务器
294
- this.startHttpServer(port, hostname);
295
-
296
- // 6. 标记为已启动
297
- this.started = true;
298
- logger.info(
299
- `${this.options.name} v${this.options.version} started successfully on port ${this.actualPort}`
300
- );
301
-
302
- return this.actualPort!;
303
- } catch (error) {
304
- logger.error("Failed to start engine", error);
305
- throw error;
306
- }
307
- }
308
-
309
- /**
310
- * 确定实际使用的端口
311
- */
312
- private async determinePort(
313
- requestedPort: number
314
- ): Promise<{ port: number; hostname: string }> {
315
- const hostname = this.options.hostname || "0.0.0.0";
316
-
317
- if (requestedPort !== 0) {
318
- const portAvailable = await this.isPortAvailable(requestedPort, hostname);
319
- if (portAvailable) {
320
- this.actualPort = requestedPort;
321
- return { port: this.actualPort, hostname };
322
- }
323
- logger.info(
324
- `Port ${requestedPort} is in use, finding a random available port...`
325
- );
326
- }
327
-
328
- this.actualPort = await this.getRandomPort(hostname);
329
- return { port: this.actualPort!, hostname };
330
- }
331
-
332
- /**
333
- * 初始化模块和插件
334
- */
335
- private initializeModulesAndPlugins(): void {
336
- // 加载并注册所有模块(从全局注册表扫描)
337
- this.loadModules();
338
-
339
- // 执行插件 onInit
340
- this.executePluginHook("onInit", (plugin) => {
341
- plugin.onInit?.(this as Microservice<Record<string, any>>);
342
- });
343
-
344
- // 执行插件 onModuleLoad
345
- this.executePluginHook("onModuleLoad", (plugin) => {
346
- plugin.onModuleLoad?.(
347
- this.modules as ModuleMetadata<Record<string, any>>[]
348
- );
349
- });
350
- }
351
-
352
- /**
353
- * 处理 Handler 元数据和插件包装
354
- */
355
- private processHandlers(): void {
356
- // 加载 Handler 元数据(平铺结构,包含 wrap API)
357
- const handlers = this.loadHandlerMetadata();
358
-
359
- // 按优先级排序插件
360
- const sortedPlugins = this.sortPluginsByPriority();
361
-
362
- // 分离包装插件和路由插件
363
- const { wrapperPlugins, routePlugins } =
364
- this.separateWrapperAndRoutePlugins(sortedPlugins);
365
-
366
- // 先执行包装插件(按优先级排序),它们可以调用 handler.wrap()
367
- this.executePluginHook(
368
- "onHandlerLoad",
369
- (plugin) => {
370
- plugin.onHandlerLoad?.(handlers);
371
- },
372
- wrapperPlugins
373
- );
374
-
375
- // 应用所有包装链到原型(在所有包装插件执行完后,RoutePlugin 执行前)
376
- this.applyWrapperChains();
377
-
378
- // 最后执行路由插件(按优先级排序,确保调用的是已被包装后的方法)
379
- this.executePluginHook(
380
- "onHandlerLoad",
381
- (plugin) => {
382
- plugin.onHandlerLoad?.(handlers);
383
- },
384
- routePlugins
385
- );
386
- }
387
-
388
- /**
389
- * 执行插件启动钩子
390
- */
391
- private async executePluginStartHooks(): Promise<void> {
392
- // 执行插件 onBeforeStart
393
- this.executePluginHook("onBeforeStart", (plugin) => {
394
- plugin.onBeforeStart?.(this as Microservice<Record<string, any>>);
395
- });
396
-
397
- // 执行插件 onAfterStart(支持异步)
398
- for (const plugin of this.plugins) {
399
- try {
400
- await plugin.onAfterStart?.(this as Microservice<Record<string, any>>);
401
- } catch (error) {
402
- throw new Error(
403
- `Plugin ${plugin.name} failed in onAfterStart: ${
404
- error instanceof Error ? error.message : String(error)
405
- }`
406
- );
407
- }
408
- }
409
-
410
- // 在所有插件完成后,注册版本路由
411
- this.registerVersionRoute();
412
- }
413
-
414
- /**
415
- * 注册版本路由(/prefix/version)
416
- * 用于健康检查和探针
417
- */
418
- private registerVersionRoute(): void {
419
- const prefix = this.options.prefix || "";
420
- const versionPath = prefix ? `${prefix}` : "/";
421
-
422
- // 检查路由是否已存在
423
- // 使用 hono.routes 来检查已注册的路由
424
- const existingRoutes = this.hono.routes;
425
- const routeExists = existingRoutes.some(
426
- (route) => route.path === versionPath && route.method === "GET"
427
- );
428
-
429
- if (routeExists) {
430
- logger.info(
431
- `Version route ${versionPath} already exists, skipping registration`
432
- );
433
- return;
434
- }
435
-
436
- // 注册版本路由
437
- try {
438
- this.hono.get(versionPath, async (ctx) => {
439
- return ctx.json({
440
- name: this.options.name,
441
- version: this.options.version,
442
- status: "running",
443
- });
444
- });
445
-
446
- logger.info(`Registered version route: GET ${versionPath}`);
447
- } catch (error) {
448
- // 如果注册失败(可能是因为路由匹配器已经构建),记录日志
449
- if (
450
- error instanceof Error &&
451
- error.message.includes("matcher is already built")
452
- ) {
453
- logger.warn(
454
- `Cannot register version route ${versionPath}: route matcher is already built. ` +
455
- `If you have already registered a route at ${versionPath}, it will be used instead.`
456
- );
457
- } else {
458
- throw error;
459
- }
460
- }
461
- }
462
-
463
- /**
464
- * 启动 HTTP 服务器
465
- */
466
- private startHttpServer(port: number, hostname: string): void {
467
- // serve() 如果启动失败会抛出错误,成功则服务器立即可以接受连接
468
- this.server = serve({
469
- fetch: this.hono.fetch,
470
- port,
471
- hostname,
472
- });
473
-
474
- logger.info(`HTTP server started on http://${hostname}:${port}`);
475
- }
476
-
477
- /**
478
- * 按优先级排序插件
479
- */
480
- private sortPluginsByPriority(): Plugin[] {
481
- return [...this.plugins].sort((a, b) => {
482
- const priorityA = this.getPluginPriority(a);
483
- const priorityB = this.getPluginPriority(b);
484
-
485
- // 按优先级排序(数值越小,优先级越高)
486
- if (priorityA !== priorityB) {
487
- return priorityA - priorityB;
488
- }
489
-
490
- // 相同优先级按注册顺序(保持稳定排序)
491
- const indexA = this.plugins.indexOf(a);
492
- const indexB = this.plugins.indexOf(b);
493
- return indexA - indexB;
494
- });
495
- }
496
-
497
- /**
498
- * 获取插件优先级
499
- */
500
- private getPluginPriority(plugin: Plugin): number {
501
- return plugin.priority ?? PluginPriority.BUSINESS;
502
- }
503
-
504
- /**
505
- * 分离包装插件和路由插件
506
- */
507
- private separateWrapperAndRoutePlugins(sortedPlugins: Plugin[]): {
508
- wrapperPlugins: Plugin[];
509
- routePlugins: Plugin[];
510
- } {
511
- const wrapperPlugins: Plugin[] = [];
512
- const routePlugins: Plugin[] = [];
513
-
514
- for (const plugin of sortedPlugins) {
515
- const priority = this.getPluginPriority(plugin);
516
- if (priority === PluginPriority.ROUTE) {
517
- routePlugins.push(plugin);
518
- } else {
519
- wrapperPlugins.push(plugin);
520
- }
521
- }
522
-
523
- return { wrapperPlugins, routePlugins };
524
- }
525
-
526
- /**
527
- * 执行插件钩子(通用方法)
528
- */
529
- private executePluginHook(
530
- hookName: string,
531
- callback: (plugin: Plugin) => void,
532
- plugins: Plugin[] = this.plugins
533
- ): void {
534
- for (const plugin of plugins) {
535
- try {
536
- callback(plugin);
537
- } catch (error) {
538
- throw new Error(
539
- `Plugin ${plugin.name} failed in ${hookName}: ${
540
- error instanceof Error ? error.message : String(error)
541
- }`
542
- );
543
- }
544
- }
545
- }
546
-
547
- /**
548
- * 停止引擎
549
- */
550
- async stop(): Promise<void> {
551
- if (!this.started) {
552
- return;
553
- }
554
-
555
- try {
556
- // 关闭 HTTP 服务器
557
- if (this.server) {
558
- // serve() 返回的对象可能不是标准的 Node.js Server
559
- // 检查是否有 close 方法,如果有则调用
560
- if (typeof this.server.close === "function") {
561
- // 尝试使用回调方式关闭(标准 Node.js Server)
562
- if (this.server.close.length > 0) {
563
- await new Promise<void>((resolve, reject) => {
564
- this.server.close((err: any) => {
565
- if (err) {
566
- reject(err);
567
- } else {
568
- resolve();
569
- }
570
- });
571
- });
572
- } else {
573
- // 如果没有回调参数,直接调用
574
- this.server.close();
575
- // 给一点时间让服务器完全关闭
576
- await new Promise((resolve) => setTimeout(resolve, 10));
577
- }
578
- }
579
- this.server = null;
580
- logger.info("HTTP server stopped");
581
- }
582
-
583
- // 执行插件onDestroy(逆序)
584
- for (let i = this.plugins.length - 1; i >= 0; i--) {
585
- const plugin = this.plugins[i];
586
- try {
587
- await plugin.onDestroy?.();
588
- } catch (error) {
589
- logger.error(`Plugin ${plugin.name} failed in onDestroy`, error);
590
- }
591
- }
592
-
593
- // 清理资源
594
- this.modules = [];
595
- this.moduleInstances.clear();
596
- this.started = false;
597
- this.actualPort = null;
598
-
599
- logger.info(`${this.options.name} stopped`);
600
- } catch (error) {
601
- logger.error("Failed to stop engine", error);
602
- throw error;
603
- }
604
- }
605
-
606
- /**
607
- * 获取模块实例(单例)
608
- * @param moduleClass 模块类
609
- * @returns 模块实例
610
- */
611
- get<T extends Class>(moduleClass: T): InstanceType<T> {
612
- if (!this.moduleInstances.has(moduleClass)) {
613
- this.moduleInstances.set(moduleClass, new moduleClass());
614
- }
615
- return this.moduleInstances.get(moduleClass) as InstanceType<T>;
616
- }
617
-
618
- /**
619
- * 获取已注册的模块列表
620
- */
621
- getModules(): ModuleMetadata<Record<string, any>>[] {
622
- // 如果模块尚未加载,先加载模块
623
- if (this.modules.length === 0 && !this.started) {
624
- this.loadModules();
625
- }
626
- return [...this.modules] as ModuleMetadata<Record<string, any>>[];
627
- }
628
-
629
- /**
630
- * 获取实际使用的端口
631
- */
632
- getPort(): number | null {
633
- return this.actualPort;
634
- }
635
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * 核心异常类型定义
3
- */
4
-
5
- export class PluginNameRequiredError extends Error {
6
- constructor(pluginName?: string) {
7
- super(
8
- `Plugin name is required${pluginName ? ` (plugin: ${pluginName})` : ""}`
9
- );
10
- this.name = "PluginNameRequiredError";
11
- }
12
- }
13
-
14
- export class ModuleConfigValidationError extends Error {
15
- constructor(moduleName: string, pluginName: string, message: string) {
16
- super(
17
- `[ModuleConfigError] Module ${moduleName} (plugin ${pluginName}): ${message}`
18
- );
19
- this.name = "ModuleConfigValidationError";
20
- }
21
- }
22
-
23
- export class DuplicateModuleError extends Error {
24
- constructor(moduleName: string) {
25
- super(`Module ${moduleName} is already registered`);
26
- this.name = "DuplicateModuleError";
27
- }
28
- }