@taicode/common-server 3.0.1 → 3.1.0
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/output/index.d.ts +2 -2
- package/output/index.d.ts.map +1 -1
- package/output/index.js +2 -2
- package/output/redis-queue/index.d.ts +7 -7
- package/output/redis-queue/index.d.ts.map +1 -1
- package/output/redis-queue/index.js +4 -4
- package/output/redis-queue/redis-batch-consumer.d.ts +7 -11
- package/output/redis-queue/redis-batch-consumer.d.ts.map +1 -1
- package/output/redis-queue/redis-batch-consumer.js +54 -65
- package/output/redis-queue/redis-batch-consumer.test.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-batch-consumer.d.ts +3 -3
- package/output/redis-queue/redis-queue-batch-consumer.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-batch-consumer.js +19 -18
- package/output/redis-queue/redis-queue-batch-consumer.test.d.ts +1 -1
- package/output/redis-queue/redis-queue-batch-consumer.test.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-batch-consumer.test.js +3 -3
- package/output/redis-queue/redis-queue-common.d.ts +2 -2
- package/output/redis-queue/redis-queue-common.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-common.js +5 -4
- package/output/redis-queue/redis-queue-common.test.d.ts +1 -1
- package/output/redis-queue/redis-queue-common.test.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-common.test.js +1 -1
- package/output/redis-queue/redis-queue-consumer.d.ts +3 -3
- package/output/redis-queue/redis-queue-consumer.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-consumer.js +19 -18
- package/output/redis-queue/redis-queue-consumer.test.d.ts +1 -1
- package/output/redis-queue/redis-queue-consumer.test.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-consumer.test.js +3 -3
- package/output/redis-queue/redis-queue-provider.d.ts +2 -2
- package/output/redis-queue/redis-queue-provider.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-provider.js +6 -5
- package/output/redis-queue/redis-queue-provider.test.d.ts +1 -1
- package/output/redis-queue/redis-queue-provider.test.d.ts.map +1 -1
- package/output/redis-queue/redis-queue-provider.test.js +2 -2
- package/output/redis-queue/registry.d.ts +58 -0
- package/output/redis-queue/registry.d.ts.map +1 -0
- package/output/redis-queue/registry.js +31 -0
- package/output/redis-queue/test-helpers.d.ts +4 -4
- package/output/redis-queue/test-helpers.d.ts.map +1 -1
- package/output/redis-queue/test-helpers.js +1 -1
- package/output/redis-queue/test-helpers.test.d.ts +1 -1
- package/output/redis-queue/test-helpers.test.d.ts.map +1 -1
- package/output/redis-queue/test-helpers.test.js +4 -4
- package/output/redis-queue/types.d.ts +1 -1
- package/output/redis-queue/types.d.ts.map +1 -1
- package/output/schema/index.d.ts +1 -1
- package/output/schema/index.d.ts.map +1 -1
- package/output/schema/index.js +1 -1
- package/package.json +8 -6
package/output/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './schema';
|
|
2
|
-
export * from './redis-queue';
|
|
1
|
+
export * from './schema/index.js';
|
|
2
|
+
export * from './redis-queue/index.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/output/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":"AACA,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":"AACA,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA"}
|
package/output/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// 根 index.ts,统一导出 mock 目录下所有能力
|
|
2
|
-
export * from './schema';
|
|
3
|
-
export * from './redis-queue';
|
|
2
|
+
export * from './schema/index.js';
|
|
3
|
+
export * from './redis-queue/index.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { RedisQueueProvider } from './redis-queue-provider';
|
|
2
|
-
export { RedisQueueConsumer } from './redis-queue-consumer';
|
|
3
|
-
export { RedisQueueBatchConsumer } from './redis-queue-batch-consumer';
|
|
4
|
-
export type { RedisQueueProviderConfig, RedisQueueConsumerConfig, BatchConsumerConfig } from './types';
|
|
5
|
-
export type { TaskData } from './types';
|
|
6
|
-
export type { RedisQueueRegistry } from './types';
|
|
7
|
-
export * as test from './test-helpers';
|
|
1
|
+
export { RedisQueueProvider } from './redis-queue-provider.js';
|
|
2
|
+
export { RedisQueueConsumer } from './redis-queue-consumer.js';
|
|
3
|
+
export { RedisQueueBatchConsumer } from './redis-queue-batch-consumer.js';
|
|
4
|
+
export type { RedisQueueProviderConfig, RedisQueueConsumerConfig, BatchConsumerConfig } from './types.js';
|
|
5
|
+
export type { TaskData } from './types.js';
|
|
6
|
+
export type { RedisQueueRegistry } from './types.js';
|
|
7
|
+
export * as test from './test-helpers.js';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/redis-queue/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/redis-queue/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AAGzE,YAAY,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAGzG,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAG1C,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAGpD,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 核心类导出
|
|
2
|
-
export { RedisQueueProvider } from './redis-queue-provider';
|
|
3
|
-
export { RedisQueueConsumer } from './redis-queue-consumer';
|
|
4
|
-
export { RedisQueueBatchConsumer } from './redis-queue-batch-consumer';
|
|
2
|
+
export { RedisQueueProvider } from './redis-queue-provider.js';
|
|
3
|
+
export { RedisQueueConsumer } from './redis-queue-consumer.js';
|
|
4
|
+
export { RedisQueueBatchConsumer } from './redis-queue-batch-consumer.js';
|
|
5
5
|
// 测试辅助函数导出
|
|
6
|
-
export * as test from './test-helpers';
|
|
6
|
+
export * as test from './test-helpers.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RedisQueueCommon } from './redis-queue-common';
|
|
2
|
-
import type { BatchConsumerConfig, QueueStats,
|
|
2
|
+
import type { BatchConsumerConfig, QueueStats, InferTaskData } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Redis 批量队列消费者
|
|
5
5
|
*
|
|
@@ -32,7 +32,7 @@ import type { BatchConsumerConfig, QueueStats, RedisQueueRegistry } from './type
|
|
|
32
32
|
* consumer.disconnect()
|
|
33
33
|
* ```
|
|
34
34
|
*/
|
|
35
|
-
export declare class RedisBatchConsumer<K extends
|
|
35
|
+
export declare class RedisBatchConsumer<K extends string = string> extends RedisQueueCommon<InferTaskData<K>> {
|
|
36
36
|
private consumerRunning;
|
|
37
37
|
private consumerInterval;
|
|
38
38
|
private recoveryInterval;
|
|
@@ -48,6 +48,11 @@ export declare class RedisBatchConsumer<K extends keyof RedisQueueRegistry> exte
|
|
|
48
48
|
* 断开 Redis 连接并停止消费者
|
|
49
49
|
*/
|
|
50
50
|
disconnect(): void;
|
|
51
|
+
/**
|
|
52
|
+
* 恢复超时的任务
|
|
53
|
+
* 检查 processing 队列中的任务,将超时的任务直接标记为失败
|
|
54
|
+
*/
|
|
55
|
+
private recoverStalledTasks;
|
|
51
56
|
/**
|
|
52
57
|
* 批量处理任务
|
|
53
58
|
*/
|
|
@@ -76,14 +81,5 @@ export declare class RedisBatchConsumer<K extends keyof RedisQueueRegistry> exte
|
|
|
76
81
|
* 健康检查
|
|
77
82
|
*/
|
|
78
83
|
health(): Promise<boolean>;
|
|
79
|
-
/**
|
|
80
|
-
* 立即派发批量任务进行处理(仅供测试工具调用)
|
|
81
|
-
*
|
|
82
|
-
* ⚠️ 此方法为 private,仅通过 test-helpers 暴露给测试代码使用
|
|
83
|
-
*
|
|
84
|
-
* @param taskIds 要立即处理的任务 ID 列表
|
|
85
|
-
* @returns Promise,等待所有任务批次处理完成
|
|
86
|
-
*/
|
|
87
|
-
private dispatch;
|
|
88
84
|
}
|
|
89
85
|
//# sourceMappingURL=redis-batch-consumer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-batch-consumer.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-batch-consumer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"redis-batch-consumer.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-batch-consumer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,mBAAmB,EAA4C,UAAU,EAAE,aAAa,EAAY,MAAM,SAAS,CAAA;AAEjI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,kBAAkB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IACnG,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,iBAAiB,CAAI;IAE7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOtB;gBAEW,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;IA2B1C,SAAS,CAAC,YAAY,IAAI,MAAM;IAIhC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B;;OAEG;IACH,UAAU,IAAI,IAAI;IAMlB;;;OAGG;YACW,mBAAmB;IAwDjC;;OAEG;YACW,YAAY;IA4F1B;;OAEG;IACH,OAAO,CAAC,aAAa;IAoBrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAQpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAyErB;;OAEG;IACH,OAAO,CAAC,YAAY;IASpB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAevC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;CAMjC"}
|
|
@@ -82,6 +82,58 @@ export class RedisBatchConsumer extends RedisQueueCommon {
|
|
|
82
82
|
this.stopRecovery();
|
|
83
83
|
super.disconnect();
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* 恢复超时的任务
|
|
87
|
+
* 检查 processing 队列中的任务,将超时的任务直接标记为失败
|
|
88
|
+
*/
|
|
89
|
+
async recoverStalledTasks() {
|
|
90
|
+
if (!this.redis)
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
const processingTaskIds = await this.redis.lRange(this.processingQueue, 0, -1);
|
|
94
|
+
if (processingTaskIds.length === 0) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
let recoveredCount = 0;
|
|
99
|
+
for (const taskId of processingTaskIds) {
|
|
100
|
+
const task = await this.getTask(taskId);
|
|
101
|
+
if (!task) {
|
|
102
|
+
// 任务不存在,从队列中移除
|
|
103
|
+
await this.redis.lRem(this.processingQueue, 0, taskId);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
// 检查是否超时
|
|
107
|
+
const processingTime = now - (task.processingStartTime || now);
|
|
108
|
+
if (processingTime > this.config.processingTimeout) {
|
|
109
|
+
console.log(`[RedisBatchConsumer] Task timeout: ${taskId} (processing time: ${processingTime}ms)`);
|
|
110
|
+
const taskKey = `${this.queueKey}:task:${taskId}`;
|
|
111
|
+
// 超时直接标记为失败
|
|
112
|
+
task.status = 'failed';
|
|
113
|
+
task.processingStartTime = undefined;
|
|
114
|
+
// 使用 Lua 脚本确保原子性
|
|
115
|
+
const script = `
|
|
116
|
+
redis.call('SETEX', KEYS[1], ARGV[1], ARGV[2])
|
|
117
|
+
redis.call('LREM', KEYS[2], 0, ARGV[3])
|
|
118
|
+
redis.call('RPUSH', KEYS[3], ARGV[3])
|
|
119
|
+
return 1
|
|
120
|
+
`;
|
|
121
|
+
await this.redis.eval(script, {
|
|
122
|
+
keys: [taskKey, this.processingQueue, this.failedQueue],
|
|
123
|
+
arguments: [this.cleanupDelay.toString(), JSON.stringify(task), taskId],
|
|
124
|
+
});
|
|
125
|
+
console.error(`[RedisBatchConsumer] Task ${taskId} failed after timeout`);
|
|
126
|
+
recoveredCount++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (recoveredCount > 0) {
|
|
130
|
+
console.log(`[RedisBatchConsumer] Recovered ${recoveredCount} timeout tasks`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
console.error('[RedisBatchConsumer] Failed to recover stalled tasks:', error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
85
137
|
/**
|
|
86
138
|
* 批量处理任务
|
|
87
139
|
*/
|
|
@@ -178,12 +230,12 @@ export class RedisBatchConsumer extends RedisQueueCommon {
|
|
|
178
230
|
return;
|
|
179
231
|
}
|
|
180
232
|
// 立即执行一次恢复
|
|
181
|
-
this.
|
|
233
|
+
this.recoverStalledTasks().catch(error => {
|
|
182
234
|
console.error('[RedisBatchConsumer] Initial recovery error:', error);
|
|
183
235
|
});
|
|
184
236
|
// 定期检查(每 10 秒检查一次)
|
|
185
237
|
this.recoveryInterval = setInterval(() => {
|
|
186
|
-
this.
|
|
238
|
+
this.recoverStalledTasks().catch(error => {
|
|
187
239
|
console.error('[RedisBatchConsumer] Recovery error:', error);
|
|
188
240
|
});
|
|
189
241
|
}, 10000);
|
|
@@ -305,67 +357,4 @@ export class RedisBatchConsumer extends RedisQueueCommon {
|
|
|
305
357
|
const result = await catchIt(() => this.redis.ping());
|
|
306
358
|
return !result.isError() && result.value === 'PONG';
|
|
307
359
|
}
|
|
308
|
-
/**
|
|
309
|
-
* 立即派发批量任务进行处理(仅供测试工具调用)
|
|
310
|
-
*
|
|
311
|
-
* ⚠️ 此方法为 private,仅通过 test-helpers 暴露给测试代码使用
|
|
312
|
-
*
|
|
313
|
-
* @param taskIds 要立即处理的任务 ID 列表
|
|
314
|
-
* @returns Promise,等待所有任务批次处理完成
|
|
315
|
-
*/
|
|
316
|
-
async dispatch(taskIds) {
|
|
317
|
-
if (!this.redis || !this.redis.isOpen) {
|
|
318
|
-
throw new Error('[RedisBatchConsumer] Redis is not connected');
|
|
319
|
-
}
|
|
320
|
-
if (taskIds.length === 0) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
// 使用 Lua 脚本将任务从 pending 移到 processing(忽略延迟时间)
|
|
324
|
-
const moveToProcessingScript = `
|
|
325
|
-
local pendingQueue = KEYS[1]
|
|
326
|
-
local processingQueue = KEYS[2]
|
|
327
|
-
local movedIds = {}
|
|
328
|
-
|
|
329
|
-
-- ARGV[1], ARGV[2], ... 是 taskId
|
|
330
|
-
for i = 1, #ARGV do
|
|
331
|
-
local taskId = ARGV[i]
|
|
332
|
-
local removed = redis.call('LREM', pendingQueue, 0, taskId)
|
|
333
|
-
if removed > 0 then
|
|
334
|
-
redis.call('RPUSH', processingQueue, taskId)
|
|
335
|
-
table.insert(movedIds, taskId)
|
|
336
|
-
end
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
return movedIds
|
|
340
|
-
`;
|
|
341
|
-
const movedIds = await this.redis.eval(moveToProcessingScript, {
|
|
342
|
-
keys: [this.pendingQueue, this.processingQueue],
|
|
343
|
-
arguments: taskIds,
|
|
344
|
-
});
|
|
345
|
-
if (movedIds.length === 0) {
|
|
346
|
-
console.warn('[RedisBatchConsumer] No tasks were moved to processing queue');
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
// 按照 batchSize 分批处理并等待完成
|
|
350
|
-
const batches = [];
|
|
351
|
-
for (let i = 0; i < movedIds.length; i += this.config.batchSize) {
|
|
352
|
-
batches.push(movedIds.slice(i, i + this.config.batchSize));
|
|
353
|
-
}
|
|
354
|
-
// 串行处理每个批次(遵循批量消费语义)
|
|
355
|
-
for (const batch of batches) {
|
|
356
|
-
await this.processBatch(batch);
|
|
357
|
-
}
|
|
358
|
-
// 检查是否有任务需要重试(已放回 pending 队列)
|
|
359
|
-
const retryIds = [];
|
|
360
|
-
for (const taskId of movedIds) {
|
|
361
|
-
const task = await this.getTask(taskId);
|
|
362
|
-
if (task && task.status === 'pending' && task.retryCount > 0) {
|
|
363
|
-
retryIds.push(taskId);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
// 递归处理重试任务
|
|
367
|
-
if (retryIds.length > 0) {
|
|
368
|
-
await this.dispatch(retryIds);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
360
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-batch-consumer.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-batch-consumer.test.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redis-batch-consumer.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-batch-consumer.test.ts"],"names":[],"mappings":"AAMA,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,kBAAkB;QAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAClC;CACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RedisQueueCommon } from './redis-queue-common';
|
|
2
|
-
import type { BatchConsumerConfig, QueueStats, RedisQueueRegistry } from './types';
|
|
1
|
+
import { RedisQueueCommon } from './redis-queue-common.js';
|
|
2
|
+
import type { BatchConsumerConfig, QueueStats, RedisQueueRegistry } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Redis 批量队列消费者
|
|
5
5
|
*
|
|
@@ -32,7 +32,7 @@ import type { BatchConsumerConfig, QueueStats, RedisQueueRegistry } from './type
|
|
|
32
32
|
* batchSize: 50,
|
|
33
33
|
* handler: async (dataList) => {
|
|
34
34
|
* const total = dataList.reduce((sum, d) => sum + d.amount, 0)
|
|
35
|
-
*
|
|
35
|
+
* logger.info('Total amount:', total)
|
|
36
36
|
* return catchIt(() => {})
|
|
37
37
|
* }
|
|
38
38
|
* }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-queue-batch-consumer.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-batch-consumer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redis-queue-batch-consumer.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-batch-consumer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,mBAAmB,EAA0B,UAAU,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAE7G;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,qBAAa,uBAAuB,CAClC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,kBAAkB,EAClD,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAC7C,SAAQ,gBAAgB;IACxB,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,iBAAiB,CAAI;IAC7B,OAAO,CAAC,kBAAkB,CAA2B;IAErD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOtB;gBAEW,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAuB1D,SAAS,CAAC,YAAY,IAAI,MAAM;IAIhC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBjC;;OAEG;YACW,YAAY;IA2G1B;;OAEG;IACH,OAAO,CAAC,aAAa;IAoBrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAQpB;;OAEG;IACH,OAAO,CAAC,aAAa;IAyErB;;OAEG;IACH,OAAO,CAAC,YAAY;IASpB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAevC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;CAMjC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { catchIt } from '@taicode/common-base';
|
|
2
|
-
import {
|
|
2
|
+
import { logger } from '@taicode/common-base';
|
|
3
|
+
import { RedisQueueCommon } from './redis-queue-common.js';
|
|
3
4
|
/**
|
|
4
5
|
* Redis 批量队列消费者
|
|
5
6
|
*
|
|
@@ -32,7 +33,7 @@ import { RedisQueueCommon } from './redis-queue-common';
|
|
|
32
33
|
* batchSize: 50,
|
|
33
34
|
* handler: async (dataList) => {
|
|
34
35
|
* const total = dataList.reduce((sum, d) => sum + d.amount, 0)
|
|
35
|
-
*
|
|
36
|
+
* logger.info('Total amount:', total)
|
|
36
37
|
* return catchIt(() => {})
|
|
37
38
|
* }
|
|
38
39
|
* }
|
|
@@ -96,9 +97,9 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
96
97
|
this.stopRecovery();
|
|
97
98
|
// 等待所有正在处理的批次完成
|
|
98
99
|
if (this.processingPromises.size > 0) {
|
|
99
|
-
|
|
100
|
+
logger.info(`[RedisQueueBatchConsumer] Waiting for ${this.processingPromises.size} batches to complete...`);
|
|
100
101
|
await Promise.allSettled(this.processingPromises);
|
|
101
|
-
|
|
102
|
+
logger.info(`[RedisQueueBatchConsumer] All batches completed`);
|
|
102
103
|
}
|
|
103
104
|
// 最后断开 Redis 连接
|
|
104
105
|
super.disconnect();
|
|
@@ -115,7 +116,7 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
115
116
|
const tasks = await Promise.all(taskIds.map(id => this.getTask(id)));
|
|
116
117
|
const validTasks = tasks.filter((task) => task !== null);
|
|
117
118
|
if (validTasks.length === 0) {
|
|
118
|
-
|
|
119
|
+
logger.warn(`[RedisQueueBatchConsumer] No valid tasks found in batch`);
|
|
119
120
|
return;
|
|
120
121
|
}
|
|
121
122
|
// 过滤出有效的待处理任务
|
|
@@ -123,7 +124,7 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
123
124
|
for (const task of validTasks) {
|
|
124
125
|
// 检查状态
|
|
125
126
|
if (task.status !== 'pending') {
|
|
126
|
-
|
|
127
|
+
logger.info(`[RedisQueueBatchConsumer] Task ${task.id} has invalid status (${task.status}), marking as failed`);
|
|
127
128
|
await this.applyStatus(task.id, task.status, 'failed');
|
|
128
129
|
continue;
|
|
129
130
|
}
|
|
@@ -147,10 +148,10 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
147
148
|
await this.config.handler(dataList);
|
|
148
149
|
// 批量更新状态为完成
|
|
149
150
|
const updatedCount = await this.applyStatusBatch(taskIdList, 'processing', 'completed');
|
|
150
|
-
|
|
151
|
+
logger.info(`[RedisQueueBatchConsumer] Batch completed: ${updatedCount} tasks`);
|
|
151
152
|
}
|
|
152
153
|
catch (error) {
|
|
153
|
-
|
|
154
|
+
logger.error(`[RedisQueueBatchConsumer] Batch failed:`, error);
|
|
154
155
|
// 批量重试或失败
|
|
155
156
|
for (const task of tasksToProcess) {
|
|
156
157
|
if (task.retryCount < task.maxRetries) {
|
|
@@ -168,7 +169,7 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
168
169
|
keys: [taskKey, this.processingQueue, this.pendingQueue],
|
|
169
170
|
arguments: [this.cleanupDelay.toString(), JSON.stringify(task), task.id],
|
|
170
171
|
});
|
|
171
|
-
|
|
172
|
+
logger.info(`[RedisQueueBatchConsumer] Task ${task.id} will retry (${task.retryCount}/${task.maxRetries})`);
|
|
172
173
|
}
|
|
173
174
|
else {
|
|
174
175
|
task.status = 'failed';
|
|
@@ -184,7 +185,7 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
184
185
|
keys: [taskKey, this.processingQueue, this.failedQueue],
|
|
185
186
|
arguments: [this.cleanupDelay.toString(), JSON.stringify(task), task.id],
|
|
186
187
|
});
|
|
187
|
-
|
|
188
|
+
logger.error(`[RedisQueueBatchConsumer] Task ${task.id} failed after ${task.maxRetries} retries`);
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
}
|
|
@@ -211,15 +212,15 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
211
212
|
}
|
|
212
213
|
// 立即执行一次恢复
|
|
213
214
|
this.recoverStalled(this.config.processingTimeout).catch(error => {
|
|
214
|
-
|
|
215
|
+
logger.error('[RedisQueueBatchConsumer] Initial recovery error:', error);
|
|
215
216
|
});
|
|
216
217
|
// 定期检查(每 10 秒检查一次)
|
|
217
218
|
this.recoveryInterval = setInterval(() => {
|
|
218
219
|
this.recoverStalled(this.config.processingTimeout).catch(error => {
|
|
219
|
-
|
|
220
|
+
logger.error('[RedisQueueBatchConsumer] Recovery error:', error);
|
|
220
221
|
});
|
|
221
222
|
}, 10000);
|
|
222
|
-
|
|
223
|
+
logger.info('[RedisQueueBatchConsumer] Recovery mechanism started');
|
|
223
224
|
}
|
|
224
225
|
/**
|
|
225
226
|
* 停止恢复机制
|
|
@@ -228,7 +229,7 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
228
229
|
if (this.recoveryInterval) {
|
|
229
230
|
clearInterval(this.recoveryInterval);
|
|
230
231
|
this.recoveryInterval = null;
|
|
231
|
-
|
|
232
|
+
logger.info('[RedisQueueBatchConsumer] Recovery mechanism stopped');
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
/**
|
|
@@ -239,7 +240,7 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
239
240
|
return;
|
|
240
241
|
}
|
|
241
242
|
this.consumerRunning = true;
|
|
242
|
-
|
|
243
|
+
logger.info(`[RedisQueueBatchConsumer] Consumer started with batchSize: ${this.config.batchSize}, concurrency: ${this.config.concurrency}`);
|
|
243
244
|
// Lua 脚本: 批量原子化从 pending 取出任务并移到 processing
|
|
244
245
|
const popAndMoveBatchScript = `
|
|
245
246
|
local pendingQueue = KEYS[1]
|
|
@@ -293,12 +294,12 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
293
294
|
if (taskIds.length > 0) {
|
|
294
295
|
// 处理这批任务(不等待完成)
|
|
295
296
|
this.processBatch(taskIds).catch(error => {
|
|
296
|
-
|
|
297
|
+
logger.error(`[RedisQueueBatchConsumer] Unhandled error in processBatch:`, error);
|
|
297
298
|
});
|
|
298
299
|
}
|
|
299
300
|
}
|
|
300
301
|
catch (error) {
|
|
301
|
-
|
|
302
|
+
logger.error('[RedisQueueBatchConsumer] Consumer error:', error);
|
|
302
303
|
}
|
|
303
304
|
}, this.config.consumerInterval);
|
|
304
305
|
}
|
|
@@ -311,7 +312,7 @@ export class RedisQueueBatchConsumer extends RedisQueueCommon {
|
|
|
311
312
|
this.consumerInterval = null;
|
|
312
313
|
}
|
|
313
314
|
this.consumerRunning = false;
|
|
314
|
-
|
|
315
|
+
logger.info('[RedisQueueBatchConsumer] Consumer stopped');
|
|
315
316
|
}
|
|
316
317
|
/**
|
|
317
318
|
* 获取队列统计信息(O(1) 时间复杂度)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-queue-batch-consumer.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-batch-consumer.test.ts"],"names":[],"mappings":"AAQA,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,GAAG,CAAA;CACX,CAAA;AAGD,OAAO,QAAQ,
|
|
1
|
+
{"version":3,"file":"redis-queue-batch-consumer.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-batch-consumer.test.ts"],"names":[],"mappings":"AAQA,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,GAAG,CAAA;CACX,CAAA;AAGD,OAAO,QAAQ,YAAY,CAAC;IAC1B,UAAU,kBAAkB;QAC1B,CAAC,GAAG,EAAE,cAAc,MAAM,EAAE,GAAG,YAAY,CAAA;KAC5C;CACF"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
2
|
import { catchIt } from '@taicode/common-base';
|
|
3
|
-
import { RedisQueueProvider } from './redis-queue-provider';
|
|
4
|
-
import { RedisQueueBatchConsumer } from './redis-queue-batch-consumer';
|
|
5
|
-
import { dispatchQueueTask, waitQueueCompletion, clearQueue, getQueueTasks } from './test-helpers';
|
|
3
|
+
import { RedisQueueProvider } from './redis-queue-provider.js';
|
|
4
|
+
import { RedisQueueBatchConsumer } from './redis-queue-batch-consumer.js';
|
|
5
|
+
import { dispatchQueueTask, waitQueueCompletion, clearQueue, getQueueTasks } from './test-helpers.js';
|
|
6
6
|
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
7
7
|
describe('RedisQueueBatchConsumer', () => {
|
|
8
8
|
const providers = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RedisClientType } from 'redis';
|
|
2
|
-
import type { Status, Task, RedisQueueCommonConfig } from './types';
|
|
1
|
+
import { type RedisClientType } from 'redis';
|
|
2
|
+
import type { Status, Task, RedisQueueCommonConfig } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Redis 队列基础类
|
|
5
5
|
* 封装 Provider 和 Consumer 的公共逻辑
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-queue-common.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,eAAe,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"redis-queue-common.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-common.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,OAAO,CAAA;AAE1D,OAAO,KAAK,EAAE,MAAM,EAAY,IAAI,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAEhF;;;GAGG;AACH,8BAAsB,gBAAgB;IACpC,SAAS,CAAC,KAAK,EAAE,eAAe,GAAG,IAAI,CAAO;IAC9C,SAAS,CAAC,eAAe,EAAE,OAAO,CAAQ;IAE1C,SAAS,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACnC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACpC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAGvC,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IACtC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IACvC,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAC1C,SAAS,CAAC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;gBAE7B,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAE,sBAA2B;IAwCjE;;OAEG;IACH,SAAS,CAAC,QAAQ,CAAC,YAAY,IAAI,MAAM;IAEzC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAS9B;;OAEG;IACH,UAAU,IAAI,IAAI;IAalB;;OAEG;cACa,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAW7D;;OAEG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM;IAUlD;;OAEG;cACa,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqChG;;;OAGG;cACa,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqD1G;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAevG;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;IAUhC;;;;;;;OAOG;cACa,cAAc,CAAC,iBAAiB,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CA4E/G"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createClient } from 'redis';
|
|
2
|
+
import { logger } from '@taicode/common-base';
|
|
2
3
|
/**
|
|
3
4
|
* Redis 队列基础类
|
|
4
5
|
* 封装 Provider 和 Consumer 的公共逻辑
|
|
@@ -46,7 +47,7 @@ export class RedisQueueCommon {
|
|
|
46
47
|
this.isExternalRedis = false;
|
|
47
48
|
// 添加错误处理
|
|
48
49
|
this.redis.on('error', (err) => {
|
|
49
|
-
|
|
50
|
+
logger.error(`[${this.getLogPrefix()}] Redis Client Error:`, err);
|
|
50
51
|
});
|
|
51
52
|
}
|
|
52
53
|
}
|
|
@@ -56,7 +57,7 @@ export class RedisQueueCommon {
|
|
|
56
57
|
async connect() {
|
|
57
58
|
if (this.redis && !this.redis.isOpen) {
|
|
58
59
|
await this.redis.connect().catch((error) => {
|
|
59
|
-
|
|
60
|
+
logger.error(`[${this.getLogPrefix()}] Failed to connect to Redis:`, error);
|
|
60
61
|
throw error;
|
|
61
62
|
});
|
|
62
63
|
}
|
|
@@ -71,7 +72,7 @@ export class RedisQueueCommon {
|
|
|
71
72
|
}
|
|
72
73
|
if (this.redis && this.redis.isOpen) {
|
|
73
74
|
this.redis.disconnect().catch((error) => {
|
|
74
|
-
|
|
75
|
+
logger.error(`[${this.getLogPrefix()}] Failed to disconnect:`, error);
|
|
75
76
|
});
|
|
76
77
|
}
|
|
77
78
|
}
|
|
@@ -295,7 +296,7 @@ export class RedisQueueCommon {
|
|
|
295
296
|
}
|
|
296
297
|
catch (error) {
|
|
297
298
|
const logPrefix = this.getLogPrefix();
|
|
298
|
-
|
|
299
|
+
logger.error(`[${logPrefix}] Failed to recover stalled tasks:`, error);
|
|
299
300
|
return { timeoutFailed: 0, cleaned: 0 };
|
|
300
301
|
}
|
|
301
302
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-queue-common.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-common.test.ts"],"names":[],"mappings":"AAOA,OAAO,QAAQ,
|
|
1
|
+
{"version":3,"file":"redis-queue-common.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-common.test.ts"],"names":[],"mappings":"AAOA,OAAO,QAAQ,YAAY,CAAC;IAC1B,UAAU,kBAAkB;QAC1B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QACjC,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC5B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAChC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QACnC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAClC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QACvB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC/B,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QACjC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAC/B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAChC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;QAChC,CAAC,GAAG,EAAE,kBAAkB,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KACvD;CACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { RedisQueueCommon } from './redis-queue-common';
|
|
2
|
+
import { RedisQueueCommon } from './redis-queue-common.js';
|
|
3
3
|
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
4
4
|
// 创建一个具体的测试类来测试抽象基类
|
|
5
5
|
class TestRedisQueueCommon extends RedisQueueCommon {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RedisQueueCommon } from './redis-queue-common';
|
|
2
|
-
import type { RedisQueueConsumerConfig, RedisQueueRegistry, QueueStats } from './types';
|
|
1
|
+
import { RedisQueueCommon } from './redis-queue-common.js';
|
|
2
|
+
import type { RedisQueueConsumerConfig, RedisQueueRegistry, QueueStats } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Redis 队列消费者
|
|
5
5
|
*
|
|
@@ -30,7 +30,7 @@ import type { RedisQueueConsumerConfig, RedisQueueRegistry, QueueStats } from '.
|
|
|
30
30
|
* {
|
|
31
31
|
* redisUrl: 'redis://localhost:6379',
|
|
32
32
|
* handler: async (data) => {
|
|
33
|
-
*
|
|
33
|
+
* logger.info(data.orderId, data.amount)
|
|
34
34
|
* return catchIt(() => {})
|
|
35
35
|
* }
|
|
36
36
|
* }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-queue-consumer.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-consumer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redis-queue-consumer.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-consumer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,wBAAwB,EAAe,kBAAkB,EAAgB,UAAU,EAAE,MAAM,YAAY,CAAA;AAErH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,qBAAa,kBAAkB,CAC7B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,kBAAkB,EAClD,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAC7C,SAAQ,gBAAgB;IACxB,OAAO,CAAC,eAAe,CAAQ;IAC/B,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,eAAe,CAAI;IAC3B,OAAO,CAAC,kBAAkB,CAA2B;IAErD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAOtB;gBAEW,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,wBAAwB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAuB/D,SAAS,CAAC,YAAY,IAAI,MAAM;IAIhC;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAO9B;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBjC;;OAEG;YACW,WAAW;IA2FzB;;OAEG;IACH,OAAO,CAAC,aAAa;IAoBrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAQpB;;OAEG;IACH,OAAO,CAAC,aAAa;IA4ErB;;OAEG;IACH,OAAO,CAAC,YAAY;IASpB;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAevC;;OAEG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;CAMjC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { catchIt } from '@taicode/common-base';
|
|
2
|
-
import {
|
|
2
|
+
import { logger } from '@taicode/common-base';
|
|
3
|
+
import { RedisQueueCommon } from './redis-queue-common.js';
|
|
3
4
|
/**
|
|
4
5
|
* Redis 队列消费者
|
|
5
6
|
*
|
|
@@ -30,7 +31,7 @@ import { RedisQueueCommon } from './redis-queue-common';
|
|
|
30
31
|
* {
|
|
31
32
|
* redisUrl: 'redis://localhost:6379',
|
|
32
33
|
* handler: async (data) => {
|
|
33
|
-
*
|
|
34
|
+
* logger.info(data.orderId, data.amount)
|
|
34
35
|
* return catchIt(() => {})
|
|
35
36
|
* }
|
|
36
37
|
* }
|
|
@@ -94,9 +95,9 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
94
95
|
this.stopRecovery();
|
|
95
96
|
// 等待所有正在处理的任务完成
|
|
96
97
|
if (this.processingPromises.size > 0) {
|
|
97
|
-
|
|
98
|
+
logger.info(`[RedisQueueConsumer] Waiting for ${this.processingPromises.size} tasks to complete...`);
|
|
98
99
|
await Promise.allSettled(this.processingPromises);
|
|
99
|
-
|
|
100
|
+
logger.info(`[RedisQueueConsumer] All tasks completed`);
|
|
100
101
|
}
|
|
101
102
|
// 最后断开 Redis 连接
|
|
102
103
|
super.disconnect();
|
|
@@ -111,12 +112,12 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
111
112
|
try {
|
|
112
113
|
const task = await this.getTask(taskId);
|
|
113
114
|
if (!task) {
|
|
114
|
-
|
|
115
|
+
logger.warn(`[RedisQueueConsumer] Task not found: ${taskId}`);
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
118
|
// 任务应该是 pending 状态(Lua 脚本已确保延迟检查)
|
|
118
119
|
if (task.status !== 'pending') {
|
|
119
|
-
|
|
120
|
+
logger.info(`[RedisQueueConsumer] Task ${taskId} has invalid status (${task.status}), marking as failed`);
|
|
120
121
|
await this.applyStatus(taskId, task.status, 'failed');
|
|
121
122
|
return;
|
|
122
123
|
}
|
|
@@ -130,10 +131,10 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
130
131
|
await this.config.handler(task.data);
|
|
131
132
|
// 更新状态为完成
|
|
132
133
|
await this.applyStatus(taskId, 'processing', 'completed');
|
|
133
|
-
|
|
134
|
+
logger.info(`[RedisQueueConsumer] Task completed: ${taskId}`);
|
|
134
135
|
}
|
|
135
136
|
catch (error) {
|
|
136
|
-
|
|
137
|
+
logger.error(`[RedisQueueConsumer] Task failed: ${taskId}`, error);
|
|
137
138
|
// 检查是否需要重试
|
|
138
139
|
if (task.retryCount < task.maxRetries) {
|
|
139
140
|
task.retryCount++;
|
|
@@ -151,7 +152,7 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
151
152
|
keys: [taskKey, this.processingQueue, this.pendingQueue],
|
|
152
153
|
arguments: [this.config.cleanupDelay.toString(), JSON.stringify(task), taskId],
|
|
153
154
|
});
|
|
154
|
-
|
|
155
|
+
logger.info(`[RedisQueueConsumer] Task ${taskId} will retry (${task.retryCount}/${task.maxRetries})`);
|
|
155
156
|
}
|
|
156
157
|
else {
|
|
157
158
|
task.status = 'failed';
|
|
@@ -168,7 +169,7 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
168
169
|
keys: [taskKey, this.processingQueue, this.failedQueue],
|
|
169
170
|
arguments: [this.config.cleanupDelay.toString(), JSON.stringify(task), taskId],
|
|
170
171
|
});
|
|
171
|
-
|
|
172
|
+
logger.error(`[RedisQueueConsumer] Task ${taskId} failed after ${task.maxRetries} retries`);
|
|
172
173
|
}
|
|
173
174
|
}
|
|
174
175
|
}
|
|
@@ -194,15 +195,15 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
194
195
|
}
|
|
195
196
|
// 立即执行一次恢复
|
|
196
197
|
this.recoverStalled(this.config.processingTimeout).catch(error => {
|
|
197
|
-
|
|
198
|
+
logger.error('[RedisQueueConsumer] Initial recovery error:', error);
|
|
198
199
|
});
|
|
199
200
|
// 定期检查(每 10 秒检查一次)
|
|
200
201
|
this.recoveryInterval = setInterval(() => {
|
|
201
202
|
this.recoverStalled(this.config.processingTimeout).catch(error => {
|
|
202
|
-
|
|
203
|
+
logger.error('[RedisQueueConsumer] Recovery error:', error);
|
|
203
204
|
});
|
|
204
205
|
}, 10000);
|
|
205
|
-
|
|
206
|
+
logger.info('[RedisQueueConsumer] Recovery mechanism started');
|
|
206
207
|
}
|
|
207
208
|
/**
|
|
208
209
|
* 停止恢复机制(内部方法,自动调用)
|
|
@@ -211,7 +212,7 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
211
212
|
if (this.recoveryInterval) {
|
|
212
213
|
clearInterval(this.recoveryInterval);
|
|
213
214
|
this.recoveryInterval = null;
|
|
214
|
-
|
|
215
|
+
logger.info('[RedisQueueConsumer] Recovery mechanism stopped');
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
218
|
/**
|
|
@@ -222,7 +223,7 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
222
223
|
return;
|
|
223
224
|
}
|
|
224
225
|
this.consumerRunning = true;
|
|
225
|
-
|
|
226
|
+
logger.info(`[RedisQueueConsumer] Consumer started with concurrency: ${this.config.concurrency}`);
|
|
226
227
|
// Lua 脚本: 原子化从 pending 取出任务并移到 processing(只取已到延迟时间的任务)
|
|
227
228
|
const popAndMoveScript = `
|
|
228
229
|
local pendingQueue = KEYS[1]
|
|
@@ -279,12 +280,12 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
279
280
|
// 并发处理所有任务(不等待完成)
|
|
280
281
|
taskIds.forEach(taskId => {
|
|
281
282
|
this.processTask(taskId).catch(error => {
|
|
282
|
-
|
|
283
|
+
logger.error(`[RedisQueueConsumer] Unhandled error in processTask: ${taskId}`, error);
|
|
283
284
|
});
|
|
284
285
|
});
|
|
285
286
|
}
|
|
286
287
|
catch (error) {
|
|
287
|
-
|
|
288
|
+
logger.error('[RedisQueueConsumer] Consumer error:', error);
|
|
288
289
|
}
|
|
289
290
|
}, this.config.consumerInterval);
|
|
290
291
|
}
|
|
@@ -297,7 +298,7 @@ export class RedisQueueConsumer extends RedisQueueCommon {
|
|
|
297
298
|
this.consumerInterval = null;
|
|
298
299
|
}
|
|
299
300
|
this.consumerRunning = false;
|
|
300
|
-
|
|
301
|
+
logger.info('[RedisQueueConsumer] Consumer stopped');
|
|
301
302
|
}
|
|
302
303
|
/**
|
|
303
304
|
* 获取队列统计信息(O(1) 时间复杂度)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-queue-consumer.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-consumer.test.ts"],"names":[],"mappings":"AAQA,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,GAAG,CAAA;CACX,CAAA;AAGD,OAAO,QAAQ,
|
|
1
|
+
{"version":3,"file":"redis-queue-consumer.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-consumer.test.ts"],"names":[],"mappings":"AAQA,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,GAAG,CAAA;CACX,CAAA;AAGD,OAAO,QAAQ,YAAY,CAAC;IAC1B,UAAU,kBAAkB;QAC1B,CAAC,GAAG,EAAE,cAAc,MAAM,EAAE,GAAG,YAAY,CAAA;KAC5C;CACF"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
2
|
import { catchIt } from '@taicode/common-base';
|
|
3
|
-
import { RedisQueueProvider } from './redis-queue-provider';
|
|
4
|
-
import { RedisQueueConsumer } from './redis-queue-consumer';
|
|
5
|
-
import { dispatchQueueTask, waitQueueCompletion, clearQueue, getQueueTasks } from './test-helpers';
|
|
3
|
+
import { RedisQueueProvider } from './redis-queue-provider.js';
|
|
4
|
+
import { RedisQueueConsumer } from './redis-queue-consumer.js';
|
|
5
|
+
import { dispatchQueueTask, waitQueueCompletion, clearQueue, getQueueTasks } from './test-helpers.js';
|
|
6
6
|
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
7
7
|
describe('RedisQueueConsumer', () => {
|
|
8
8
|
const providers = [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { RedisQueueCommon } from './redis-queue-common';
|
|
2
|
-
import type { RedisQueueRegistry, RedisQueueProviderConfig, Task, QueueStats } from './types';
|
|
1
|
+
import { RedisQueueCommon } from './redis-queue-common.js';
|
|
2
|
+
import type { RedisQueueRegistry, RedisQueueProviderConfig, Task, QueueStats } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Redis 队列生产者
|
|
5
5
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-queue-provider.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-provider.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redis-queue-provider.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-provider.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEhG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,qBAAa,kBAAkB,CAC7B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,kBAAkB,EAClD,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAC7C,SAAQ,gBAAgB;IACxB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAQ;gBAE5B,QAAQ,EAAE,CAAC,EAAE,MAAM,GAAE,wBAA6B;IAU9D,SAAS,CAAC,YAAY,IAAI,MAAM;IAMhC;;;;;;;;;;;OAWG;IACG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IACxC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IA0F1C;;OAEG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAWzD;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,UAAU,CAAC;IAevC;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAmD7B"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import { logger } from '@taicode/common-base';
|
|
3
|
+
import { RedisQueueCommon } from './redis-queue-common.js';
|
|
3
4
|
/**
|
|
4
5
|
* Redis 队列生产者
|
|
5
6
|
*
|
|
@@ -49,7 +50,7 @@ export class RedisQueueProvider extends RedisQueueCommon {
|
|
|
49
50
|
}
|
|
50
51
|
async enqueue(data) {
|
|
51
52
|
if (!this.redis) {
|
|
52
|
-
|
|
53
|
+
logger.warn('[RedisQueueProvider] Redis not available, skipping task enqueue');
|
|
53
54
|
return Array.isArray(data) ? [] : '';
|
|
54
55
|
}
|
|
55
56
|
// 统一处理为数组
|
|
@@ -114,10 +115,10 @@ export class RedisQueueProvider extends RedisQueueCommon {
|
|
|
114
115
|
arguments: args,
|
|
115
116
|
});
|
|
116
117
|
if (successCount < tasks.length) {
|
|
117
|
-
|
|
118
|
+
logger.info(`[RedisQueueProvider] ${tasks.length - successCount} tasks already exist, skipped`);
|
|
118
119
|
}
|
|
119
120
|
if (successCount > 0) {
|
|
120
|
-
|
|
121
|
+
logger.info(`[RedisQueueProvider] Enqueued ${successCount} tasks`);
|
|
121
122
|
}
|
|
122
123
|
// 返回单个 ID 或 ID 数组
|
|
123
124
|
return Array.isArray(data) ? taskIds : taskIds[0];
|
|
@@ -198,7 +199,7 @@ export class RedisQueueProvider extends RedisQueueCommon {
|
|
|
198
199
|
arguments: [this.queueKey],
|
|
199
200
|
});
|
|
200
201
|
if (deletedCount > 0) {
|
|
201
|
-
|
|
202
|
+
logger.info(`[RedisQueueProvider] Cleared ${deletedCount} tasks`);
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
205
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-queue-provider.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-provider.test.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,GAAG,CAAA;CACX,CAAA;AAGD,OAAO,QAAQ,
|
|
1
|
+
{"version":3,"file":"redis-queue-provider.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/redis-queue-provider.test.ts"],"names":[],"mappings":"AAMA,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,GAAG,CAAA;CACX,CAAA;AAGD,OAAO,QAAQ,YAAY,CAAC;IAC1B,UAAU,kBAAkB;QAC1B,CAAC,GAAG,EAAE,iBAAiB,MAAM,EAAE,GAAG,YAAY,CAAA;KAC/C;CACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
-
import { RedisQueueProvider } from "./redis-queue-provider";
|
|
3
|
-
import { clearQueue, getQueueTasks } from "./test-helpers";
|
|
2
|
+
import { RedisQueueProvider } from "./redis-queue-provider.js";
|
|
3
|
+
import { clearQueue, getQueueTasks } from "./test-helpers.js";
|
|
4
4
|
const REDIS_URL = process.env.REDIS_URL || "redis://localhost:6379";
|
|
5
5
|
describe("RedisQueueProvider", () => {
|
|
6
6
|
const providers = [];
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局队列注册表
|
|
3
|
+
*
|
|
4
|
+
* 使用 interface 合并模式来定义队列键和对应的任务数据类型
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // 在项目中声明队列类型
|
|
9
|
+
* declare module '@taicode/common-server' {
|
|
10
|
+
* interface RedisQueueRegistry {
|
|
11
|
+
* 'email-queue': { to: string; subject: string; body: string }
|
|
12
|
+
* 'sms-queue': { phone: string; message: string }
|
|
13
|
+
* 'payment-queue': { orderId: string; amount: number }
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* // 使用时会有完整的类型提示和检查
|
|
18
|
+
* const provider = new RedisQueueProvider({
|
|
19
|
+
* redisUrl: 'redis://localhost:6379',
|
|
20
|
+
* queueKey: 'email-queue', // 类型安全:只能使用注册的键
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* // 自动推断任务数据类型
|
|
24
|
+
* await provider.enqueue({
|
|
25
|
+
* to: 'user@example.com', // 类型检查
|
|
26
|
+
* subject: 'Hello',
|
|
27
|
+
* body: 'World'
|
|
28
|
+
* })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* 队列注册表接口
|
|
33
|
+
*
|
|
34
|
+
* 通过 interface 合并机制,允许在项目中扩展队列定义
|
|
35
|
+
*/
|
|
36
|
+
export interface RedisQueueRegistry {
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 从注册表中提取队列键类型
|
|
40
|
+
*/
|
|
41
|
+
export type RegisteredRedisQueueKey = keyof RedisQueueRegistry;
|
|
42
|
+
/**
|
|
43
|
+
* 根据队列键获取对应的任务数据类型
|
|
44
|
+
*/
|
|
45
|
+
export type RedisQueueTaskData<K extends RegisteredRedisQueueKey> = RedisQueueRegistry[K];
|
|
46
|
+
/**
|
|
47
|
+
* 类型安全的队列键
|
|
48
|
+
* 如果注册表为空,允许使用任意字符串;否则只允许注册的键
|
|
49
|
+
*/
|
|
50
|
+
export type SafeRedisQueueKey<K extends string = string> = RegisteredRedisQueueKey extends never ? K : K extends RegisteredRedisQueueKey ? K : never;
|
|
51
|
+
/**
|
|
52
|
+
* 根据队列键推断任务数据类型
|
|
53
|
+
*
|
|
54
|
+
* - 如果键在注册表中,使用注册表中的类型
|
|
55
|
+
* - 否则使用泛型参数 T
|
|
56
|
+
*/
|
|
57
|
+
export type InferRedisQueueTaskData<K extends string, T extends Record<string, unknown> = Record<string, unknown>> = K extends RegisteredRedisQueueKey ? RedisQueueTaskData<K> : T;
|
|
58
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../source/redis-queue/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH;;;;GAIG;AAEH,MAAM,WAAW,kBAAkB;CAKlC;AAED;;GAEG;AACH,MAAM,MAAM,uBAAuB,GAAG,MAAM,kBAAkB,CAAA;AAE9D;;GAEG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,uBAAuB,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAA;AAEzF;;;GAGG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,IACrD,uBAAuB,SAAS,KAAK,GACjC,CAAC,GACD,CAAC,SAAS,uBAAuB,GAC/B,CAAC,GACD,KAAK,CAAA;AAEb;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,CACjC,CAAC,SAAS,MAAM,EAChB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACzD,CAAC,SAAS,uBAAuB,GACjC,kBAAkB,CAAC,CAAC,CAAC,GACrB,CAAC,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局队列注册表
|
|
3
|
+
*
|
|
4
|
+
* 使用 interface 合并模式来定义队列键和对应的任务数据类型
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // 在项目中声明队列类型
|
|
9
|
+
* declare module '@taicode/common-server' {
|
|
10
|
+
* interface RedisQueueRegistry {
|
|
11
|
+
* 'email-queue': { to: string; subject: string; body: string }
|
|
12
|
+
* 'sms-queue': { phone: string; message: string }
|
|
13
|
+
* 'payment-queue': { orderId: string; amount: number }
|
|
14
|
+
* }
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* // 使用时会有完整的类型提示和检查
|
|
18
|
+
* const provider = new RedisQueueProvider({
|
|
19
|
+
* redisUrl: 'redis://localhost:6379',
|
|
20
|
+
* queueKey: 'email-queue', // 类型安全:只能使用注册的键
|
|
21
|
+
* })
|
|
22
|
+
*
|
|
23
|
+
* // 自动推断任务数据类型
|
|
24
|
+
* await provider.enqueue({
|
|
25
|
+
* to: 'user@example.com', // 类型检查
|
|
26
|
+
* subject: 'Hello',
|
|
27
|
+
* body: 'World'
|
|
28
|
+
* })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { RedisQueueBatchConsumer } from './redis-queue-batch-consumer';
|
|
2
|
-
import { RedisQueueConsumer } from './redis-queue-consumer';
|
|
3
|
-
import type { RedisQueueProvider } from './redis-queue-provider';
|
|
4
|
-
import type { RedisQueueRegistry, Task } from './types';
|
|
1
|
+
import type { RedisQueueBatchConsumer } from './redis-queue-batch-consumer.js';
|
|
2
|
+
import { RedisQueueConsumer } from './redis-queue-consumer.js';
|
|
3
|
+
import type { RedisQueueProvider } from './redis-queue-provider.js';
|
|
4
|
+
import type { RedisQueueRegistry, Task } from './types.js';
|
|
5
5
|
/**
|
|
6
6
|
* 测试辅助函数:立即派发任务到消费者
|
|
7
7
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../source/redis-queue/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../source/redis-queue/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAA;AAC9E,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,KAAK,EAAE,kBAAkB,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAE1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,iBAAiB,CACrC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,kBAAkB,EAClD,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,EAE7C,QAAQ,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC,EAAE,CAAC,CAAC,EAClE,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,IAAI,CAAC,CA+Ff;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,kBAAkB,EAClD,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,EAE7C,QAAQ,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC,EAAE,CAAC,CAAC,EAC7F,SAAS,EAAE,CAAC,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,EACzG,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,UAAU,CAC9B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,kBAAkB,EAClD,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,EAE7C,QAAQ,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,uBAAuB,CAAC,CAAC,EAAE,CAAC,CAAC,GAC5F,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,aAAa,CACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,kBAAkB,EAClD,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,EAE7C,QAAQ,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,EAClC,MAAM,CAAC,EAAE,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,GACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAmCvB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-helpers.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/test-helpers.test.ts"],"names":[],"mappings":"AAcA,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAGD,OAAO,QAAQ,
|
|
1
|
+
{"version":3,"file":"test-helpers.test.d.ts","sourceRoot":"","sources":["../../source/redis-queue/test-helpers.test.ts"],"names":[],"mappings":"AAcA,KAAK,YAAY,GAAG;IAClB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IACvB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,MAAM,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAGD,OAAO,QAAQ,YAAY,CAAC;IAC1B,UAAU,kBAAkB;QAC1B,CAAC,GAAG,EAAE,QAAQ,MAAM,EAAE,GAAG,YAAY,CAAA;KACtC;CACF"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { catchIt } from '@taicode/common-base';
|
|
3
|
-
import { RedisQueueProvider } from './redis-queue-provider';
|
|
4
|
-
import { RedisQueueConsumer } from './redis-queue-consumer';
|
|
5
|
-
import { RedisQueueBatchConsumer } from './redis-queue-batch-consumer';
|
|
6
|
-
import { dispatchQueueTask, waitQueueCompletion, clearQueue, getQueueTasks, } from './test-helpers';
|
|
3
|
+
import { RedisQueueProvider } from './redis-queue-provider.js';
|
|
4
|
+
import { RedisQueueConsumer } from './redis-queue-consumer.js';
|
|
5
|
+
import { RedisQueueBatchConsumer } from './redis-queue-batch-consumer.js';
|
|
6
|
+
import { dispatchQueueTask, waitQueueCompletion, clearQueue, getQueueTasks, } from './test-helpers.js';
|
|
7
7
|
const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
8
8
|
/**
|
|
9
9
|
* 测试辅助方法使用示例
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/redis-queue/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/redis-queue/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AAEH,MAAM,WAAW,kBAAkB;CAElC;AAED;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAA;AAEtE;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAA;AAE1D;;GAEG;AACH,MAAM,WAAW,IAAI,CAAC,CAAC,GAAG,QAAQ;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,CAAC,CAAA;IACP,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;AAE3G;;GAEG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA"}
|
package/output/schema/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../source/schema/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AA2BzC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;;;;;;;;;;;;;kBAMlF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;;;kBAKxD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,kCAE/B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;;;kBAKvD;AAED;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;;;;;;;;;;kBAMvF;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;;;mBAK7D;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;;;;;;;;;;;kBAMjE;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;;;mBAKjE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;;;;;;;;;;;;;kBAMtF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC;;;kBAK5D;AAGD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;;;;kBAMlF;AAED,wBAAgB,cAAc,CAAC,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,OAAO,EAAE,CAAC,SAAS,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;;;;;;;;;;;;;;;;;;EAMvG;AAED,UAAU,YAAa,SAAQ,aAAa;IAC1C,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,YAAY,EAAE,MAAM,EAAE,CAAC,GAAG,YAAY,GAAG,CAAC,GAAG,YAAY,CAY/F"}
|
package/output/schema/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taicode/common-server",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"author": "Alain",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"description": "",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=16.0.0"
|
|
10
|
+
},
|
|
7
11
|
"main": "./output/index.js",
|
|
8
12
|
"types": "./output/index.d.ts",
|
|
9
13
|
"exports": {
|
|
10
14
|
".": {
|
|
11
15
|
"types": "./output/index.d.ts",
|
|
12
|
-
"
|
|
13
|
-
"require": "./output/index.js"
|
|
16
|
+
"default": "./output/index.js"
|
|
14
17
|
},
|
|
15
18
|
"./*": {
|
|
16
19
|
"types": "./output/*/index.d.ts",
|
|
17
|
-
"
|
|
18
|
-
"require": "./output/*/index.js"
|
|
20
|
+
"default": "./output/*/index.js"
|
|
19
21
|
}
|
|
20
22
|
},
|
|
21
23
|
"scripts": {
|
|
@@ -28,7 +30,7 @@
|
|
|
28
30
|
"output"
|
|
29
31
|
],
|
|
30
32
|
"peerDependencies": {
|
|
31
|
-
"@taicode/common-base": "^3.0
|
|
33
|
+
"@taicode/common-base": "^3.1.0",
|
|
32
34
|
"fastify": "^5.5.0",
|
|
33
35
|
"redis": "^5.10.0",
|
|
34
36
|
"zod": "^4.1.0"
|