@taicode/common-server 1.0.12 → 1.0.13

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 (56) hide show
  1. package/output/index.d.ts +1 -0
  2. package/output/index.d.ts.map +1 -1
  3. package/output/index.js +1 -0
  4. package/output/redis-queue/index.d.ts +1 -0
  5. package/output/redis-queue/index.d.ts.map +1 -1
  6. package/output/redis-queue/redis-batch-consumer.d.ts +4 -9
  7. package/output/redis-queue/redis-batch-consumer.d.ts.map +1 -1
  8. package/output/redis-queue/redis-batch-consumer.js +3 -55
  9. package/output/redis-queue/redis-batch-consumer.test.d.ts +5 -0
  10. package/output/redis-queue/redis-batch-consumer.test.d.ts.map +1 -1
  11. package/output/redis-queue/redis-queue-common.d.ts +15 -3
  12. package/output/redis-queue/redis-queue-common.d.ts.map +1 -1
  13. package/output/redis-queue/redis-queue-common.js +80 -0
  14. package/output/redis-queue/redis-queue-common.test.d.ts +17 -0
  15. package/output/redis-queue/redis-queue-common.test.d.ts.map +1 -1
  16. package/output/redis-queue/redis-queue-common.test.js +11 -11
  17. package/output/redis-queue/redis-queue-consumer.d.ts +6 -27
  18. package/output/redis-queue/redis-queue-consumer.d.ts.map +1 -1
  19. package/output/redis-queue/redis-queue-consumer.js +23 -187
  20. package/output/redis-queue/redis-queue-consumer.test.d.ts +5 -0
  21. package/output/redis-queue/redis-queue-consumer.test.d.ts.map +1 -1
  22. package/output/redis-queue/redis-queue-provider.d.ts +14 -15
  23. package/output/redis-queue/redis-queue-provider.d.ts.map +1 -1
  24. package/output/redis-queue/redis-queue-provider.js +8 -9
  25. package/output/redis-queue/redis-queue-provider.test.d.ts +5 -0
  26. package/output/redis-queue/redis-queue-provider.test.d.ts.map +1 -1
  27. package/output/redis-queue/types.d.ts +47 -9
  28. package/output/redis-queue/types.d.ts.map +1 -1
  29. package/package.json +1 -1
  30. package/output/logger/logger.d.ts +0 -33
  31. package/output/logger/logger.d.ts.map +0 -1
  32. package/output/logger/logger.js +0 -65
  33. package/output/logger/logger.test.d.ts +0 -2
  34. package/output/logger/logger.test.d.ts.map +0 -1
  35. package/output/logger/logger.test.js +0 -87
  36. package/output/redis-queue/batch-consumer.d.ts +0 -107
  37. package/output/redis-queue/batch-consumer.d.ts.map +0 -1
  38. package/output/redis-queue/batch-consumer.js +0 -492
  39. package/output/redis-queue/batch-consumer.test.d.ts +0 -2
  40. package/output/redis-queue/batch-consumer.test.d.ts.map +0 -1
  41. package/output/redis-queue/batch-consumer.test.js +0 -216
  42. package/output/redis-queue/batch-redis-queue.d.ts +0 -136
  43. package/output/redis-queue/batch-redis-queue.d.ts.map +0 -1
  44. package/output/redis-queue/batch-redis-queue.js +0 -583
  45. package/output/redis-queue/batch-redis-queue.test.d.ts +0 -2
  46. package/output/redis-queue/batch-redis-queue.test.d.ts.map +0 -1
  47. package/output/redis-queue/batch-redis-queue.test.js +0 -243
  48. package/output/redis-queue/redis-queue.d.ts +0 -129
  49. package/output/redis-queue/redis-queue.d.ts.map +0 -1
  50. package/output/redis-queue/redis-queue.js +0 -557
  51. package/output/redis-queue/redis-queue.test.d.ts +0 -2
  52. package/output/redis-queue/redis-queue.test.d.ts.map +0 -1
  53. package/output/redis-queue/redis-queue.test.js +0 -234
  54. package/output/redis-queue/registry.d.ts +0 -57
  55. package/output/redis-queue/registry.d.ts.map +0 -1
  56. package/output/redis-queue/registry.js +0 -30
@@ -1,11 +1,11 @@
1
- import { createClient } from 'redis';
2
1
  import { catchIt } from '@taicode/common-base';
2
+ import { RedisQueueCommon } from './redis-queue-common';
3
3
  /**
4
4
  * Redis 队列消费者
5
5
  *
6
6
  * 只负责消费任务,不包含入队逻辑
7
7
  *
8
- * @template T 任务数据类型
8
+ * @template K 队列键名类型
9
9
  *
10
10
  * @example
11
11
  * ```ts
@@ -33,76 +33,47 @@ import { catchIt } from '@taicode/common-base';
33
33
  * consumer.disconnect()
34
34
  * ```
35
35
  */
36
- export class RedisQueueConsumer {
36
+ export class RedisQueueConsumer extends RedisQueueCommon {
37
37
  consumerRunning = false;
38
- redis = null;
39
38
  consumerInterval = null;
40
39
  recoveryInterval = null;
41
40
  processingTasks = 0; // 当前正在处理的任务数
42
41
  config;
43
- // 不同状态队列的键名
44
- failedQueue;
45
- pendingQueue;
46
- processingQueue;
47
- completedQueue;
48
42
  constructor(config) {
49
43
  // 验证必填参数
50
- if (!config.redisUrl && !config.redisClient) {
51
- throw new Error('[RedisQueueConsumer] Either redisUrl or redisClient is required');
52
- }
53
- if (config.redisUrl && config.redisClient) {
54
- throw new Error('[RedisQueueConsumer] Cannot specify both redisUrl and redisClient');
55
- }
56
- if (!config.queueKey) {
57
- throw new Error('[RedisQueueConsumer] queueKey is required');
58
- }
59
- if (typeof config.queueKey === 'string' && config.queueKey.length < 6) {
60
- throw new Error('[RedisQueueConsumer] queueKey must be at least 6 characters long');
61
- }
62
44
  if (!config.handler) {
63
45
  throw new Error('[RedisQueueConsumer] handler is required');
64
46
  }
47
+ // 调用父类构造函数
48
+ super({
49
+ redisUrl: config.redisUrl,
50
+ redisClient: config.redisClient,
51
+ queueKey: config.queueKey,
52
+ cleanupDelay: config.cleanupDelay ?? 86400,
53
+ });
65
54
  this.config = {
55
+ handler: config.handler,
66
56
  queueKey: config.queueKey,
67
57
  redisUrl: config.redisUrl,
68
58
  redisClient: config.redisClient,
69
- handler: config.handler,
70
- cleanupDelay: config.cleanupDelay ?? 86400, // 24 hours
71
59
  maxRetries: config.maxRetries ?? 3,
72
60
  concurrency: config.concurrency ?? 1,
73
61
  consumerInterval: config.consumerInterval ?? 1000,
74
- processingTimeout: config.processingTimeout ?? 60000, // 60 seconds
62
+ processingTimeout: config.processingTimeout ?? 60000,
63
+ cleanupDelay: config.cleanupDelay ?? 86400,
75
64
  };
76
- // 初始化不同状态队列的键名
77
- this.failedQueue = `${config.queueKey}:failed`;
78
- this.pendingQueue = `${config.queueKey}:pending`;
79
- this.completedQueue = `${config.queueKey}:completed`;
80
- this.processingQueue = `${config.queueKey}:processing`;
81
- // 使用外部客户端或创建新客户端
82
- if (config.redisClient) {
83
- this.redis = config.redisClient;
84
- }
85
- else {
86
- this.redis = createClient({ url: this.config.redisUrl });
87
- // 添加错误处理
88
- this.redis.on('error', (err) => {
89
- console.error('[RedisQueueConsumer] Redis Client Error:', err);
90
- });
91
- }
65
+ }
66
+ getLogPrefix() {
67
+ return 'RedisQueueConsumer';
92
68
  }
93
69
  /**
94
70
  * 连接 Redis 并自动启动消费者
95
71
  */
96
72
  async connect() {
97
- if (this.redis && !this.redis.isOpen) {
98
- await this.redis.connect().catch((error) => {
99
- console.error('[RedisQueueConsumer] Failed to connect to Redis:', error);
100
- throw error;
101
- });
102
- // 连接成功后启动消费者和恢复机制
103
- this.startConsumer();
104
- this.startRecovery();
105
- }
73
+ await super.connect();
74
+ // 连接成功后启动消费者和恢复机制
75
+ this.startConsumer();
76
+ this.startRecovery();
106
77
  }
107
78
  /**
108
79
  * 断开 Redis 连接并停止消费者
@@ -110,142 +81,7 @@ export class RedisQueueConsumer {
110
81
  disconnect() {
111
82
  this.stopConsumer();
112
83
  this.stopRecovery();
113
- if (this.redis && this.redis.isOpen) {
114
- this.redis.disconnect().catch((error) => {
115
- console.error('[RedisQueueConsumer] Failed to disconnect:', error);
116
- });
117
- }
118
- }
119
- /**
120
- * 获取任务详情
121
- */
122
- async getTask(taskId) {
123
- if (!this.redis)
124
- return null;
125
- const taskKey = `${this.config.queueKey}:task:${taskId}`;
126
- const taskData = await this.redis.get(taskKey);
127
- if (!taskData)
128
- return null;
129
- return JSON.parse(taskData);
130
- }
131
- /**
132
- * 更新任务状态并移动到对应队列(原子操作)
133
- */
134
- async applyStatus(taskId, oldStatus, newStatus) {
135
- if (!this.redis)
136
- return;
137
- const task = await this.getTask(taskId);
138
- if (!task)
139
- return;
140
- task.status = newStatus;
141
- const taskKey = `${this.config.queueKey}:task:${taskId}`;
142
- const oldQueue = this.getQueueByStatus(oldStatus);
143
- const newQueue = this.getQueueByStatus(newStatus);
144
- if (oldQueue !== newQueue) {
145
- // 使用 Lua 脚本确保原子性:更新任务 + 移动队列
146
- const script = `
147
- redis.call('SETEX', KEYS[1], ARGV[1], ARGV[2])
148
- redis.call('LREM', KEYS[2], 0, ARGV[3])
149
- redis.call('RPUSH', KEYS[3], ARGV[3])
150
- return 1
151
- `;
152
- await this.redis.eval(script, {
153
- keys: [taskKey, oldQueue, newQueue],
154
- arguments: [this.config.cleanupDelay.toString(), JSON.stringify(task), taskId],
155
- });
156
- }
157
- else {
158
- // 只更新任务数据
159
- await this.redis.setEx(taskKey, this.config.cleanupDelay, JSON.stringify(task));
160
- }
161
- }
162
- /**
163
- * 根据状态获取对应的队列键
164
- */
165
- getQueueByStatus(status) {
166
- switch (status) {
167
- case 'pending': return this.pendingQueue;
168
- case 'processing': return this.processingQueue;
169
- case 'completed': return this.completedQueue;
170
- case 'failed': return this.failedQueue;
171
- default: return this.pendingQueue;
172
- }
173
- }
174
- /**
175
- * 恢复超时的任务
176
- * 检查 processing 队列中的任务,将超时的任务直接标记为失败
177
- * 使用 Lua 脚本批量处理以提高性能和原子性
178
- */
179
- async recoverStalledTasks() {
180
- if (!this.redis)
181
- return;
182
- try {
183
- const processingTaskIds = await this.redis.lRange(this.processingQueue, 0, -1);
184
- if (processingTaskIds.length === 0) {
185
- return;
186
- }
187
- // 使用 Lua 脚本批量恢复超时任务
188
- const batchRecoveryScript = `
189
- local processingQueue = KEYS[1]
190
- local pendingQueue = KEYS[2]
191
- local failedQueue = KEYS[3]
192
- local queueKeyPrefix = ARGV[1]
193
- local ttl = tonumber(ARGV[2])
194
- local processingTimeout = tonumber(ARGV[3])
195
- local now = tonumber(ARGV[4])
196
-
197
- local retryCount = 0
198
- local failedCount = 0
199
- local cleanupCount = 0
200
-
201
- -- ARGV[5], ARGV[6], ARGV[7]... 是 taskId
202
- for i = 5, #ARGV do
203
- local taskId = ARGV[i]
204
- local taskKey = queueKeyPrefix .. ':task:' .. taskId
205
- local taskData = redis.call('GET', taskKey)
206
-
207
- if not taskData then
208
- -- 任务不存在,从队列中清理
209
- redis.call('LREM', processingQueue, 0, taskId)
210
- cleanupCount = cleanupCount + 1
211
- else
212
- local task = cjson.decode(taskData)
213
- local processingTime = now - (task.processingStartTime or now)
214
-
215
- -- 检查是否超时
216
- if processingTime > processingTimeout then
217
- -- 超时直接标记为失败
218
- task.status = 'failed'
219
- task.processingStartTime = nil
220
-
221
- redis.call('SETEX', taskKey, ttl, cjson.encode(task))
222
- redis.call('LREM', processingQueue, 0, taskId)
223
- redis.call('RPUSH', failedQueue, taskId)
224
- failedCount = failedCount + 1
225
- end
226
- end
227
- end
228
-
229
- return {retryCount, failedCount, cleanupCount}
230
- `;
231
- const result = await this.redis.eval(batchRecoveryScript, {
232
- keys: [this.processingQueue, this.pendingQueue, this.failedQueue],
233
- arguments: [
234
- this.config.queueKey,
235
- this.config.cleanupDelay.toString(),
236
- this.config.processingTimeout.toString(),
237
- Date.now().toString(),
238
- ...processingTaskIds,
239
- ],
240
- });
241
- const [retryCount, failedCount, cleanupCount] = result;
242
- if (failedCount > 0 || cleanupCount > 0) {
243
- console.log(`[RedisQueueConsumer] Recovered tasks - timeout failed: ${failedCount}, cleaned: ${cleanupCount}`);
244
- }
245
- }
246
- catch (error) {
247
- console.error('[RedisQueueConsumer] Failed to recover stalled tasks:', error);
248
- }
84
+ super.disconnect();
249
85
  }
250
86
  /**
251
87
  * 处理单个任务
@@ -328,12 +164,12 @@ export class RedisQueueConsumer {
328
164
  return;
329
165
  }
330
166
  // 立即执行一次恢复
331
- this.recoverStalledTasks().catch(error => {
167
+ this.recoverStalled(this.config.processingTimeout).catch(error => {
332
168
  console.error('[RedisQueueConsumer] Initial recovery error:', error);
333
169
  });
334
170
  // 定期检查(每 10 秒检查一次)
335
171
  this.recoveryInterval = setInterval(() => {
336
- this.recoverStalledTasks().catch(error => {
172
+ this.recoverStalled(this.config.processingTimeout).catch(error => {
337
173
  console.error('[RedisQueueConsumer] Recovery error:', error);
338
174
  });
339
175
  }, 10000);
@@ -1,2 +1,7 @@
1
+ declare module './types' {
2
+ interface RedisQueueRegistry {
3
+ 'test-queue': Record<string, any>;
4
+ }
5
+ }
1
6
  export {};
2
7
  //# sourceMappingURL=redis-queue-consumer.test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis-queue-consumer.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-consumer.test.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"redis-queue-consumer.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-consumer.test.ts"],"names":[],"mappings":"AAOA,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,kBAAkB;QAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAClC;CACF"}
@@ -1,30 +1,29 @@
1
1
  import { RedisQueueCommon } from './redis-queue-common';
2
- import type { RedisQueueProviderConfig, TaskData, Task, QueueStats } from './types';
2
+ import type { RedisQueueRegistry, RedisQueueProviderConfig, Task, QueueStats } from './types';
3
3
  /**
4
4
  * Redis 队列生产者
5
5
  *
6
6
  * 只负责任务的入队和队列管理,不包含消费逻辑
7
7
  *
8
- * @template T 任务数据类型
8
+ * @template K 队列键类型
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * // 使用类型注册表
13
- * const provider = new RedisQueueProvider<'email-queue'>({
14
- * redisUrl: 'redis://localhost:6379',
15
- * queueKey: 'email-queue',
12
+ * // 注册类型后使用
13
+ * const provider = new RedisQueueProvider({
14
+ * queueKey: 'email-queue', // 只能使用注册的键
16
15
  * })
17
16
  *
18
- * // 或直接指定类型
19
- * const provider = new RedisQueueProvider<EmailTask>({
20
- * redisUrl: 'redis://localhost:6379',
21
- * queueKey: 'email-queue',
17
+ * await provider.enqueue({
18
+ * to: 'user@example.com', // 类型自动推断
19
+ * subject: 'Hello',
20
+ * body: 'World'
22
21
  * })
23
22
  * ```
24
23
  */
25
- export declare class RedisQueueProvider<T extends TaskData = TaskData> extends RedisQueueCommon<T> {
24
+ export declare class RedisQueueProvider<K extends keyof RedisQueueRegistry> extends RedisQueueCommon {
26
25
  private readonly processingDelay;
27
- constructor(config: RedisQueueProviderConfig<string, T>);
26
+ constructor(config: RedisQueueProviderConfig<K>);
28
27
  protected getLogPrefix(): string;
29
28
  /**
30
29
  * 将任务推入队列(支持单个或批量)
@@ -38,12 +37,12 @@ export declare class RedisQueueProvider<T extends TaskData = TaskData> extends R
38
37
  * await provider.enqueue({ id: 'email-123', to: 'user@example.com' })
39
38
  * await provider.enqueue({ id: 'email-123', to: 'user@example.com' }) // 会被跳过
40
39
  */
41
- enqueue(data: T[]): Promise<string[]>;
42
- enqueue(data: T): Promise<string>;
40
+ enqueue(data: RedisQueueRegistry[K]): Promise<string>;
41
+ enqueue(data: RedisQueueRegistry[K]): Promise<string[]>;
43
42
  /**
44
43
  * 获取任务详情
45
44
  */
46
- getTask(taskId: string): Promise<Task<T> | null>;
45
+ getTask(taskId: string): Promise<Task<RedisQueueRegistry[K]> | null>;
47
46
  /**
48
47
  * 获取队列统计信息(O(1) 时间复杂度)
49
48
  */
@@ -1 +1 @@
1
- {"version":3,"file":"redis-queue-provider.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-provider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,wBAAwB,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAGnF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,kBAAkB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,CAAE,SAAQ,gBAAgB,CAAC,CAAC,CAAC;IACxF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;gBAE5B,MAAM,EAAE,wBAAwB,CAAC,MAAM,EAAE,CAAC,CAAC;IAWvD,SAAS,CAAC,YAAY,IAAI,MAAM;IAMhC;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IACrC,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAiFvC;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAWtD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAevC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAmD7B"}
1
+ {"version":3,"file":"redis-queue-provider.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-provider.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAE7F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,kBAAkB,CAAC,CAAC,SAAS,MAAM,kBAAkB,CAAE,SAAQ,gBAAgB;IAC1F,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;gBAE5B,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC;IAW/C,SAAS,CAAC,YAAY,IAAI,MAAM;IAMhC;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IACrD,OAAO,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAiF7D;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAW1E;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAevC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAmD7B"}
@@ -5,20 +5,19 @@ import { RedisQueueCommon } from './redis-queue-common';
5
5
  *
6
6
  * 只负责任务的入队和队列管理,不包含消费逻辑
7
7
  *
8
- * @template T 任务数据类型
8
+ * @template K 队列键类型
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * // 使用类型注册表
13
- * const provider = new RedisQueueProvider<'email-queue'>({
14
- * redisUrl: 'redis://localhost:6379',
15
- * queueKey: 'email-queue',
12
+ * // 注册类型后使用
13
+ * const provider = new RedisQueueProvider({
14
+ * queueKey: 'email-queue', // 只能使用注册的键
16
15
  * })
17
16
  *
18
- * // 或直接指定类型
19
- * const provider = new RedisQueueProvider<EmailTask>({
20
- * redisUrl: 'redis://localhost:6379',
21
- * queueKey: 'email-queue',
17
+ * await provider.enqueue({
18
+ * to: 'user@example.com', // 类型自动推断
19
+ * subject: 'Hello',
20
+ * body: 'World'
22
21
  * })
23
22
  * ```
24
23
  */
@@ -1,2 +1,7 @@
1
+ declare module './types' {
2
+ interface RedisQueueRegistry {
3
+ 'test-queue': Record<string, any>;
4
+ }
5
+ }
1
6
  export {};
2
7
  //# sourceMappingURL=redis-queue-provider.test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis-queue-provider.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-provider.test.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"redis-queue-provider.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-provider.test.ts"],"names":[],"mappings":"AAKA,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,kBAAkB;QAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAClC;CACF"}
@@ -1,4 +1,41 @@
1
+ import { CatchResult } from '@taicode/common-base';
1
2
  import type { RedisClientType } from 'redis';
3
+ /**
4
+ * 队列注册表
5
+ *
6
+ * 使用 interface 合并模式来定义队列键和对应的任务数据类型
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * // 在项目中声明队列类型 (types/redis-queue.d.ts)
11
+ * declare module '@taicode/common-server' {
12
+ * interface RedisQueueRegistry {
13
+ * 'email-queue': { to: string; subject: string; body: string }
14
+ * 'sms-queue': { phone: string; message: string }
15
+ * 'payment-queue': { orderId: string; amount: number }
16
+ * }
17
+ * }
18
+ *
19
+ * // 使用时自动类型检查和推断
20
+ * const provider = new RedisQueueProvider({
21
+ * queueKey: 'email-queue', // ✅ 只能使用注册的键
22
+ * })
23
+ *
24
+ * const consumer = new RedisQueueConsumer({
25
+ * queueKey: 'email-queue',
26
+ * handler: async (data) => {
27
+ * // data 自动推断为 { to: string; subject: string; body: string }
28
+ * console.log(data.to)
29
+ * return catchIt(() => {})
30
+ * }
31
+ * })
32
+ * ```
33
+ */
34
+ export interface RedisQueueRegistry {
35
+ 'test': {
36
+ example: string;
37
+ };
38
+ }
2
39
  /**
3
40
  * 任务状态
4
41
  */
@@ -6,8 +43,9 @@ export type Status = 'pending' | 'processing' | 'completed' | 'failed';
6
43
  /**
7
44
  * 任务数据类型,支持可选的 id 字段用于幂等性
8
45
  */
9
- export type TaskData = Record<string, unknown> & {
46
+ export type TaskData = {
10
47
  id?: string;
48
+ [key: string]: any;
11
49
  };
12
50
  /**
13
51
  * 任务数据结构
@@ -37,13 +75,13 @@ export interface QueueStats {
37
75
  export interface RedisQueueCommonConfig {
38
76
  redisUrl?: string;
39
77
  redisClient?: RedisClientType;
40
- queueKey: string;
78
+ queueKey: keyof RedisQueueRegistry;
41
79
  cleanupDelay?: number;
42
80
  }
43
81
  /**
44
82
  * 队列生产者配置
45
83
  */
46
- export interface RedisQueueProviderConfig<K extends string = string, T extends TaskData = TaskData> {
84
+ export interface RedisQueueProviderConfig<K extends keyof RedisQueueRegistry = keyof RedisQueueRegistry> {
47
85
  redisUrl?: string;
48
86
  redisClient?: RedisClientType;
49
87
  queueKey: K;
@@ -53,11 +91,11 @@ export interface RedisQueueProviderConfig<K extends string = string, T extends T
53
91
  /**
54
92
  * 队列消费者配置
55
93
  */
56
- export interface RedisQueueConsumerConfig<K extends string = string, T extends TaskData = TaskData> {
94
+ export interface RedisQueueConsumerConfig<K extends keyof RedisQueueRegistry> {
57
95
  redisUrl?: string;
58
96
  redisClient?: RedisClientType;
59
97
  queueKey: K;
60
- handler: TaskHandler<T>;
98
+ handler: TaskHandler<RedisQueueRegistry[K]>;
61
99
  maxRetries?: number;
62
100
  concurrency?: number;
63
101
  consumerInterval?: number;
@@ -67,15 +105,15 @@ export interface RedisQueueConsumerConfig<K extends string = string, T extends T
67
105
  /**
68
106
  * 任务处理器函数
69
107
  */
70
- export type TaskHandler<T extends TaskData = TaskData> = (data: T) => Promise<import('@taicode/common-base').CatchResult<unknown, string>>;
108
+ export type TaskHandler<T extends TaskData = TaskData> = (data: T) => Promise<CatchResult<unknown, string>>;
71
109
  /**
72
110
  * 批量消费者配置
73
111
  */
74
- export interface BatchConsumerConfig<K extends string = string, T extends TaskData = TaskData> {
112
+ export interface BatchConsumerConfig<K extends keyof RedisQueueRegistry> {
75
113
  redisUrl?: string;
76
114
  redisClient?: RedisClientType;
77
115
  queueKey: K;
78
- handler: BatchTaskHandler<T>;
116
+ handler: BatchTaskHandler<RedisQueueRegistry[K]>;
79
117
  batchSize?: number;
80
118
  maxRetries?: number;
81
119
  concurrency?: number;
@@ -86,5 +124,5 @@ export interface BatchConsumerConfig<K extends string = string, T extends TaskDa
86
124
  /**
87
125
  * 批量任务处理器函数
88
126
  */
89
- export type BatchTaskHandler<T extends TaskData = TaskData> = (dataList: T[]) => Promise<import('@taicode/common-base').CatchResult<unknown, string>>;
127
+ export type BatchTaskHandler<T extends TaskData = TaskData> = (dataList: T[]) => Promise<CatchResult<unknown, string>>;
90
128
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/redis-queue/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/redis-queue/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-server",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "author": "Alain",
5
5
  "license": "ISC",
6
6
  "description": "",
@@ -1,33 +0,0 @@
1
- import { FastifyBaseLogger } from 'fastify';
2
- import { Bindings, ChildLoggerOptions } from 'fastify/types/logger';
3
- import { LevelWithSilentOrString, LogFn } from 'pino';
4
- import { LoggerOptions } from '@taicode/common-base/logger';
5
- export declare class FastifyLogger implements FastifyBaseLogger {
6
- private logger;
7
- private _msgPrefix?;
8
- constructor(options?: LoggerOptions);
9
- /**
10
- * 创建子 logger
11
- */
12
- child(bindings: Bindings, options?: ChildLoggerOptions): FastifyBaseLogger;
13
- /**
14
- * Pino 兼容的 level getter/setter
15
- */
16
- get level(): LevelWithSilentOrString;
17
- set level(newLevel: LevelWithSilentOrString);
18
- /**
19
- * 获取消息前缀
20
- */
21
- get msgPrefix(): string | undefined;
22
- /**
23
- * 日志方法实现
24
- */
25
- fatal: LogFn;
26
- error: LogFn;
27
- warn: LogFn;
28
- info: LogFn;
29
- debug: LogFn;
30
- trace: LogFn;
31
- silent: LogFn;
32
- }
33
- //# sourceMappingURL=logger.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../source/logger/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AACnE,OAAO,EAAE,uBAAuB,EAAE,KAAK,EAAE,MAAM,MAAM,CAAA;AACrD,OAAO,EAAU,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAEnE,qBAAa,aAAc,YAAW,iBAAiB;IACrD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,UAAU,CAAC,CAAQ;gBAEf,OAAO,CAAC,EAAE,aAAa;IAInC;;OAEG;IACH,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,kBAAkB,GAAG,iBAAiB;IAgB1E;;OAEG;IACH,IAAI,KAAK,IAAI,uBAAuB,CAEnC;IAED,IAAI,KAAK,CAAC,QAAQ,EAAE,uBAAuB,EAK1C;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED;;OAEG;IACH,KAAK,EAAE,KAAK,CAEX;IAED,KAAK,EAAE,KAAK,CAEX;IAED,IAAI,EAAE,KAAK,CAEV;IAED,IAAI,EAAE,KAAK,CAEV;IAED,KAAK,EAAE,KAAK,CAEX;IAED,KAAK,EAAE,KAAK,CAEX;IAED,MAAM,EAAE,KAAK,CAEZ;CACF"}
@@ -1,65 +0,0 @@
1
- import { Logger } from '@taicode/common-base/logger';
2
- export class FastifyLogger {
3
- logger;
4
- _msgPrefix;
5
- constructor(options) {
6
- this.logger = new Logger(options);
7
- }
8
- /**
9
- * 创建子 logger
10
- */
11
- child(bindings, options) {
12
- const childOptions = {
13
- level: options?.level,
14
- transports: this.logger.getTransports(),
15
- };
16
- const childLogger = new FastifyLogger(childOptions);
17
- // 如果 bindings 包含 msgPrefix,保存它
18
- if (bindings && typeof bindings === 'object' && 'msgPrefix' in bindings) {
19
- childLogger._msgPrefix = bindings.msgPrefix;
20
- }
21
- return childLogger;
22
- }
23
- /**
24
- * Pino 兼容的 level getter/setter
25
- */
26
- get level() {
27
- return this.logger.level;
28
- }
29
- set level(newLevel) {
30
- // 过滤掉 'silent' 因为我们的 Logger 不支持它作为 level
31
- if (newLevel !== 'silent') {
32
- this.logger.level = newLevel;
33
- }
34
- }
35
- /**
36
- * 获取消息前缀
37
- */
38
- get msgPrefix() {
39
- return this._msgPrefix;
40
- }
41
- /**
42
- * 日志方法实现
43
- */
44
- fatal = (...args) => {
45
- this.logger.fatal(...args);
46
- };
47
- error = (...args) => {
48
- this.logger.error(...args);
49
- };
50
- warn = (...args) => {
51
- this.logger.warn(...args);
52
- };
53
- info = (...args) => {
54
- this.logger.info(...args);
55
- };
56
- debug = (...args) => {
57
- this.logger.debug(...args);
58
- };
59
- trace = (...args) => {
60
- this.logger.trace(...args);
61
- };
62
- silent = (...args) => {
63
- this.logger.silent();
64
- };
65
- }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=logger.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logger.test.d.ts","sourceRoot":"","sources":["../../source/logger/logger.test.ts"],"names":[],"mappings":""}