@taicode/common-server 3.7.5 → 3.7.7
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/LICENSE +21 -0
- package/package.json +9 -10
- package/output/context/index.d.ts +0 -6
- package/output/context/index.d.ts.map +0 -1
- package/output/context/index.js +0 -14
- package/output/context/index.test.d.ts +0 -2
- package/output/context/index.test.d.ts.map +0 -1
- package/output/context/index.test.js +0 -14
- package/output/index.d.ts +0 -5
- package/output/index.d.ts.map +0 -1
- package/output/index.js +0 -5
- package/output/rate-limiter/index.d.ts +0 -4
- package/output/rate-limiter/index.d.ts.map +0 -1
- package/output/rate-limiter/index.js +0 -3
- package/output/rate-limiter/memory-store.d.ts +0 -12
- package/output/rate-limiter/memory-store.d.ts.map +0 -1
- package/output/rate-limiter/memory-store.js +0 -46
- package/output/rate-limiter/rate-limiter.d.ts +0 -17
- package/output/rate-limiter/rate-limiter.d.ts.map +0 -1
- package/output/rate-limiter/rate-limiter.js +0 -64
- package/output/rate-limiter/types.d.ts +0 -27
- package/output/rate-limiter/types.d.ts.map +0 -1
- package/output/rate-limiter/types.js +0 -1
- package/output/redis-queue/index.d.ts +0 -8
- package/output/redis-queue/index.d.ts.map +0 -1
- package/output/redis-queue/index.js +0 -6
- package/output/redis-queue/redis-queue-batch-consumer.d.ts +0 -97
- package/output/redis-queue/redis-queue-batch-consumer.d.ts.map +0 -1
- package/output/redis-queue/redis-queue-batch-consumer.js +0 -341
- package/output/redis-queue/redis-queue-batch-consumer.test.d.ts +0 -26
- package/output/redis-queue/redis-queue-batch-consumer.test.d.ts.map +0 -1
- package/output/redis-queue/redis-queue-batch-consumer.test.js +0 -341
- package/output/redis-queue/redis-queue-common.d.ts +0 -73
- package/output/redis-queue/redis-queue-common.d.ts.map +0 -1
- package/output/redis-queue/redis-queue-common.js +0 -303
- package/output/redis-queue/redis-queue-common.test.d.ts +0 -19
- package/output/redis-queue/redis-queue-common.test.d.ts.map +0 -1
- package/output/redis-queue/redis-queue-common.test.js +0 -585
- package/output/redis-queue/redis-queue-consumer.d.ts +0 -95
- package/output/redis-queue/redis-queue-consumer.d.ts.map +0 -1
- package/output/redis-queue/redis-queue-consumer.js +0 -327
- package/output/redis-queue/redis-queue-consumer.test.d.ts +0 -26
- package/output/redis-queue/redis-queue-consumer.test.d.ts.map +0 -1
- package/output/redis-queue/redis-queue-consumer.test.js +0 -316
- package/output/redis-queue/redis-queue-provider.d.ts +0 -69
- package/output/redis-queue/redis-queue-provider.d.ts.map +0 -1
- package/output/redis-queue/redis-queue-provider.js +0 -205
- package/output/redis-queue/redis-queue-provider.test.d.ts +0 -28
- package/output/redis-queue/redis-queue-provider.test.d.ts.map +0 -1
- package/output/redis-queue/redis-queue-provider.test.js +0 -149
- package/output/redis-queue/test-helpers.d.ts +0 -112
- package/output/redis-queue/test-helpers.d.ts.map +0 -1
- package/output/redis-queue/test-helpers.js +0 -242
- package/output/redis-queue/test-helpers.test.d.ts +0 -28
- package/output/redis-queue/test-helpers.test.d.ts.map +0 -1
- package/output/redis-queue/test-helpers.test.js +0 -572
- package/output/redis-queue/types.d.ts +0 -146
- package/output/redis-queue/types.d.ts.map +0 -1
- package/output/redis-queue/types.js +0 -1
- package/output/schema/index.d.ts +0 -177
- package/output/schema/index.d.ts.map +0 -1
- package/output/schema/index.js +0 -180
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { RedisQueueConsumer } from './redis-queue-consumer.js';
|
|
2
|
-
/**
|
|
3
|
-
* 测试辅助函数:立即派发任务到消费者
|
|
4
|
-
*
|
|
5
|
-
* 这个辅助函数提供了一个简洁的测试接口,让你能够立即派发任务到消费者,
|
|
6
|
-
* 而不需要等待定时轮询机制。这使得测试更加快速和可控。
|
|
7
|
-
*
|
|
8
|
-
* @param consumer RedisQueueConsumer 或 RedisQueueBatchConsumer 实例
|
|
9
|
-
* @param taskIds 要派发的任务 ID 列表
|
|
10
|
-
* @returns Promise,等待所有任务处理完成
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* import { dispatchQueueTask } from '@taicode/common-server/redis-queue/test-helpers'
|
|
15
|
-
* import { RedisQueueProvider, RedisQueueConsumer } from '@taicode/common-server/redis-queue'
|
|
16
|
-
*
|
|
17
|
-
* describe('My Queue Tests', () => {
|
|
18
|
-
* it('should process tasks', async () => {
|
|
19
|
-
* const provider = new RedisQueueProvider({ ... })
|
|
20
|
-
* const consumer = new RedisQueueConsumer({ ... })
|
|
21
|
-
*
|
|
22
|
-
* await provider.connect()
|
|
23
|
-
* await consumer.connect()
|
|
24
|
-
*
|
|
25
|
-
* // 添加任务
|
|
26
|
-
* const taskId = await provider.enqueue({ data: 'test' })
|
|
27
|
-
*
|
|
28
|
-
* // 立即派发任务进行测试
|
|
29
|
-
* await dispatchQueueTask(consumer, [taskId as string])
|
|
30
|
-
*
|
|
31
|
-
* // 验证结果
|
|
32
|
-
* const stats = await consumer.statistics()
|
|
33
|
-
* expect(stats.completed).toBe(1)
|
|
34
|
-
* })
|
|
35
|
-
* })
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
export async function dispatchQueueTask(consumer, taskIds) {
|
|
39
|
-
if (taskIds.length === 0) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
// @ts-ignore - 访问 protected 属性用于测试
|
|
43
|
-
const redis = consumer.redis;
|
|
44
|
-
// @ts-ignore
|
|
45
|
-
const queueKey = consumer.queueKey;
|
|
46
|
-
// @ts-ignore
|
|
47
|
-
const pendingQueue = consumer.pendingQueue;
|
|
48
|
-
// @ts-ignore
|
|
49
|
-
const processingQueue = consumer.processingQueue;
|
|
50
|
-
if (!redis || !redis.isOpen) {
|
|
51
|
-
throw new Error('Redis client is not connected');
|
|
52
|
-
}
|
|
53
|
-
// 使用 Lua 脚本将指定的任务从 pending 移到 processing 队列
|
|
54
|
-
const moveTasksScript = `
|
|
55
|
-
local pendingQueue = KEYS[1]
|
|
56
|
-
local processingQueue = KEYS[2]
|
|
57
|
-
local moved = 0
|
|
58
|
-
|
|
59
|
-
for i = 3, #KEYS do
|
|
60
|
-
local taskId = KEYS[i]
|
|
61
|
-
-- 从 pending 队列移除
|
|
62
|
-
local removed = redis.call('LREM', pendingQueue, 1, taskId)
|
|
63
|
-
if (removed > 0) then
|
|
64
|
-
-- 添加到 processing 队列
|
|
65
|
-
redis.call('RPUSH', processingQueue, taskId)
|
|
66
|
-
moved = moved + 1
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
return moved
|
|
71
|
-
`;
|
|
72
|
-
// 持续处理直到所有任务完成或失败
|
|
73
|
-
let remainingTasks = [...taskIds];
|
|
74
|
-
let maxAttempts = 100; // 防止无限循环
|
|
75
|
-
let attempts = 0;
|
|
76
|
-
while (remainingTasks.length > 0 && attempts < maxAttempts) {
|
|
77
|
-
attempts++;
|
|
78
|
-
// 移动任务到 processing 队列
|
|
79
|
-
await redis.eval(moveTasksScript, {
|
|
80
|
-
keys: [pendingQueue, processingQueue, ...remainingTasks],
|
|
81
|
-
arguments: [],
|
|
82
|
-
});
|
|
83
|
-
// 处理任务
|
|
84
|
-
if (consumer instanceof RedisQueueConsumer) {
|
|
85
|
-
// RedisQueueConsumer: 逐个处理任务
|
|
86
|
-
const promises = remainingTasks.map(taskId =>
|
|
87
|
-
// @ts-ignore
|
|
88
|
-
consumer.processTask(taskId).catch(() => {
|
|
89
|
-
// 忽略错误,让 consumer 内部处理
|
|
90
|
-
}));
|
|
91
|
-
await Promise.all(promises);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
// RedisQueueBatchConsumer: 按照 consumer 的 batchSize 分批处理任务
|
|
95
|
-
// @ts-ignore
|
|
96
|
-
const batchConsumer = consumer;
|
|
97
|
-
const batchSize = batchConsumer.config?.batchSize || 10;
|
|
98
|
-
// 分批处理任务
|
|
99
|
-
for (let i = 0; i < remainingTasks.length; i += batchSize) {
|
|
100
|
-
const batch = remainingTasks.slice(i, i + batchSize);
|
|
101
|
-
try {
|
|
102
|
-
// @ts-ignore
|
|
103
|
-
await batchConsumer.processBatch(batch);
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
// 批次处理失败是正常的,consumer 会处理重试逻辑
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
// 等待一小段时间让重试任务被放回 pending 队列
|
|
111
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
112
|
-
// 检查还有哪些任务在 pending 队列中(需要重试)
|
|
113
|
-
const stillPending = [];
|
|
114
|
-
for (const taskId of remainingTasks) {
|
|
115
|
-
// @ts-ignore
|
|
116
|
-
const task = await consumer.getTask(taskId);
|
|
117
|
-
if (task && task.status === 'pending') {
|
|
118
|
-
stillPending.push(taskId);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
remainingTasks = stillPending;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* 测试辅助函数:等待队列达到指定状态
|
|
126
|
-
*
|
|
127
|
-
* @param instance Provider 或 Consumer 实例
|
|
128
|
-
* @param predicate 状态断言函数
|
|
129
|
-
* @param options 配置选项
|
|
130
|
-
* @returns Promise,在条件满足时 resolve
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* ```ts
|
|
134
|
-
* // 使用 consumer 等待所有任务完成
|
|
135
|
-
* await waitQueueCompletion(consumer, stats => stats.pending === 0 && stats.processing === 0)
|
|
136
|
-
*
|
|
137
|
-
* // 使用 provider 等待至少完成 10 个任务
|
|
138
|
-
* await waitQueueCompletion(provider, stats => stats.completed >= 10, { timeout: 5000 })
|
|
139
|
-
* ```
|
|
140
|
-
*/
|
|
141
|
-
export async function waitQueueCompletion(instance, predicate, options = {}) {
|
|
142
|
-
const timeout = options.timeout ?? 10000; // 默认 10 秒超时
|
|
143
|
-
const interval = options.interval ?? 100; // 默认 100ms 检查间隔
|
|
144
|
-
const startTime = Date.now();
|
|
145
|
-
while (Date.now() - startTime < timeout) {
|
|
146
|
-
const stats = await instance.statistics();
|
|
147
|
-
if (predicate(stats)) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
await new Promise(resolve => setTimeout(resolve, interval));
|
|
151
|
-
}
|
|
152
|
-
const finalStats = await instance.statistics();
|
|
153
|
-
throw new Error(`Queue state timeout after ${timeout}ms. Final state: ${JSON.stringify(finalStats)}`);
|
|
154
|
-
}
|
|
155
|
-
/**
|
|
156
|
-
* 测试辅助函数:清空队列所有状态
|
|
157
|
-
*
|
|
158
|
-
* 用于测试前后的清理工作,支持 Provider 或 Consumer 实例
|
|
159
|
-
*
|
|
160
|
-
* @param instance Provider 或 Consumer 实例
|
|
161
|
-
* @returns Promise
|
|
162
|
-
*
|
|
163
|
-
* @example
|
|
164
|
-
* ```ts
|
|
165
|
-
* // 使用 consumer 清理
|
|
166
|
-
* afterEach(async () => {
|
|
167
|
-
* await clearQueue(consumer)
|
|
168
|
-
* consumer.disconnect()
|
|
169
|
-
* })
|
|
170
|
-
*
|
|
171
|
-
* // 使用 provider 清理
|
|
172
|
-
* afterEach(async () => {
|
|
173
|
-
* await clearQueue(provider)
|
|
174
|
-
* provider.disconnect()
|
|
175
|
-
* })
|
|
176
|
-
* ```
|
|
177
|
-
*/
|
|
178
|
-
export async function clearQueue(instance) {
|
|
179
|
-
// 访问 protected 属性需要类型断言
|
|
180
|
-
const redis = instance.redis;
|
|
181
|
-
const queueKey = instance.queueKey;
|
|
182
|
-
if (!redis || !redis.isOpen) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
// 删除所有队列相关的键
|
|
186
|
-
const keys = await redis.keys(`${queueKey}:*`);
|
|
187
|
-
if (keys.length > 0) {
|
|
188
|
-
await redis.del(keys);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* 测试辅助函数:获取队列中的所有任务详情
|
|
193
|
-
*
|
|
194
|
-
* 用于测试验证,可以检查任务的完整状态
|
|
195
|
-
*
|
|
196
|
-
* @param provider Provider 实例
|
|
197
|
-
* @param status 可选,筛选特定状态的任务
|
|
198
|
-
* @returns 任务列表
|
|
199
|
-
*
|
|
200
|
-
* @example
|
|
201
|
-
* ```typescript
|
|
202
|
-
* // 获取所有失败的任务
|
|
203
|
-
* const failedTasks = await getQueueTasks(provider, 'failed')
|
|
204
|
-
* expect(failedTasks).toHaveLength(3)
|
|
205
|
-
*
|
|
206
|
-
* // 验证任务数据
|
|
207
|
-
* expect(failedTasks[0].data).toMatchObject({ id: 123 })
|
|
208
|
-
* ```
|
|
209
|
-
*/
|
|
210
|
-
export async function getQueueTasks(provider, status) {
|
|
211
|
-
// @ts-ignore
|
|
212
|
-
const redis = provider.redis;
|
|
213
|
-
// @ts-ignore
|
|
214
|
-
const queueKey = provider.queueKey;
|
|
215
|
-
if (!redis || !redis.isOpen) {
|
|
216
|
-
return [];
|
|
217
|
-
}
|
|
218
|
-
const queueName = status ? `${queueKey}:${status}` : null;
|
|
219
|
-
const tasks = [];
|
|
220
|
-
if (queueName) {
|
|
221
|
-
// 获取特定状态队列中的所有任务 ID
|
|
222
|
-
const taskIds = await redis.lRange(queueName, 0, -1);
|
|
223
|
-
for (const taskId of taskIds) {
|
|
224
|
-
const taskKey = `${queueKey}:task:${taskId}`;
|
|
225
|
-
const taskData = await redis.get(taskKey);
|
|
226
|
-
if (taskData) {
|
|
227
|
-
tasks.push(JSON.parse(taskData));
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
else {
|
|
232
|
-
// 获取所有任务
|
|
233
|
-
const taskKeys = await redis.keys(`${queueKey}:task:*`);
|
|
234
|
-
for (const taskKey of taskKeys) {
|
|
235
|
-
const taskData = await redis.get(taskKey);
|
|
236
|
-
if (taskData) {
|
|
237
|
-
tasks.push(JSON.parse(taskData));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return tasks;
|
|
242
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
type TestTaskData = {
|
|
2
|
-
id?: string;
|
|
3
|
-
message?: string;
|
|
4
|
-
value?: string | number;
|
|
5
|
-
name?: string;
|
|
6
|
-
test?: string | boolean;
|
|
7
|
-
shouldFail?: boolean;
|
|
8
|
-
order?: number;
|
|
9
|
-
taskId?: number;
|
|
10
|
-
index?: number;
|
|
11
|
-
step?: number;
|
|
12
|
-
real?: boolean;
|
|
13
|
-
nested?: {
|
|
14
|
-
value: number;
|
|
15
|
-
};
|
|
16
|
-
example?: string;
|
|
17
|
-
payload?: string;
|
|
18
|
-
data?: any;
|
|
19
|
-
foo?: string;
|
|
20
|
-
number?: number;
|
|
21
|
-
};
|
|
22
|
-
declare module './types.js' {
|
|
23
|
-
interface RedisQueueRegistry {
|
|
24
|
-
[key: `test:${string}`]: TestTaskData;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export {};
|
|
28
|
-
//# sourceMappingURL=test-helpers.test.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|