@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,234 +0,0 @@
1
- import { describe, it, expect, afterEach } from 'vitest';
2
- import { catchIt } from '@taicode/common-base';
3
- import { RedisQueue } from './redis-queue';
4
- const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
5
- describe('RedisQueue', () => {
6
- const queues = [];
7
- afterEach(async () => {
8
- for (const queue of queues) {
9
- try {
10
- await queue.clear();
11
- }
12
- catch (error) {
13
- // 忽略清理错误
14
- }
15
- queue.disconnect();
16
- }
17
- queues.length = 0;
18
- });
19
- const createQueue = (handler, options) => {
20
- const uniqueKey = `test:queue:${Date.now()}:${Math.random()}`;
21
- const queue = new RedisQueue({
22
- redisUrl: REDIS_URL,
23
- queueKey: uniqueKey,
24
- consumerInterval: 100,
25
- maxRetries: 2,
26
- ...options,
27
- handler,
28
- });
29
- queues.push(queue);
30
- return queue;
31
- };
32
- describe('连接管理', () => {
33
- it('应该成功连接到 Redis', async () => {
34
- const queue = createQueue(async () => catchIt(() => { }));
35
- await queue.connect();
36
- const health = await queue.health();
37
- expect(health).toBe(true);
38
- });
39
- it('连接后应该自动启动消费者', async () => {
40
- let processed = false;
41
- const queue = createQueue(async () => {
42
- processed = true;
43
- return catchIt(() => { });
44
- });
45
- await queue.connect();
46
- await queue.enqueue({ test: true });
47
- await new Promise(resolve => setTimeout(resolve, 300));
48
- expect(processed).toBe(true);
49
- });
50
- });
51
- describe('任务入队', () => {
52
- it('应该成功将任务推入队列', async () => {
53
- const queue = createQueue(async () => catchIt(() => { }));
54
- await queue.connect();
55
- const taskId = await queue.enqueue({ foo: 'bar' });
56
- expect(taskId).toBeTruthy();
57
- const stats = await queue.statistics();
58
- expect(stats.pending).toBeGreaterThan(0);
59
- });
60
- it('应该能够批量入队任务', async () => {
61
- const queue = createQueue(async () => catchIt(() => { }));
62
- await queue.connect();
63
- const taskIds = await queue.enqueue([
64
- { value: 1 },
65
- { value: 2 },
66
- { value: 3 },
67
- ]);
68
- expect(taskIds).toHaveLength(3);
69
- const stats = await queue.statistics();
70
- expect(stats.pending + stats.processing).toBe(3);
71
- });
72
- it('入队的任务应该包含正确的元数据', async () => {
73
- const queue = createQueue(async () => catchIt(() => { }));
74
- await queue.connect();
75
- const taskId = await queue.enqueue({ test: 'data', number: 42 });
76
- const task = await queue.getTask(taskId);
77
- expect(task).toBeTruthy();
78
- expect(task?.id).toBe(taskId);
79
- expect(task?.data).toEqual({ test: 'data', number: 42 });
80
- expect(task?.status).toBe('pending');
81
- expect(task?.retryCount).toBe(0);
82
- expect(task?.maxRetries).toBe(2);
83
- });
84
- });
85
- describe('任务处理', () => {
86
- it('应该成功处理任务', async () => {
87
- const processedData = [];
88
- const queue = createQueue(async (data) => {
89
- processedData.push(data);
90
- return catchIt(() => { });
91
- });
92
- await queue.connect();
93
- await queue.enqueue({ value: 'test' });
94
- await new Promise(resolve => setTimeout(resolve, 500));
95
- expect(processedData).toHaveLength(1);
96
- expect(processedData[0]).toEqual({ value: 'test' });
97
- });
98
- it('应该按 FIFO 顺序处理任务', async () => {
99
- const processOrder = [];
100
- const queue = createQueue(async (data) => {
101
- processOrder.push(data.order);
102
- await new Promise(resolve => setTimeout(resolve, 50));
103
- return catchIt(() => { });
104
- });
105
- await queue.connect();
106
- await queue.enqueue({ order: 1 });
107
- await new Promise(resolve => setTimeout(resolve, 10));
108
- await queue.enqueue({ order: 2 });
109
- await new Promise(resolve => setTimeout(resolve, 10));
110
- await queue.enqueue({ order: 3 });
111
- await new Promise(resolve => setTimeout(resolve, 1000));
112
- expect(processOrder).toEqual([1, 2, 3]);
113
- });
114
- });
115
- describe('错误处理和重试', () => {
116
- it('失败的任务应该自动重试', async () => {
117
- let attemptCount = 0;
118
- const queue = createQueue(async () => {
119
- attemptCount++;
120
- if (attemptCount < 2) {
121
- throw new Error('Simulated failure');
122
- }
123
- return catchIt(() => { });
124
- });
125
- await queue.connect();
126
- await queue.enqueue({ test: 'retry' });
127
- await new Promise(resolve => setTimeout(resolve, 1000));
128
- expect(attemptCount).toBe(2);
129
- });
130
- it('超过最大重试次数应该标记为失败', async () => {
131
- let attemptCount = 0;
132
- const queue = createQueue(async () => {
133
- attemptCount++;
134
- throw new Error('Always fails');
135
- });
136
- await queue.connect();
137
- const taskId = await queue.enqueue({ test: 'fail' });
138
- await new Promise(resolve => setTimeout(resolve, 1500));
139
- expect(attemptCount).toBe(3); // 初始 + 2 次重试
140
- const task = await queue.getTask(taskId);
141
- expect(task?.status).toBe('failed');
142
- });
143
- });
144
- describe('消费者管理', () => {
145
- it('断开连接后消费者应该停止', async () => {
146
- let processCount = 0;
147
- const queue = createQueue(async () => {
148
- processCount++;
149
- return catchIt(() => { });
150
- });
151
- await queue.connect();
152
- await queue.enqueue({ test: 1 });
153
- await new Promise(resolve => setTimeout(resolve, 300));
154
- expect(processCount).toBe(1);
155
- queue.disconnect();
156
- await queue.connect();
157
- await queue.enqueue({ test: 2 });
158
- await new Promise(resolve => setTimeout(resolve, 300));
159
- expect(processCount).toBe(2);
160
- });
161
- });
162
- describe('队列操作', () => {
163
- it('应该正确返回队列统计信息', async () => {
164
- const queue = createQueue(async () => {
165
- await new Promise(resolve => setTimeout(resolve, 100));
166
- return catchIt(() => { });
167
- });
168
- await queue.connect();
169
- await queue.enqueue([{ data: 1 }, { data: 2 }, { data: 3 }]);
170
- await new Promise(resolve => setTimeout(resolve, 50));
171
- const stats = await queue.statistics();
172
- expect(stats.pending + stats.processing).toBeGreaterThan(0);
173
- });
174
- it('应该能够清空队列', async () => {
175
- const queue = createQueue(async () => catchIt(() => { }));
176
- await queue.connect();
177
- await queue.enqueue([{ data: 1 }, { data: 2 }]);
178
- await queue.clear();
179
- const stats = await queue.statistics();
180
- expect(stats.pending).toBe(0);
181
- });
182
- });
183
- describe('并发处理', () => {
184
- it('应该支持并发处理多个任务', async () => {
185
- const processing = [];
186
- const completed = [];
187
- const queue = createQueue(async (data) => {
188
- processing.push(data.taskId);
189
- await new Promise(resolve => setTimeout(resolve, 200));
190
- completed.push(data.taskId);
191
- return catchIt(() => { });
192
- }, { concurrency: 3 });
193
- await queue.connect();
194
- await queue.enqueue([
195
- { taskId: 1 }, { taskId: 2 }, { taskId: 3 }, { taskId: 4 }, { taskId: 5 },
196
- ]);
197
- await new Promise(resolve => setTimeout(resolve, 250));
198
- expect(processing.length).toBeGreaterThanOrEqual(2);
199
- await new Promise(resolve => setTimeout(resolve, 1500));
200
- expect(completed.length).toBe(5);
201
- });
202
- });
203
- describe('幂等性', () => {
204
- it('应该支持在 data 中指定 id 来实现幂等性', async () => {
205
- let processCount = 0;
206
- const queue = createQueue(async () => {
207
- processCount++;
208
- return catchIt(() => { });
209
- });
210
- await queue.connect();
211
- await queue.enqueue({ id: 'unique-1', value: 1 });
212
- await queue.enqueue({ id: 'unique-1', value: 2 });
213
- await queue.enqueue({ id: 'unique-2', value: 3 });
214
- await new Promise(resolve => setTimeout(resolve, 1000));
215
- expect(processCount).toBe(2); // 只处理 unique-1 和 unique-2
216
- });
217
- });
218
- describe('延迟处理', () => {
219
- it('应该支持延迟处理任务', async () => {
220
- const startTime = Date.now();
221
- let processTime = 0;
222
- const queue = createQueue(async () => {
223
- processTime = Date.now() - startTime;
224
- return catchIt(() => { });
225
- }, { processingDelay: 2000 });
226
- await queue.connect();
227
- await queue.enqueue({ test: true });
228
- await new Promise(resolve => setTimeout(resolve, 1000));
229
- expect(processTime).toBe(0); // 还未处理
230
- await new Promise(resolve => setTimeout(resolve, 2000));
231
- expect(processTime).toBeGreaterThanOrEqual(1900);
232
- });
233
- });
234
- });
@@ -1,57 +0,0 @@
1
- /**
2
- * 全局队列注册表
3
- *
4
- * 使用 interface 合并模式来定义队列键和对应的任务数据类型
5
- *
6
- * @example
7
- * ```ts
8
- * // 在项目中声明队列类型
9
- * declare module '@taicode/common-server' {
10
- * interface QueueRegistry {
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<'email-queue'>({
19
- * redisUrl: 'redis://localhost:6379',
20
- * queueKey: 'email-queue', // 类型安全
21
- * })
22
- *
23
- * await provider.enqueue({
24
- * to: 'user@example.com', // 类型检查
25
- * subject: 'Hello',
26
- * body: 'World'
27
- * })
28
- * ```
29
- */
30
- /**
31
- * 队列注册表接口
32
- *
33
- * 通过 interface 合并机制,允许在项目中扩展队列定义
34
- */
35
- export interface RedisQueueRegistry {
36
- }
37
- /**
38
- * 从注册表中提取队列键类型
39
- */
40
- export type RegisteredRedisQueueKey = keyof RedisQueueRegistry;
41
- /**
42
- * 根据队列键获取对应的任务数据类型
43
- */
44
- export type RedisQueueTaskData<K extends RegisteredRedisQueueKey> = RedisQueueRegistry[K];
45
- /**
46
- * 类型安全的队列键
47
- * 如果注册表为空,允许使用任意字符串;否则只允许注册的键
48
- */
49
- export type SafeRedisQueueKey<K extends string = string> = RegisteredRedisQueueKey extends never ? K : K extends RegisteredRedisQueueKey ? K : never;
50
- /**
51
- * 根据队列键推断任务数据类型
52
- *
53
- * - 如果键在注册表中,使用注册表中的类型
54
- * - 否则使用泛型参数 T
55
- */
56
- export type InferRedisQueueTaskData<K extends string, T extends Record<string, unknown> = Record<string, unknown>> = K extends RegisteredRedisQueueKey ? RedisQueueTaskData<K> : T;
57
- //# sourceMappingURL=registry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../source/redis-queue/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;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"}
@@ -1,30 +0,0 @@
1
- /**
2
- * 全局队列注册表
3
- *
4
- * 使用 interface 合并模式来定义队列键和对应的任务数据类型
5
- *
6
- * @example
7
- * ```ts
8
- * // 在项目中声明队列类型
9
- * declare module '@taicode/common-server' {
10
- * interface QueueRegistry {
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<'email-queue'>({
19
- * redisUrl: 'redis://localhost:6379',
20
- * queueKey: 'email-queue', // 类型安全
21
- * })
22
- *
23
- * await provider.enqueue({
24
- * to: 'user@example.com', // 类型检查
25
- * subject: 'Hello',
26
- * body: 'World'
27
- * })
28
- * ```
29
- */
30
- export {};