@zhin.js/core 1.0.38 → 1.0.40
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/CHANGELOG.md +20 -0
- package/lib/ai/index.d.ts +10 -5
- package/lib/ai/index.d.ts.map +1 -1
- package/lib/ai/index.js +7 -4
- package/lib/ai/index.js.map +1 -1
- package/lib/ai/providers/anthropic.d.ts.map +1 -1
- package/lib/ai/providers/anthropic.js +2 -0
- package/lib/ai/providers/anthropic.js.map +1 -1
- package/lib/ai/providers/openai.d.ts.map +1 -1
- package/lib/ai/providers/openai.js +8 -0
- package/lib/ai/providers/openai.js.map +1 -1
- package/lib/ai/types.d.ts +1 -0
- package/lib/ai/types.d.ts.map +1 -1
- package/lib/cron.d.ts +2 -43
- package/lib/cron.d.ts.map +1 -1
- package/lib/cron.js +2 -126
- package/lib/cron.js.map +1 -1
- package/lib/errors.d.ts +3 -146
- package/lib/errors.d.ts.map +1 -1
- package/lib/errors.js +3 -279
- package/lib/errors.js.map +1 -1
- package/lib/feature.d.ts +5 -87
- package/lib/feature.d.ts.map +1 -1
- package/lib/feature.js +4 -105
- package/lib/feature.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/scheduler/index.d.ts +3 -7
- package/lib/scheduler/index.d.ts.map +1 -1
- package/lib/scheduler/index.js +2 -9
- package/lib/scheduler/index.js.map +1 -1
- package/lib/types.d.ts +8 -1
- package/lib/types.d.ts.map +1 -1
- package/lib/utils.d.ts +7 -52
- package/lib/utils.d.ts.map +1 -1
- package/lib/utils.js +9 -325
- package/lib/utils.js.map +1 -1
- package/package.json +6 -4
- package/src/ai/index.ts +15 -9
- package/src/ai/providers/anthropic.ts +1 -0
- package/src/ai/providers/openai.ts +5 -1
- package/src/ai/types.ts +1 -0
- package/src/cron.ts +2 -140
- package/src/errors.ts +15 -334
- package/src/feature.ts +5 -154
- package/src/index.ts +3 -1
- package/src/scheduler/index.ts +8 -17
- package/src/types.ts +10 -2
- package/src/utils.ts +37 -334
- package/tests/cron.test.ts +4 -299
- package/tests/errors.test.ts +17 -307
- package/tests/utils.test.ts +11 -516
- package/tests/feature.test.ts +0 -145
package/src/ai/types.ts
CHANGED
package/src/cron.ts
CHANGED
|
@@ -1,142 +1,4 @@
|
|
|
1
|
-
import { Cron as Croner } from 'croner';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
5
|
-
* 基于 croner 实现的定时任务调度器 (无需 Luxon,内存占用更小)
|
|
2
|
+
* Re-export from @zhin.js/kernel for backward compatibility.
|
|
6
3
|
*/
|
|
7
|
-
export
|
|
8
|
-
private job: Croner | null = null;
|
|
9
|
-
id:string = '';
|
|
10
|
-
private callback: () => void | Promise<void>;
|
|
11
|
-
private isDisposed = false;
|
|
12
|
-
private _cronExpression: string;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* 创建一个新的 Cron 实例
|
|
16
|
-
* @param cronExpression - Cron 表达式 (例如: '0 0 * * *' 表示每天午夜执行)
|
|
17
|
-
* @param callback - 要执行的回调函数
|
|
18
|
-
*/
|
|
19
|
-
constructor(cronExpression: string, callback: () => void | Promise<void>) {
|
|
20
|
-
this.id=Math.random().toString(36).substring(2, 10);
|
|
21
|
-
try {
|
|
22
|
-
this._cronExpression = cronExpression;
|
|
23
|
-
this.callback = callback;
|
|
24
|
-
|
|
25
|
-
// 验证 cron 表达式是否有效 (不启动)
|
|
26
|
-
const testJob = new Croner(cronExpression, { paused: true });
|
|
27
|
-
testJob.stop();
|
|
28
|
-
} catch (error) {
|
|
29
|
-
throw new Error(`Invalid cron expression "${cronExpression}": ${(error as Error).message}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 启动定时任务
|
|
35
|
-
*/
|
|
36
|
-
run(): void {
|
|
37
|
-
if (this.isDisposed) {
|
|
38
|
-
throw new Error('Cannot run a disposed cron job');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (this.job) {
|
|
42
|
-
return; // 已经在运行中
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// 创建并启动任务
|
|
46
|
-
this.job = new Croner(this._cronExpression, async () => {
|
|
47
|
-
try {
|
|
48
|
-
await this.callback();
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error(`Error executing cron callback: ${(error as Error).message}`);
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* 停止定时任务
|
|
57
|
-
*/
|
|
58
|
-
stop(): void {
|
|
59
|
-
if (this.job) {
|
|
60
|
-
this.job.stop();
|
|
61
|
-
this.job = null;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 销毁定时任务,释放资源
|
|
67
|
-
*/
|
|
68
|
-
dispose(): void {
|
|
69
|
-
this.stop();
|
|
70
|
-
this.isDisposed = true;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* 获取下一次执行时间
|
|
75
|
-
*/
|
|
76
|
-
getNextExecutionTime(): Date {
|
|
77
|
-
if (this.isDisposed) {
|
|
78
|
-
throw new Error('Cannot get next execution time for a disposed cron job');
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 创建临时任务来获取下次执行时间
|
|
82
|
-
const tempJob = new Croner(this._cronExpression, { paused: true });
|
|
83
|
-
const nextRun = tempJob.nextRun();
|
|
84
|
-
tempJob.stop();
|
|
85
|
-
|
|
86
|
-
if (!nextRun) {
|
|
87
|
-
throw new Error('Cannot determine next execution time');
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return nextRun;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 检查任务是否正在运行
|
|
95
|
-
*/
|
|
96
|
-
get running(): boolean {
|
|
97
|
-
return this.job !== null;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* 检查任务是否已被销毁
|
|
102
|
-
*/
|
|
103
|
-
get disposed(): boolean {
|
|
104
|
-
return this.isDisposed;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* 获取原始的 cron 表达式字符串
|
|
109
|
-
*/
|
|
110
|
-
get cronExpression(): string {
|
|
111
|
-
return this._cronExpression;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
//
|
|
116
|
-
// Cron 表达式格式说明:
|
|
117
|
-
//
|
|
118
|
-
// 标准格式: "分 时 日 月 周" (5 字段)
|
|
119
|
-
//
|
|
120
|
-
// 字段说明:
|
|
121
|
-
// - 分: 0-59
|
|
122
|
-
// - 时: 0-23
|
|
123
|
-
// - 日: 1-31
|
|
124
|
-
// - 月: 1-12 (或 JAN-DEC)
|
|
125
|
-
// - 周: 0-7 (0和7都表示周日,或 SUN-SAT)
|
|
126
|
-
//
|
|
127
|
-
// > croner 也支持 6 字段格式 "秒 分 时 日 月 周",但推荐使用 5 字段格式。
|
|
128
|
-
//
|
|
129
|
-
// 特殊字符:
|
|
130
|
-
// - 星号: 匹配任意值
|
|
131
|
-
// - 问号: 用于日和周字段,表示不指定值
|
|
132
|
-
// - 横线: 表示范围,如 1-5
|
|
133
|
-
// - 逗号: 表示列表,如 1,3,5
|
|
134
|
-
// - 斜杠: 表示步长,如 */15 表示每15分钟
|
|
135
|
-
//
|
|
136
|
-
// 常用示例:
|
|
137
|
-
// - "0 0 * * *": 每天午夜执行
|
|
138
|
-
// - "*/15 * * * *": 每15分钟执行
|
|
139
|
-
// - "0 12 * * *": 每天中午12点执行
|
|
140
|
-
// - "0 0 1 * *": 每月1号午夜执行
|
|
141
|
-
// - "0 0 * * 0": 每周日午夜执行
|
|
142
|
-
//
|
|
4
|
+
export { Cron } from '@zhin.js/kernel';
|
package/src/errors.ts
CHANGED
|
@@ -1,70 +1,21 @@
|
|
|
1
|
-
// ============================================================================
|
|
2
|
-
// 错误处理系统
|
|
3
|
-
// ============================================================================
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
|
-
*
|
|
2
|
+
* Re-export generic errors from @zhin.js/kernel
|
|
7
3
|
*/
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (Error.captureStackTrace) {
|
|
22
|
-
Error.captureStackTrace(this, this.constructor)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 转换为JSON格式
|
|
28
|
-
*/
|
|
29
|
-
toJSON() {
|
|
30
|
-
return {
|
|
31
|
-
name: this.name,
|
|
32
|
-
message: this.message,
|
|
33
|
-
code: this.code,
|
|
34
|
-
timestamp: this.timestamp.toISOString(),
|
|
35
|
-
context: this.context,
|
|
36
|
-
stack: this.stack
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 转换为用户友好的格式
|
|
42
|
-
*/
|
|
43
|
-
toUserString(): string {
|
|
44
|
-
return `[${this.code}] ${this.message}`
|
|
45
|
-
}
|
|
46
|
-
}
|
|
4
|
+
export {
|
|
5
|
+
ZhinError,
|
|
6
|
+
ConfigError,
|
|
7
|
+
PluginError,
|
|
8
|
+
ConnectionError,
|
|
9
|
+
ContextError,
|
|
10
|
+
ValidationError,
|
|
11
|
+
PermissionError,
|
|
12
|
+
TimeoutError,
|
|
13
|
+
ErrorManager,
|
|
14
|
+
RetryManager,
|
|
15
|
+
CircuitBreaker,
|
|
16
|
+
} from '@zhin.js/kernel';
|
|
47
17
|
|
|
48
|
-
|
|
49
|
-
* 配置相关错误
|
|
50
|
-
*/
|
|
51
|
-
export class ConfigError extends ZhinError {
|
|
52
|
-
constructor(message: string, context?: Record<string, any>) {
|
|
53
|
-
super(message, 'CONFIG_ERROR', context)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 插件相关错误
|
|
59
|
-
*/
|
|
60
|
-
export class PluginError extends ZhinError {
|
|
61
|
-
public readonly pluginName: string
|
|
62
|
-
|
|
63
|
-
constructor(message: string, pluginName: string, context?: Record<string, any>) {
|
|
64
|
-
super(message, 'PLUGIN_ERROR', { ...context, pluginName })
|
|
65
|
-
this.pluginName = pluginName
|
|
66
|
-
}
|
|
67
|
-
}
|
|
18
|
+
import { ZhinError } from '@zhin.js/kernel';
|
|
68
19
|
|
|
69
20
|
/**
|
|
70
21
|
* 适配器相关错误
|
|
@@ -80,18 +31,6 @@ export class AdapterError extends ZhinError {
|
|
|
80
31
|
}
|
|
81
32
|
}
|
|
82
33
|
|
|
83
|
-
/**
|
|
84
|
-
* 连接相关错误
|
|
85
|
-
*/
|
|
86
|
-
export class ConnectionError extends ZhinError {
|
|
87
|
-
public readonly retryable: boolean
|
|
88
|
-
|
|
89
|
-
constructor(message: string, retryable: boolean = true, context?: Record<string, any>) {
|
|
90
|
-
super(message, 'CONNECTION_ERROR', { ...context, retryable })
|
|
91
|
-
this.retryable = retryable
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
34
|
/**
|
|
96
35
|
* 消息处理错误
|
|
97
36
|
*/
|
|
@@ -105,261 +44,3 @@ export class MessageError extends ZhinError {
|
|
|
105
44
|
this.channelId = channelId
|
|
106
45
|
}
|
|
107
46
|
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* 上下文相关错误
|
|
111
|
-
*/
|
|
112
|
-
export class ContextError extends ZhinError {
|
|
113
|
-
public readonly contextName: string
|
|
114
|
-
|
|
115
|
-
constructor(message: string, contextName: string, context?: Record<string, any>) {
|
|
116
|
-
super(message, 'CONTEXT_ERROR', { ...context, contextName })
|
|
117
|
-
this.contextName = contextName
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* 验证错误
|
|
123
|
-
*/
|
|
124
|
-
export class ValidationError extends ZhinError {
|
|
125
|
-
public readonly field?: string
|
|
126
|
-
public readonly value?: any
|
|
127
|
-
|
|
128
|
-
constructor(message: string, field?: string, value?: any, context?: Record<string, any>) {
|
|
129
|
-
super(message, 'VALIDATION_ERROR', { ...context, field, value })
|
|
130
|
-
this.field = field
|
|
131
|
-
this.value = value
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* 权限错误
|
|
137
|
-
*/
|
|
138
|
-
export class PermissionError extends ZhinError {
|
|
139
|
-
public readonly userId?: string
|
|
140
|
-
public readonly requiredPermission?: string
|
|
141
|
-
|
|
142
|
-
constructor(message: string, userId?: string, requiredPermission?: string, context?: Record<string, any>) {
|
|
143
|
-
super(message, 'PERMISSION_ERROR', { ...context, userId, requiredPermission })
|
|
144
|
-
this.userId = userId
|
|
145
|
-
this.requiredPermission = requiredPermission
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* 超时错误
|
|
151
|
-
*/
|
|
152
|
-
export class TimeoutError extends ZhinError {
|
|
153
|
-
public readonly timeoutMs: number
|
|
154
|
-
|
|
155
|
-
constructor(message: string, timeoutMs: number, context?: Record<string, any>) {
|
|
156
|
-
super(message, 'TIMEOUT_ERROR', { ...context, timeoutMs })
|
|
157
|
-
this.timeoutMs = timeoutMs
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* 错误处理器接口
|
|
163
|
-
*/
|
|
164
|
-
export interface ErrorHandler {
|
|
165
|
-
(error: Error, context?: Record<string, any>): void | Promise<void>
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 错误管理器
|
|
170
|
-
*/
|
|
171
|
-
export class ErrorManager {
|
|
172
|
-
private handlers: Map<string, ErrorHandler[]> = new Map()
|
|
173
|
-
private globalHandlers: ErrorHandler[] = []
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* 注册错误处理器
|
|
177
|
-
*/
|
|
178
|
-
register(errorType: string, handler: ErrorHandler): void {
|
|
179
|
-
if (!this.handlers.has(errorType)) {
|
|
180
|
-
this.handlers.set(errorType, [])
|
|
181
|
-
}
|
|
182
|
-
this.handlers.get(errorType)!.push(handler)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* 注册全局错误处理器
|
|
187
|
-
*/
|
|
188
|
-
registerGlobal(handler: ErrorHandler): void {
|
|
189
|
-
this.globalHandlers.push(handler)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* 处理错误
|
|
194
|
-
*/
|
|
195
|
-
async handle(error: Error, context?: Record<string, any>): Promise<void> {
|
|
196
|
-
// 首先调用全局处理器
|
|
197
|
-
for (const handler of this.globalHandlers) {
|
|
198
|
-
try {
|
|
199
|
-
await handler(error, context)
|
|
200
|
-
} catch (handlerError) {
|
|
201
|
-
// console.error 已替换为注释
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// 然后调用特定类型的处理器
|
|
206
|
-
const errorType = error.constructor.name
|
|
207
|
-
const handlers = this.handlers.get(errorType) || []
|
|
208
|
-
|
|
209
|
-
for (const handler of handlers) {
|
|
210
|
-
try {
|
|
211
|
-
await handler(error, context)
|
|
212
|
-
} catch (handlerError) {
|
|
213
|
-
// console.error 已替换为注释
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* 移除错误处理器
|
|
220
|
-
*/
|
|
221
|
-
unregister(errorType: string, handler: ErrorHandler): boolean {
|
|
222
|
-
const handlers = this.handlers.get(errorType)
|
|
223
|
-
if (handlers) {
|
|
224
|
-
const index = handlers.indexOf(handler)
|
|
225
|
-
if (index !== -1) {
|
|
226
|
-
handlers.splice(index, 1)
|
|
227
|
-
return true
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
return false
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* 清理所有处理器
|
|
235
|
-
*/
|
|
236
|
-
clear(): void {
|
|
237
|
-
this.handlers.clear()
|
|
238
|
-
this.globalHandlers.length = 0
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 错误重试工具
|
|
244
|
-
*/
|
|
245
|
-
export class RetryManager {
|
|
246
|
-
/**
|
|
247
|
-
* 执行重试逻辑
|
|
248
|
-
*/
|
|
249
|
-
static async retry<T>(
|
|
250
|
-
fn: () => Promise<T>,
|
|
251
|
-
options: {
|
|
252
|
-
maxRetries?: number
|
|
253
|
-
delay?: number
|
|
254
|
-
exponentialBackoff?: boolean
|
|
255
|
-
retryCondition?: (error: Error) => boolean
|
|
256
|
-
} = {}
|
|
257
|
-
): Promise<T> {
|
|
258
|
-
const {
|
|
259
|
-
maxRetries = 3,
|
|
260
|
-
delay = 1000,
|
|
261
|
-
exponentialBackoff = true,
|
|
262
|
-
retryCondition = () => true
|
|
263
|
-
} = options
|
|
264
|
-
|
|
265
|
-
let lastError: Error
|
|
266
|
-
|
|
267
|
-
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
268
|
-
try {
|
|
269
|
-
return await fn()
|
|
270
|
-
} catch (error) {
|
|
271
|
-
lastError = error as Error
|
|
272
|
-
|
|
273
|
-
// 如果是最后一次尝试或不满足重试条件,直接抛出错误
|
|
274
|
-
if (attempt === maxRetries || !retryCondition(lastError)) {
|
|
275
|
-
throw lastError
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// 计算延迟时间
|
|
279
|
-
const currentDelay = exponentialBackoff
|
|
280
|
-
? delay * Math.pow(2, attempt)
|
|
281
|
-
: delay
|
|
282
|
-
|
|
283
|
-
// 等待后重试
|
|
284
|
-
await new Promise(resolve => setTimeout(resolve, currentDelay))
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
throw lastError!
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* 断路器模式实现
|
|
294
|
-
*/
|
|
295
|
-
export class CircuitBreaker {
|
|
296
|
-
private failures: number = 0
|
|
297
|
-
private lastFailureTime: number = 0
|
|
298
|
-
private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'
|
|
299
|
-
|
|
300
|
-
constructor(
|
|
301
|
-
private failureThreshold: number = 5,
|
|
302
|
-
private timeoutMs: number = 60000,
|
|
303
|
-
private monitoringPeriodMs: number = 10000
|
|
304
|
-
) {}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* 执行受保护的操作
|
|
308
|
-
*/
|
|
309
|
-
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
310
|
-
if (this.state === 'OPEN') {
|
|
311
|
-
if (Date.now() - this.lastFailureTime > this.timeoutMs) {
|
|
312
|
-
this.state = 'HALF_OPEN'
|
|
313
|
-
} else {
|
|
314
|
-
throw new Error('Circuit breaker is OPEN')
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
try {
|
|
319
|
-
const result = await fn()
|
|
320
|
-
this.onSuccess()
|
|
321
|
-
return result
|
|
322
|
-
} catch (error) {
|
|
323
|
-
this.onFailure()
|
|
324
|
-
throw error
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
private onSuccess(): void {
|
|
329
|
-
this.failures = 0
|
|
330
|
-
this.state = 'CLOSED'
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
private onFailure(): void {
|
|
334
|
-
this.failures++
|
|
335
|
-
this.lastFailureTime = Date.now()
|
|
336
|
-
|
|
337
|
-
if (this.failures >= this.failureThreshold) {
|
|
338
|
-
this.state = 'OPEN'
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* 获取断路器状态
|
|
344
|
-
*/
|
|
345
|
-
getState(): 'CLOSED' | 'OPEN' | 'HALF_OPEN' {
|
|
346
|
-
return this.state
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* 重置断路器
|
|
351
|
-
*/
|
|
352
|
-
reset(): void {
|
|
353
|
-
this.failures = 0
|
|
354
|
-
this.lastFailureTime = 0
|
|
355
|
-
this.state = 'CLOSED'
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// 默认错误管理器实例
|
|
360
|
-
export const errorManager = new ErrorManager()
|
|
361
|
-
|
|
362
|
-
// 默认错误处理器
|
|
363
|
-
errorManager.registerGlobal((error, context) => {
|
|
364
|
-
// Default error handler - logs to console
|
|
365
|
-
})
|
package/src/feature.ts
CHANGED
|
@@ -1,156 +1,7 @@
|
|
|
1
|
-
import type { Plugin } from "./plugin.js";
|
|
2
1
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Re-export from @zhin.js/kernel for backward compatibility.
|
|
3
|
+
* Core's Plugin class implements PluginLike, so the Feature's
|
|
4
|
+
* mounted(plugin: PluginLike) signature is compatible.
|
|
6
5
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* Feature 序列化后的 JSON 格式,用于 HTTP API 返回给前端
|
|
10
|
-
*/
|
|
11
|
-
export interface FeatureJSON {
|
|
12
|
-
name: string;
|
|
13
|
-
icon: string;
|
|
14
|
-
desc: string;
|
|
15
|
-
count: number;
|
|
16
|
-
items: any[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Feature 变更事件监听器类型
|
|
21
|
-
*/
|
|
22
|
-
export type FeatureListener<T> = (item: T, pluginName: string) => void;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Feature<T> 抽象类
|
|
26
|
-
* - name / icon / desc: 自描述元数据
|
|
27
|
-
* - items: 全局 item 列表
|
|
28
|
-
* - pluginItems: 按插件名分组的 item 列表
|
|
29
|
-
* - toJSON: 控制 HTTP API 返回的数据格式
|
|
30
|
-
* - extensions: 提供给 Plugin.prototype 的扩展方法(如 addCommand)
|
|
31
|
-
* - mounted / dispose: 可选生命周期钩子
|
|
32
|
-
* - on('add' | 'remove'): 变更事件通知
|
|
33
|
-
*/
|
|
34
|
-
export abstract class Feature<T = any> {
|
|
35
|
-
abstract readonly name: string;
|
|
36
|
-
abstract readonly icon: string;
|
|
37
|
-
abstract readonly desc: string;
|
|
38
|
-
|
|
39
|
-
/** 全局 item 列表 */
|
|
40
|
-
readonly items: T[] = [];
|
|
41
|
-
|
|
42
|
-
/** 按插件名分组的 item 列表 */
|
|
43
|
-
protected pluginItems = new Map<string, T[]>();
|
|
44
|
-
|
|
45
|
-
/** 事件监听器 */
|
|
46
|
-
#listeners = new Map<string, Set<FeatureListener<T>>>();
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 监听变更事件
|
|
50
|
-
* @param event 'add' | 'remove'
|
|
51
|
-
* @param listener 回调函数
|
|
52
|
-
* @returns 取消监听的函数
|
|
53
|
-
*/
|
|
54
|
-
on(event: 'add' | 'remove', listener: FeatureListener<T>): () => void {
|
|
55
|
-
if (!this.#listeners.has(event)) {
|
|
56
|
-
this.#listeners.set(event, new Set());
|
|
57
|
-
}
|
|
58
|
-
this.#listeners.get(event)!.add(listener);
|
|
59
|
-
return () => { this.#listeners.get(event)?.delete(listener); };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** 触发事件 */
|
|
63
|
-
protected emit(event: 'add' | 'remove', item: T, pluginName: string): void {
|
|
64
|
-
const listeners = this.#listeners.get(event);
|
|
65
|
-
if (!listeners) return;
|
|
66
|
-
for (const listener of listeners) {
|
|
67
|
-
try { listener(item, pluginName); } catch { /* 防止监听器异常影响主流程 */ }
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 添加 item,同时记录所属插件
|
|
73
|
-
* @returns dispose 函数,用于移除该 item
|
|
74
|
-
*/
|
|
75
|
-
add(item: T, pluginName: string): () => void {
|
|
76
|
-
this.items.push(item);
|
|
77
|
-
if (!this.pluginItems.has(pluginName)) {
|
|
78
|
-
this.pluginItems.set(pluginName, []);
|
|
79
|
-
}
|
|
80
|
-
this.pluginItems.get(pluginName)!.push(item);
|
|
81
|
-
this.emit('add', item, pluginName);
|
|
82
|
-
return () => this.remove(item, pluginName);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* 移除 item
|
|
87
|
-
*/
|
|
88
|
-
remove(item: T, pluginName?: string): boolean {
|
|
89
|
-
const idx = this.items.indexOf(item);
|
|
90
|
-
if (idx !== -1) {
|
|
91
|
-
this.items.splice(idx, 1);
|
|
92
|
-
const resolvedPluginName = pluginName ?? this.#findPluginName(item);
|
|
93
|
-
for (const [, items] of this.pluginItems) {
|
|
94
|
-
const i = items.indexOf(item);
|
|
95
|
-
if (i !== -1) items.splice(i, 1);
|
|
96
|
-
}
|
|
97
|
-
this.emit('remove', item, resolvedPluginName ?? '');
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** 反查 item 所属的 pluginName */
|
|
104
|
-
#findPluginName(item: T): string | undefined {
|
|
105
|
-
for (const [name, items] of this.pluginItems) {
|
|
106
|
-
if (items.includes(item)) return name;
|
|
107
|
-
}
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* 获取指定插件注册的 item 列表
|
|
113
|
-
*/
|
|
114
|
-
getByPlugin(pluginName: string): T[] {
|
|
115
|
-
return this.pluginItems.get(pluginName) || [];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* 全局 item 数量
|
|
120
|
-
*/
|
|
121
|
-
get count(): number {
|
|
122
|
-
return this.items.length;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* 指定插件的 item 数量
|
|
127
|
-
*/
|
|
128
|
-
countByPlugin(pluginName: string): number {
|
|
129
|
-
return this.getByPlugin(pluginName).length;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* 序列化为 JSON(用于 HTTP API)
|
|
134
|
-
* @param pluginName 如果提供,则只返回该插件的 item;否则返回全部
|
|
135
|
-
*/
|
|
136
|
-
abstract toJSON(pluginName?: string): FeatureJSON;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* 提供给 Plugin.prototype 的扩展方法
|
|
140
|
-
* 子类重写此 getter 以注册扩展(如 addCommand)
|
|
141
|
-
*/
|
|
142
|
-
get extensions(): Record<string, Function> {
|
|
143
|
-
return {};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* 生命周期: 服务挂载时调用
|
|
148
|
-
* @param plugin 宿主插件(通常是 root plugin)
|
|
149
|
-
*/
|
|
150
|
-
mounted?(plugin: Plugin): void | Promise<void>;
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* 生命周期: 服务销毁时调用
|
|
154
|
-
*/
|
|
155
|
-
dispose?(): void;
|
|
156
|
-
}
|
|
6
|
+
export { Feature } from '@zhin.js/kernel';
|
|
7
|
+
export type { FeatureJSON, FeatureListener } from '@zhin.js/kernel';
|
package/src/index.ts
CHANGED
|
@@ -39,4 +39,6 @@ export * from './scheduler/index.js'
|
|
|
39
39
|
export * from '@zhin.js/database'
|
|
40
40
|
export * from '@zhin.js/logger'
|
|
41
41
|
// 只导出 Schema 类,避免与 utils.js 的 isEmpty 冲突
|
|
42
|
-
export { Schema } from '@zhin.js/schema'
|
|
42
|
+
export { Schema } from '@zhin.js/schema'
|
|
43
|
+
// Re-export PluginLike from kernel (generic plugin interface)
|
|
44
|
+
export type { PluginLike } from '@zhin.js/kernel'
|