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,72 +0,0 @@
1
- import { MiddlewareHandler } from "hono";
2
- import { ContentfulStatusCode } from "hono/utils/http-status";
3
-
4
- // HTTP方法类型
5
- export type HTTPMethod =
6
- | "GET"
7
- | "POST"
8
- | "PUT"
9
- | "DELETE"
10
- | "PATCH"
11
- | "HEAD"
12
- | "OPTIONS";
13
- /**
14
- * Route装饰器配置选项
15
- */
16
- export interface RouteOptions {
17
- /**
18
- * HTTP方法(可选,默认为 GET)
19
- * 如果未指定,则默认为 GET 方法
20
- */
21
- method?: HTTPMethod | HTTPMethod[]; // HTTP方法
22
- /**
23
- * 路由路径(支持单个路径或多个路径)
24
- * 如果提供数组,将为每个路径注册相同的处理器
25
- */
26
- path: string | string[]; // 路由路径
27
- /**
28
- * 路由专属中间件
29
- */
30
- middlewares?: MiddlewareHandler[]; // 路由专属中间件
31
- /**
32
- * 路由描述(用于文档和日志)
33
- */
34
- description?: string;
35
- /**
36
- * 请求参数校验规则
37
- */
38
- validate?: {
39
- query?: Record<string, any>;
40
- body?: Record<string, any>;
41
- params?: Record<string, any>;
42
- };
43
- /**
44
- * 响应配置
45
- */
46
- response?: {
47
- status?: ContentfulStatusCode;
48
- type?: "json" | "text" | "html";
49
- };
50
- }
51
-
52
- /**
53
- * RoutePlugin的Module配置
54
- * 注意:配置直接平铺在 Module options 中,不使用 route 命名空间
55
- * 插件作者需要确保字段名不与其他插件冲突
56
- */
57
- export interface RouteModuleOptions {
58
- routePrefix?: string; // 模块级路由前缀(使用 routePrefix 避免与其他插件的 prefix 冲突)
59
- routeMiddlewares?: MiddlewareHandler[]; // 模块级路由中间件(使用 routeMiddlewares 避免冲突)
60
- }
61
-
62
- /**
63
- * RoutePlugin 配置选项
64
- */
65
- export interface RoutePluginOptions {
66
- /**
67
- * 全局中间件(应用于所有路由)
68
- * 执行顺序:全局中间件 -> 模块级中间件 -> 路由级中间件
69
- * 常用于鉴权、日志等全局功能
70
- */
71
- globalMiddlewares?: MiddlewareHandler[];
72
- }
@@ -1,309 +0,0 @@
1
- # SchedulePlugin - 调度任务插件
2
-
3
- ## 概述
4
-
5
- `SchedulePlugin` 是一个基于 etcd 选举机制的分布式定时任务插件。它确保在多个微服务实例中,只有一个实例会执行定时任务,避免重复执行。
6
-
7
- ## 功能特性
8
-
9
- - ✅ 基于 etcd 的分布式选举机制
10
- - ✅ 支持固定频率(FIXED_RATE)和固定延迟(FIXED_DELAY)两种调度模式
11
- - ✅ 自动故障转移:当 leader 实例宕机时,其他实例会自动接管
12
- - ✅ 集成 OpenTelemetry 追踪
13
- - ✅ 优雅关闭:停止时自动清理所有定时器和选举
14
-
15
- ## 安装依赖
16
-
17
- ```bash
18
- npm install etcd3
19
- ```
20
-
21
- ## 使用方法
22
-
23
- ### 方式一:使用真实的 etcd(生产环境)
24
-
25
- #### 1. 创建 etcd 客户端
26
-
27
- ```typescript
28
- import { Etcd3 } from "etcd3";
29
-
30
- const etcdClient = new Etcd3({
31
- hosts: ["localhost:2379"],
32
- // 可选:认证信息
33
- auth: {
34
- username: "root",
35
- password: "password",
36
- },
37
- });
38
- ```
39
-
40
- #### 2. 创建引擎并注册插件
41
-
42
- ```typescript
43
- import { Factory } from "@imean/service-engine";
44
- import { SchedulePlugin } from "@imean/service-engine/plugins/schedule";
45
- import { Etcd3 } from "etcd3";
46
-
47
- const etcdClient = new Etcd3({
48
- hosts: ["localhost:2379"],
49
- });
50
-
51
- const { Module, Microservice } = Factory.create(
52
- new SchedulePlugin({
53
- etcdClient,
54
- })
55
- );
56
- ```
57
-
58
- ### 方式二:使用 Mock Etcd(测试和本地开发)
59
-
60
- 如果不想依赖真实的 etcd 服务,可以使用内置的 Mock Etcd。Mock Etcd 会自动选举自己作为 leader,适合单实例开发和测试场景。
61
-
62
- ```typescript
63
- import { Factory } from "@imean/service-engine";
64
- import { SchedulePlugin } from "@imean/service-engine/plugins/schedule";
65
-
66
- const { Module, Microservice } = Factory.create(
67
- new SchedulePlugin({
68
- useMockEtcd: true, // 启用 Mock Etcd,无需真实 etcd 服务
69
- })
70
- );
71
- ```
72
-
73
- **注意**:
74
- - `useMockEtcd: true` 时,插件会自动使用内置的 `MockEtcd3`
75
- - Mock Etcd 始终选举自己作为 leader,适合单实例场景
76
- - 如果同时提供了 `etcdClient` 和 `useMockEtcd: true`,会优先使用提供的 `etcdClient`
77
-
78
- ### 3. 使用 @Schedule 装饰器
79
-
80
- ```typescript
81
- import { Module } from "./engine";
82
- import { Schedule, ScheduleMode } from "@imean/service-engine/plugins/schedule";
83
-
84
- @Module("user-service")
85
- class UserService {
86
- /**
87
- * 固定频率模式:每 5 秒执行一次
88
- * 无论任务执行时间多长,都会按照固定间隔执行
89
- */
90
- @Schedule({
91
- interval: 5000, // 5 秒
92
- mode: ScheduleMode.FIXED_RATE,
93
- })
94
- async syncUsers() {
95
- console.log("同步用户数据...");
96
- // 执行同步逻辑
97
- }
98
-
99
- /**
100
- * 固定延迟模式:任务执行完成后,等待 10 秒再执行下一次
101
- * 如果任务执行了 15 秒,则下次执行在 25 秒后
102
- */
103
- @Schedule({
104
- interval: 10000, // 10 秒
105
- mode: ScheduleMode.FIXED_DELAY,
106
- })
107
- async cleanupExpiredData() {
108
- console.log("清理过期数据...");
109
- // 执行清理逻辑
110
- await new Promise((resolve) => setTimeout(resolve, 5000)); // 模拟耗时操作
111
- }
112
- }
113
- ```
114
-
115
- ### 4. 启动引擎
116
-
117
- ```typescript
118
- const engine = new Microservice({
119
- name: "my-service",
120
- version: "1.0.0",
121
- });
122
-
123
- await engine.start(3000);
124
- ```
125
-
126
- ## 调度模式说明
127
-
128
- ### FIXED_RATE(固定频率)
129
-
130
- - **特点**:无论任务执行时间多长,都会按照固定间隔执行
131
- - **适用场景**:需要定期执行的任务,执行时间较短且稳定
132
- - **示例**:每 5 秒检查一次系统状态
133
-
134
- ```typescript
135
- @Schedule({
136
- interval: 5000,
137
- mode: ScheduleMode.FIXED_RATE,
138
- })
139
- async checkSystemStatus() {
140
- // 快速检查系统状态
141
- }
142
- ```
143
-
144
- ### FIXED_DELAY(固定延迟)
145
-
146
- - **特点**:任务执行完成后,等待固定间隔再执行下一次
147
- - **适用场景**:任务执行时间不确定,需要等待上次任务完成后再执行
148
- - **示例**:数据同步任务,需要等待上次同步完成
149
-
150
- ```typescript
151
- @Schedule({
152
- interval: 60000, // 1 分钟
153
- mode: ScheduleMode.FIXED_DELAY,
154
- })
155
- async syncData() {
156
- // 可能执行 30 秒或更长时间
157
- await longRunningTask();
158
- }
159
- ```
160
-
161
- ## 选举机制
162
-
163
- 插件使用 etcd 的选举机制来确保只有一个实例执行任务:
164
-
165
- 1. **选举键格式**:`/schedule/{serviceName}/{moduleName}/{methodName}`
166
- 2. **服务 ID**:每个实例使用唯一的服务 ID 参与选举
167
- 3. **自动故障转移**:当 leader 实例宕机时,etcd 会自动选择新的 leader
168
-
169
- ## 注意事项
170
-
171
- 1. **etcd 配置**:必须正确配置 etcd 客户端,否则调度任务不会启动
172
- 2. **网络连接**:确保所有实例都能连接到 etcd 集群
173
- 3. **任务幂等性**:建议任务设计为幂等的,避免重复执行导致的问题
174
- 4. **资源清理**:引擎停止时会自动清理所有定时器和选举,无需手动处理
175
-
176
- ## 错误处理
177
-
178
- 插件会自动记录错误日志,但不会中断引擎的运行:
179
-
180
- - 如果 etcd 连接失败,会记录警告日志,但不会启动调度任务
181
- - 如果任务执行失败,会记录错误日志,但不会影响其他任务
182
- - 如果选举失败,会记录错误日志,但不会影响其他功能
183
-
184
- ## 示例:完整的使用场景
185
-
186
- ```typescript
187
- import { Factory } from "@imean/service-engine";
188
- import { SchedulePlugin, Schedule, ScheduleMode } from "@imean/service-engine/plugins/schedule";
189
- import { Etcd3 } from "etcd3";
190
-
191
- // 1. 创建 etcd 客户端
192
- const etcdClient = new Etcd3({
193
- hosts: ["etcd1:2379", "etcd2:2379", "etcd3:2379"],
194
- });
195
-
196
- // 2. 创建引擎
197
- const { Module, Microservice } = Factory.create(
198
- new SchedulePlugin({
199
- etcdClient,
200
- })
201
- );
202
-
203
- // 3. 定义服务模块
204
- @Module("data-service")
205
- class DataService {
206
- @Schedule({
207
- interval: 30000, // 30 秒
208
- mode: ScheduleMode.FIXED_RATE,
209
- })
210
- async syncData() {
211
- console.log("同步数据...");
212
- // 同步逻辑
213
- }
214
-
215
- @Schedule({
216
- interval: 600000, // 10 分钟
217
- mode: ScheduleMode.FIXED_DELAY,
218
- })
219
- async cleanupOldData() {
220
- console.log("清理旧数据...");
221
- // 清理逻辑
222
- }
223
- }
224
-
225
- // 4. 启动引擎
226
- const engine = new Microservice({
227
- name: "data-service",
228
- version: "1.0.0",
229
- });
230
-
231
- await engine.start(3000);
232
- console.log("服务已启动,调度任务已注册");
233
- ```
234
-
235
- ### 本地开发/测试(使用 Mock Etcd)
236
-
237
- ```typescript
238
- import { Factory } from "@imean/service-engine";
239
- import { SchedulePlugin, Schedule, ScheduleMode } from "@imean/service-engine/plugins/schedule";
240
-
241
- // 1. 创建引擎(使用 Mock Etcd,无需真实 etcd 服务)
242
- const { Module, Microservice } = Factory.create(
243
- new SchedulePlugin({
244
- useMockEtcd: true, // 启用 Mock Etcd
245
- })
246
- );
247
-
248
- // 2. 定义服务模块
249
- @Module("data-service")
250
- class DataService {
251
- @Schedule({
252
- interval: 30000, // 30 秒
253
- mode: ScheduleMode.FIXED_RATE,
254
- })
255
- async syncData() {
256
- console.log("同步数据...");
257
- // 同步逻辑
258
- }
259
- }
260
-
261
- // 3. 启动引擎
262
- const engine = new Microservice({
263
- name: "data-service",
264
- version: "1.0.0",
265
- });
266
-
267
- await engine.start(3000);
268
- console.log("服务已启动,调度任务已注册(使用 Mock Etcd)");
269
- ```
270
-
271
- ## API 参考
272
-
273
- ### ScheduleOptions
274
-
275
- ```typescript
276
- interface ScheduleOptions {
277
- /**
278
- * 执行间隔(毫秒)
279
- */
280
- interval: number;
281
-
282
- /**
283
- * 调度模式(默认 FIXED_RATE)
284
- */
285
- mode?: ScheduleMode;
286
- }
287
- ```
288
-
289
- ### SchedulePluginOptions
290
-
291
- ```typescript
292
- interface SchedulePluginOptions {
293
- /**
294
- * Etcd3 客户端实例
295
- * 如果未提供且 useMockEtcd 为 false,插件将不会启动调度任务
296
- */
297
- etcdClient?: Etcd3;
298
-
299
- /**
300
- * 是否使用 Mock Etcd(用于测试和本地开发)
301
- * 当设置为 true 时,将使用内置的 MockEtcd3,始终选举自己作为 leader
302
- * 这样可以在没有真实 etcd 的情况下运行调度任务
303
- *
304
- * @default false
305
- */
306
- useMockEtcd?: boolean;
307
- }
308
- ```
309
-
@@ -1,25 +0,0 @@
1
- import { Handler } from "../../core/decorators";
2
- import { ScheduleOptions, ScheduleMode } from "./types";
3
-
4
- /**
5
- * Schedule装饰器
6
- * 用于标记需要定时调度的方法
7
- *
8
- * @example
9
- * ```typescript
10
- * @Schedule({ interval: 5000, mode: ScheduleMode.FIXED_RATE })
11
- * async syncData() {
12
- * // 定时执行的任务
13
- * }
14
- * ```
15
- */
16
- export function Schedule(options: ScheduleOptions) {
17
- return Handler({
18
- type: "schedule",
19
- options: {
20
- interval: options.interval,
21
- mode: options.mode || ScheduleMode.FIXED_RATE,
22
- },
23
- });
24
- }
25
-
@@ -1,12 +0,0 @@
1
- export { Schedule } from "./decorator";
2
- export { SchedulePlugin } from "./plugin";
3
- export { Scheduler } from "./scheduler";
4
- export { MockEtcd3 } from "./mock-etcd";
5
- export {
6
- ScheduleMode,
7
- ScheduleOptions,
8
- ScheduleMetadata,
9
- SchedulePluginOptions,
10
- } from "./types";
11
- export type { Etcd3, Election, Observer, Campaign } from "./types";
12
-
@@ -1,145 +0,0 @@
1
- import { Etcd3, Election, Observer, Campaign } from "./types";
2
-
3
- /**
4
- * Mock Etcd3 客户端
5
- * 用于测试和本地开发,模拟 etcd 的选举机制
6
- * 始终选举第一个参与选举的候选者作为 leader
7
- */
8
- export class MockEtcd3 implements Etcd3 {
9
- private elections: Map<string, MockElection> = new Map();
10
-
11
- election(key: string, ttl: number): Election {
12
- if (!this.elections.has(key)) {
13
- this.elections.set(key, new MockElection(key));
14
- }
15
- return this.elections.get(key)!;
16
- }
17
-
18
- getElection(key: string): MockElection | undefined {
19
- return this.elections.get(key);
20
- }
21
-
22
- clearElections(): void {
23
- this.elections.clear();
24
- }
25
- }
26
-
27
- /**
28
- * Mock Election
29
- */
30
- class MockElection implements Election {
31
- private observers: MockObserver[] = [];
32
- private campaigns: MockCampaign[] = [];
33
- private currentLeader: string | null = null;
34
-
35
- constructor(private key: string) {}
36
-
37
- async observe(): Promise<Observer> {
38
- const observer = new MockObserver(this);
39
- this.observers.push(observer);
40
- // 如果已经有 leader,立即通知
41
- if (this.currentLeader) {
42
- setTimeout(() => {
43
- observer.notifyChange(this.currentLeader!);
44
- }, 0);
45
- }
46
- return observer;
47
- }
48
-
49
- campaign(candidate: string): Campaign {
50
- const campaign = new MockCampaign(candidate, this);
51
- this.campaigns.push(campaign);
52
-
53
- // 如果没有当前 leader,异步选举(模拟真实 etcd 行为)
54
- // 使用 setTimeout 确保回调有时间注册
55
- if (!this.currentLeader) {
56
- setTimeout(() => {
57
- this.setLeader(candidate);
58
- }, 50);
59
- }
60
-
61
- return campaign;
62
- }
63
-
64
- setLeader(leader: string): void {
65
- this.currentLeader = leader;
66
- // 通知所有 observers
67
- for (const observer of this.observers) {
68
- observer.notifyChange(leader);
69
- }
70
- // 通知被选中的 campaign(异步执行,确保回调已注册)
71
- setTimeout(() => {
72
- for (const campaign of this.campaigns) {
73
- if (campaign.candidate === leader && !campaign.resigned) {
74
- campaign.notifyElected();
75
- }
76
- }
77
- }, 50);
78
- }
79
-
80
- getCurrentLeader(): string | null {
81
- return this.currentLeader;
82
- }
83
- }
84
-
85
- /**
86
- * Mock Observer
87
- */
88
- class MockObserver implements Observer {
89
- private changeCallbacks: ((leader: string) => void)[] = [];
90
-
91
- constructor(private election: MockElection) {}
92
-
93
- on(event: "change", callback: (leader: string) => void): void {
94
- if (event === "change") {
95
- this.changeCallbacks.push(callback);
96
- }
97
- }
98
-
99
- notifyChange(leader: string): void {
100
- for (const callback of this.changeCallbacks) {
101
- callback(leader);
102
- }
103
- }
104
- }
105
-
106
- /**
107
- * Mock Campaign
108
- */
109
- class MockCampaign implements Campaign {
110
- private errorCallbacks: ((error: any) => void)[] = [];
111
- private electedCallbacks: (() => void)[] = [];
112
- public resigned: boolean = false;
113
-
114
- constructor(
115
- public readonly candidate: string,
116
- private election: MockElection
117
- ) {}
118
-
119
- on(event: "error" | "elected", callback: (error?: any) => void): void {
120
- if (event === "error") {
121
- this.errorCallbacks.push(callback);
122
- } else if (event === "elected") {
123
- this.electedCallbacks.push(callback);
124
- }
125
- }
126
-
127
- notifyElected(): void {
128
- if (!this.resigned) {
129
- for (const callback of this.electedCallbacks) {
130
- callback();
131
- }
132
- }
133
- }
134
-
135
- notifyError(error: any): void {
136
- for (const callback of this.errorCallbacks) {
137
- callback(error);
138
- }
139
- }
140
-
141
- async resign(): Promise<void> {
142
- this.resigned = true;
143
- }
144
- }
145
-