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,171 +0,0 @@
1
- /**
2
- * RateLimitPlugin 示例实现
3
- * 演示如何与 CachePlugin 配合工作
4
- */
5
-
6
- import {
7
- HandlerMetadata,
8
- Microservice,
9
- Plugin,
10
- PluginModuleOptionsSchema,
11
- } from "../../core/types";
12
-
13
- export interface RateLimitOptions {
14
- maxRequests?: number; // 最大请求数
15
- windowMs?: number; // 时间窗口(毫秒)
16
- keyGenerator?: (methodName: string, ...args: any[]) => string; // 限流键生成器
17
- }
18
-
19
- export interface RateLimitModuleOptions {
20
- rateLimitMaxRequests?: number; // 模块级默认最大请求数
21
- rateLimitWindowMs?: number; // 模块级默认时间窗口
22
- }
23
-
24
- /**
25
- * RateLimitPlugin - 限流插件
26
- *
27
- * 执行顺序说明:
28
- * - 如果 RateLimitPlugin 在 CachePlugin 之前注册,限流在外层,缓存在内层
29
- * 请求流程:限流检查 → 缓存检查 → 业务逻辑
30
- * - 如果 RateLimitPlugin 在 CachePlugin 之后注册,缓存在外层,限流在内层
31
- * 请求流程:缓存检查 → 限流检查 → 业务逻辑
32
- *
33
- * 推荐顺序:RateLimitPlugin → CachePlugin
34
- * 这样可以避免缓存命中时仍然进行限流检查
35
- */
36
- export class RateLimitPlugin implements Plugin<RateLimitModuleOptions> {
37
- public readonly name = "rate-limit-plugin";
38
- private requestCounts: Map<string, { count: number; resetAt: number }> =
39
- new Map();
40
- private defaultMaxRequests: number = 100;
41
- private defaultWindowMs: number = 60000; // 1分钟
42
- private engine: Microservice | null = null;
43
-
44
- getModuleOptionsSchema(): PluginModuleOptionsSchema<RateLimitModuleOptions> {
45
- return {
46
- _type: {} as RateLimitModuleOptions,
47
- validate: (options) => {
48
- if (
49
- options.rateLimitMaxRequests !== undefined &&
50
- options.rateLimitMaxRequests <= 0
51
- ) {
52
- return `rateLimitMaxRequests must be greater than 0`;
53
- }
54
- if (
55
- options.rateLimitWindowMs !== undefined &&
56
- options.rateLimitWindowMs <= 0
57
- ) {
58
- return `rateLimitWindowMs must be greater than 0`;
59
- }
60
- return true;
61
- },
62
- };
63
- }
64
-
65
- onInit(engine: Microservice): void {
66
- this.engine = engine;
67
- console.log("[RateLimitPlugin] Initialized rate limit storage");
68
- }
69
-
70
- onHandlerLoad(handlers: HandlerMetadata[]): void {
71
- // 筛选出所有 type="rate-limit" 的 Handler 元数据
72
- const rateLimitHandlers = handlers.filter(
73
- (handler) => handler.type === "rate-limit"
74
- );
75
-
76
- console.log(
77
- `[RateLimitPlugin] Found ${rateLimitHandlers.length} rate limit handlers`
78
- );
79
-
80
- // 遍历限流 Handler,包装方法
81
- // 注意:使用原型上的当前方法(可能已被其他插件包装),而不是 handler.method
82
- for (const handler of rateLimitHandlers) {
83
- const methodName = handler.methodName;
84
- const moduleClass = handler.module;
85
- const rateLimitOptions = handler.options as RateLimitOptions;
86
-
87
- // 获取模块元数据以读取配置
88
- const moduleMetadata = this.engine
89
- ?.getModules()
90
- .find((m) => m.clazz === moduleClass);
91
-
92
- if (!moduleMetadata) {
93
- console.warn(
94
- `[RateLimitPlugin] Module metadata not found for ${moduleClass.name}`
95
- );
96
- continue;
97
- }
98
-
99
- // 获取模块配置
100
- const moduleOptions = moduleMetadata.options as RateLimitModuleOptions;
101
- const maxRequests =
102
- rateLimitOptions.maxRequests ??
103
- moduleOptions?.rateLimitMaxRequests ??
104
- this.defaultMaxRequests;
105
- const windowMs =
106
- rateLimitOptions.windowMs ??
107
- moduleOptions?.rateLimitWindowMs ??
108
- this.defaultWindowMs;
109
-
110
- // 生成限流键的函数
111
- const keyGenerator =
112
- rateLimitOptions.keyGenerator ||
113
- ((methodName: string, ...args: any[]) =>
114
- `${methodName}:${JSON.stringify(args)}`);
115
-
116
- // 获取当前原型上的方法(可能已被其他插件包装)
117
- const prototype = moduleClass.prototype;
118
- const currentMethod = prototype[methodName];
119
-
120
- // 包装当前方法(支持插件链式包装)
121
- const rateLimitedMethod = async (...args: any[]) => {
122
- // 生成限流键
123
- const limitKey = keyGenerator(methodName, ...args);
124
- const now = Date.now();
125
-
126
- // 获取或初始化计数器
127
- let counter = this.requestCounts.get(limitKey);
128
- if (!counter || counter.resetAt <= now) {
129
- counter = { count: 0, resetAt: now + windowMs };
130
- this.requestCounts.set(limitKey, counter);
131
- }
132
-
133
- // 检查限流
134
- if (counter.count >= maxRequests) {
135
- console.log(
136
- `[RateLimitPlugin] Rate limit exceeded for ${limitKey} (${counter.count}/${maxRequests})`
137
- );
138
- throw new Error(
139
- `Rate limit exceeded: ${counter.count} requests in ${windowMs}ms`
140
- );
141
- }
142
-
143
- // 增加计数
144
- counter.count++;
145
- console.log(
146
- `[RateLimitPlugin] Request allowed for ${limitKey} (${counter.count}/${maxRequests})`
147
- );
148
-
149
- // 执行当前方法(可能是原始方法,也可能是被其他插件包装后的)
150
- const result = await currentMethod.apply(
151
- this.engine?.get(moduleClass),
152
- args
153
- );
154
-
155
- return result;
156
- };
157
-
158
- // 替换原型上的方法
159
- prototype[methodName] = rateLimitedMethod;
160
-
161
- console.log(
162
- `[RateLimitPlugin] Wrapped ${moduleClass.name}.${methodName} with rate limit (${maxRequests} requests per ${windowMs}ms)`
163
- );
164
- }
165
- }
166
-
167
- onDestroy(): void {
168
- this.requestCounts.clear();
169
- console.log("[RateLimitPlugin] Rate limit storage destroyed");
170
- }
171
- }
@@ -1,42 +0,0 @@
1
- import { html } from "hono/html";
2
-
3
- const DEFAULT_FAVICON = (
4
- <link
5
- rel="icon"
6
- href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='100' height='100'><defs><linearGradient id='nodeGradient' x1='0%' y1='0%' x2='100%' y2='100%'><stop offset='0%' stop-color='%233498db'/><stop offset='100%' stop-color='%232980b9'/></linearGradient><linearGradient id='centerNodeGradient' x1='0%' y1='0%' x2='100%' y2='100%'><stop offset='0%' stop-color='%232ecc71'/><stop offset='100%' stop-color='%2327ae60'/></linearGradient></defs><circle cx='50' cy='50' r='45' fill='%23f5f7fa'/><path d='M30,30 L50,50' stroke='%23bdc3c7' stroke-width='2' stroke-linecap='round'/><path d='M70,30 L50,50' stroke='%23bdc3c7' stroke-width='2' stroke-linecap='round'/><path d='M30,70 L50,50' stroke='%23bdc3c7' stroke-width='2' stroke-linecap='round'/><path d='M70,70 L50,50' stroke='%23bdc3c7' stroke-width='2' stroke-linecap='round'/><polygon points='30,15 45,25 45,45 30,55 15,45 15,25' fill='url(%23nodeGradient)' stroke='%232980b9' stroke-width='1.5'/><polygon points='70,15 85,25 85,45 70,55 55,45 55,25' fill='url(%23nodeGradient)' stroke='%232980b9' stroke-width='1.5'/><polygon points='30,45 45,55 45,75 30,85 15,75 15,55' fill='url(%23nodeGradient)' stroke='%232980b9' stroke-width='1.5'/><polygon points='70,45 85,55 85,75 70,85 55,75 55,55' fill='url(%23nodeGradient)' stroke='%232980b9' stroke-width='1.5'/><polygon points='50,30 65,40 65,60 50,70 35,60 35,40' fill='url(%23centerNodeGradient)' stroke='%2327ae60' stroke-width='2'/><circle cx='30' cy='30' r='3' fill='%23ffffff'/><circle cx='70' cy='30' r='3' fill='%23ffffff'/><circle cx='30' cy='70' r='3' fill='%23ffffff'/><circle cx='70' cy='70' r='3' fill='%23ffffff'/><circle cx='50' cy='50' r='4' fill='%23ffffff'/></svg>"
7
- type="image/svg+xml"
8
- ></link>
9
- );
10
-
11
- export const BaseLayout = (
12
- props: { children?: any; title?: string; heads?: any } = {
13
- title: "Microservice Template",
14
- }
15
- ) =>
16
- html`<!DOCTYPE html>
17
- <html>
18
- <head>
19
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
20
- <title>${props.title}</title>
21
- ${props.heads}
22
- </head>
23
- <body>
24
- ${props.children}
25
- </body>
26
- </html>`;
27
-
28
- export const HtmxLayout = (
29
- props: { children?: any; title: string; favicon?: any } = {
30
- title: "Microservice Template",
31
- }
32
- ) =>
33
- BaseLayout({
34
- title: props.title,
35
- heads: html`
36
- <script src="https://unpkg.com/htmx.org@latest"></script>
37
- <script src="https://unpkg.com/hyperscript.org@latest"></script>
38
- <script src="https://cdn.tailwindcss.com"></script>
39
- ${props.favicon || DEFAULT_FAVICON}
40
- `,
41
- children: props.children,
42
- });
@@ -1,141 +0,0 @@
1
- import { type ServiceInfo } from "../../../mod.ts";
2
-
3
- interface InfoCardProps {
4
- icon: string;
5
- iconColor: string;
6
- bgColor: string;
7
- label: string;
8
- value: string | any;
9
- }
10
-
11
- const InfoCard = ({
12
- icon,
13
- iconColor,
14
- bgColor,
15
- label,
16
- value,
17
- }: InfoCardProps) => (
18
- <div className={`${bgColor} p-4 rounded-lg`}>
19
- <div className="flex items-center mb-2">
20
- <svg
21
- className={`w-5 h-5 ${iconColor} mr-2`}
22
- fill="none"
23
- stroke="currentColor"
24
- viewBox="0 0 24 24"
25
- >
26
- <path
27
- strokeLinecap="round"
28
- strokeLinejoin="round"
29
- strokeWidth={2}
30
- d={icon}
31
- />
32
- </svg>
33
- <span className="text-sm font-medium text-gray-600">{label}</span>
34
- </div>
35
- <p className={`text-xl font-semibold text-gray-900`}>{value}</p>
36
- </div>
37
- );
38
-
39
- const getEnvironmentBadgeClass = (env: string): string => {
40
- switch (env) {
41
- case "prod":
42
- return "bg-red-100 text-red-800";
43
- case "stg":
44
- return "bg-yellow-100 text-yellow-800";
45
- case "dev":
46
- default:
47
- return "bg-blue-100 text-blue-800";
48
- }
49
- };
50
-
51
- export const ServiceInfoCards = ({
52
- serviceInfo,
53
- }: {
54
- serviceInfo: ServiceInfo;
55
- }) => {
56
- const infoCards = [
57
- {
58
- icon: "M7 4V2a1 1 0 011-1h8a1 1 0 011 1v2m-9 0h10m-10 0a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 002-2V6a2 2 0 00-2-2",
59
- iconColor: "text-blue-600",
60
- bgColor: "bg-blue-50",
61
- label: "服务名称",
62
- value: serviceInfo.name,
63
- },
64
- {
65
- icon: "M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1",
66
- iconColor: "text-orange-600",
67
- bgColor: "bg-orange-50",
68
- label: "服务路径",
69
- value: serviceInfo.prefix || "/",
70
- isMonospace: true,
71
- },
72
- {
73
- icon: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z",
74
- iconColor: "text-green-600",
75
- bgColor: "bg-green-50",
76
- label: "运行环境",
77
- value: (
78
- <span
79
- className={`px-2 py-1 rounded-full text-sm ${getEnvironmentBadgeClass(serviceInfo.env ?? "dev")}`}
80
- >
81
- {serviceInfo.env ?? "dev"}
82
- </span>
83
- ),
84
- },
85
- {
86
- icon: "M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z",
87
- iconColor: "text-purple-600",
88
- bgColor: "bg-purple-50",
89
- label: "版本号",
90
- value: serviceInfo.version || "unknown",
91
- },
92
- ];
93
-
94
- return (
95
- <div className="bg-white rounded-lg shadow-md p-6 mb-8">
96
- <h2 className="text-2xl font-semibold text-gray-800 mb-6 flex items-center">
97
- <svg
98
- className="w-6 h-6 mr-2 text-blue-600"
99
- fill="none"
100
- stroke="currentColor"
101
- viewBox="0 0 24 24"
102
- >
103
- <path
104
- strokeLinecap="round"
105
- strokeLinejoin="round"
106
- strokeWidth={2}
107
- d="M13 10V3L4 14h7v7l9-11h-7z"
108
- />
109
- </svg>
110
- 服务基本信息
111
- </h2>
112
-
113
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-2 gap-6">
114
- {infoCards.map((card, index) => (
115
- <InfoCard key={index} {...card} />
116
- ))}
117
- </div>
118
- </div>
119
- );
120
- };
121
-
122
- export const ServiceStatusPage = ({
123
- serviceInfo,
124
- }: {
125
- serviceInfo: ServiceInfo;
126
- }) => {
127
- return (
128
- <div className="min-h-screen bg-gray-50 py-8">
129
- <div className="max-w-6xl mx-auto px-4">
130
- <div className="mb-8">
131
- <h1 className="text-4xl font-bold text-gray-900 mb-2">
132
- Service Status
133
- </h1>
134
- </div>
135
- <ServiceInfoCards serviceInfo={serviceInfo} />
136
- </div>
137
- </div>
138
- );
139
- };
140
-
141
- export default ServiceStatusPage;
@@ -1,50 +0,0 @@
1
- import { Handler } from "../../core/decorators";
2
- import { RouteOptions } from "./types";
3
-
4
- /**
5
- * Route装饰器(Handler的语法糖封装)
6
- * 固定type="route",简化HTTP路由标注
7
- * 使用最新的 Stage 3 装饰器标准
8
- *
9
- * @example
10
- * ```typescript
11
- * @Route({ method: "GET", path: "/users" })
12
- * async getUsers(ctx: Context) {
13
- * return ctx.json({ users: [] });
14
- * }
15
- *
16
- * // 支持多个路径
17
- * @Route({ path: ["/", "/home", "/dashboard"] })
18
- * async homePage(ctx: Context) {
19
- * return <HomePage />;
20
- * }
21
- * ```
22
- */
23
- export function Route(options: RouteOptions) {
24
- return Handler({
25
- type: "route",
26
- options,
27
- });
28
- }
29
-
30
- /**
31
- * Page装饰器(Route的别名,用于向后兼容)
32
- * 专门用于页面路由,默认使用 GET 方法
33
- *
34
- * @example
35
- * ```typescript
36
- * @Page({ path: ["/", "/home"] })
37
- * async adminPage(ctx: Context) {
38
- * return HtmxLayout({ title: "Admin", children: <AdminLayout /> });
39
- * }
40
- * ```
41
- */
42
- export function Page(options: RouteOptions) {
43
- // 如果没有指定 method,默认为 GET
44
- const pageOptions: RouteOptions = {
45
- method: options.method || "GET",
46
- ...options,
47
- };
48
- return Route(pageOptions);
49
- }
50
-
@@ -1,16 +0,0 @@
1
- /**
2
- * Route Plugin - 路由插件
3
- *
4
- * 提供HTTP路由功能,支持模块级前缀和中间件
5
- */
6
-
7
- export { RoutePlugin } from "./plugin";
8
- export { Route, Page } from "./decorator";
9
- export type {
10
- RouteModuleOptions,
11
- RouteOptions,
12
- RoutePluginOptions,
13
- HTTPMethod,
14
- } from "./types";
15
- export { BaseLayout, HtmxLayout } from "./components/Layout";
16
-
@@ -1,218 +0,0 @@
1
- import { Context, Hono, MiddlewareHandler } from "hono";
2
- import {
3
- HandlerMetadata,
4
- Microservice,
5
- Plugin,
6
- PluginModuleOptionsSchema,
7
- PluginPriority,
8
- } from "../../core/types";
9
- import logger from "../../core/logger";
10
- import { RouteModuleOptions, RouteOptions, RoutePluginOptions } from "./types";
11
-
12
- /**
13
- * RoutePlugin - 核心路由插件
14
- * 负责解析type="route"的Handler元数据,注册HTTP路由到Hono实例
15
- *
16
- * @example
17
- * ```typescript
18
- * // 使用全局中间件(如鉴权)
19
- * const authMiddleware = async (ctx, next) => {
20
- * // 验证 token
21
- * await next();
22
- * };
23
- *
24
- * const routePlugin = new RoutePlugin({
25
- * globalMiddlewares: [authMiddleware],
26
- * });
27
- * ```
28
- */
29
- export class RoutePlugin implements Plugin<RouteModuleOptions> {
30
- public readonly name = "route-plugin";
31
- public readonly priority = PluginPriority.ROUTE;
32
- private engine!: Microservice;
33
- private globalMiddlewares: MiddlewareHandler[];
34
-
35
- /**
36
- * 构造函数
37
- * @param options 插件配置选项
38
- */
39
- constructor(options?: RoutePluginOptions) {
40
- this.globalMiddlewares = options?.globalMiddlewares || [];
41
- }
42
-
43
- /**
44
- * 声明Module配置Schema(用于类型推导+运行时校验)
45
- */
46
- getModuleOptionsSchema(): PluginModuleOptionsSchema<RouteModuleOptions> {
47
- return {
48
- _type: {} as RouteModuleOptions,
49
- validate: (options) => {
50
- if (
51
- options.routePrefix !== undefined &&
52
- typeof options.routePrefix !== "string"
53
- ) {
54
- return "routePrefix must be a string";
55
- }
56
- if (options.routePrefix && !options.routePrefix.startsWith("/")) {
57
- return `routePrefix must start with '/'`;
58
- }
59
- if (
60
- options.routeMiddlewares !== undefined &&
61
- !Array.isArray(options.routeMiddlewares)
62
- ) {
63
- return "routeMiddlewares must be an array";
64
- }
65
- return true;
66
- },
67
- };
68
- }
69
-
70
- /**
71
- * 引擎初始化钩子:获取Hono实例
72
- */
73
- onInit(engine: Microservice): void {
74
- this.engine = engine;
75
- logger.info("RoutePlugin initialized");
76
- }
77
-
78
- /**
79
- * Handler加载钩子:解析type="route"的Handler元数据,注册HTTP路由
80
- */
81
- onHandlerLoad(handlers: HandlerMetadata[]): void {
82
- // 筛选出所有type="route"的Handler元数据
83
- const routeHandlers = handlers.filter(
84
- (handler) => handler.type === "route"
85
- );
86
-
87
- logger.info(`Found ${routeHandlers.length} route handler(s)`);
88
-
89
- for (const handler of routeHandlers) {
90
- const routeOptions = handler.options as RouteOptions;
91
- const methodName = handler.methodName;
92
- const moduleClass = handler.module;
93
-
94
- // 获取模块实例
95
- const moduleInstance = this.engine.get(moduleClass);
96
- if (!moduleInstance) {
97
- logger.warn(
98
- `Module instance not found for ${moduleClass.name}, skipping route registration`
99
- );
100
- continue;
101
- }
102
-
103
- // 获取模块配置
104
- const moduleMetadata = this.engine
105
- .getModules()
106
- .find((m: any) => m.clazz === moduleClass);
107
- const moduleOptions = (moduleMetadata?.options ||
108
- {}) as RouteModuleOptions;
109
-
110
- // 构建路由路径数组(支持单个路径或多个路径)
111
- const routePrefix = moduleOptions.routePrefix || "";
112
- const paths = Array.isArray(routeOptions.path)
113
- ? routeOptions.path
114
- : [routeOptions.path];
115
- const fullPaths = paths.map((p) => routePrefix + p);
116
-
117
- // 构建路由处理器
118
- const routeHandler = async (ctx: Context) => {
119
- // 引擎保证此时原型上的方法已经被所有包装插件包装
120
- // 直接调用方法名即可获取包装后的方法
121
- const method = (moduleInstance as any)[methodName];
122
- if (typeof method !== "function") {
123
- return ctx.json({ error: "Handler method not found" }, 500);
124
- }
125
-
126
- try {
127
- const result = await method.call(moduleInstance, ctx);
128
- // Hono 会自动处理 JSX/HTML/Response 响应
129
- // 如果返回的是普通对象,自动转换为 JSON
130
- if (result instanceof Response) {
131
- return result;
132
- }
133
- // 如果返回的是 JSX 或其他 Hono 支持的类型,直接返回
134
- if (
135
- typeof result === "string" ||
136
- (typeof result === "object" && result !== null && "type" in result)
137
- ) {
138
- return result;
139
- }
140
- // 否则作为 JSON 返回
141
- return ctx.json(result);
142
- } catch (error) {
143
- logger.error(
144
- `Error in route handler ${moduleClass.name}.${methodName}`,
145
- error
146
- );
147
- return ctx.json(
148
- {
149
- error: "Internal server error",
150
- message: error instanceof Error ? error.message : String(error),
151
- },
152
- 500
153
- );
154
- }
155
- };
156
-
157
- // 获取 Hono 实例
158
- const hono = this.engine.getHono();
159
-
160
- // 注册路由(支持单个方法或多个方法,默认为 GET)
161
- const methods = routeOptions.method
162
- ? Array.isArray(routeOptions.method)
163
- ? routeOptions.method
164
- : [routeOptions.method]
165
- : ["GET"];
166
-
167
- // 为每个路径注册路由
168
- for (const fullPath of fullPaths) {
169
- let route = hono;
170
-
171
- // 应用全局中间件(最先执行)
172
- if (this.globalMiddlewares.length > 0) {
173
- route = route.use(fullPath, ...this.globalMiddlewares);
174
- }
175
-
176
- // 应用模块级中间件
177
- if (
178
- moduleOptions.routeMiddlewares &&
179
- moduleOptions.routeMiddlewares.length > 0
180
- ) {
181
- route = route.use(fullPath, ...moduleOptions.routeMiddlewares);
182
- }
183
-
184
- // 应用路由级中间件(最后执行)
185
- if (routeOptions.middlewares && routeOptions.middlewares.length > 0) {
186
- route = route.use(fullPath, ...routeOptions.middlewares);
187
- }
188
-
189
- // 为每个 HTTP 方法注册路由
190
- for (const method of methods) {
191
- const methodLower = method.toLowerCase();
192
- if (
193
- methodLower === "get" ||
194
- methodLower === "post" ||
195
- methodLower === "put" ||
196
- methodLower === "delete" ||
197
- methodLower === "patch" ||
198
- methodLower === "head" ||
199
- methodLower === "options"
200
- ) {
201
- (route as any)[methodLower](fullPath, routeHandler);
202
- const description = routeOptions.description
203
- ? ` (${routeOptions.description})`
204
- : "";
205
- logger.info(
206
- `Registered route: ${method.toUpperCase()} ${fullPath} -> ${moduleClass.name}.${methodName}${description}`
207
- );
208
- } else {
209
- logger.warn(
210
- `Unsupported HTTP method: ${method}, skipping route ${fullPath}`
211
- );
212
- }
213
- }
214
- }
215
- }
216
- }
217
-
218
- }