@taicode/common-server 1.0.11 → 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 (51) 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 +6 -4
  5. package/output/redis-queue/index.d.ts.map +1 -1
  6. package/output/redis-queue/index.js +4 -2
  7. package/output/redis-queue/redis-batch-consumer.d.ts +80 -0
  8. package/output/redis-queue/redis-batch-consumer.d.ts.map +1 -0
  9. package/output/redis-queue/redis-batch-consumer.js +308 -0
  10. package/output/redis-queue/redis-batch-consumer.test.d.ts +7 -0
  11. package/output/redis-queue/redis-batch-consumer.test.d.ts.map +1 -0
  12. package/output/redis-queue/redis-batch-consumer.test.js +265 -0
  13. package/output/redis-queue/redis-queue-common.d.ts +73 -0
  14. package/output/redis-queue/redis-queue-common.d.ts.map +1 -0
  15. package/output/redis-queue/redis-queue-common.js +302 -0
  16. package/output/redis-queue/redis-queue-common.test.d.ts +19 -0
  17. package/output/redis-queue/redis-queue-common.test.d.ts.map +1 -0
  18. package/output/redis-queue/redis-queue-common.test.js +623 -0
  19. package/output/redis-queue/redis-queue-consumer.d.ts +81 -0
  20. package/output/redis-queue/redis-queue-consumer.d.ts.map +1 -0
  21. package/output/redis-queue/redis-queue-consumer.js +297 -0
  22. package/output/redis-queue/redis-queue-consumer.test.d.ts +7 -0
  23. package/output/redis-queue/redis-queue-consumer.test.d.ts.map +1 -0
  24. package/output/redis-queue/redis-queue-consumer.test.js +242 -0
  25. package/output/redis-queue/redis-queue-provider.d.ts +56 -0
  26. package/output/redis-queue/redis-queue-provider.d.ts.map +1 -0
  27. package/output/redis-queue/redis-queue-provider.js +187 -0
  28. package/output/redis-queue/redis-queue-provider.test.d.ts +7 -0
  29. package/output/redis-queue/redis-queue-provider.test.d.ts.map +1 -0
  30. package/output/redis-queue/redis-queue-provider.test.js +114 -0
  31. package/output/redis-queue/types.d.ts +77 -19
  32. package/output/redis-queue/types.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/output/logger/logger.d.ts +0 -33
  35. package/output/logger/logger.d.ts.map +0 -1
  36. package/output/logger/logger.js +0 -65
  37. package/output/logger/logger.test.d.ts +0 -2
  38. package/output/logger/logger.test.d.ts.map +0 -1
  39. package/output/logger/logger.test.js +0 -87
  40. package/output/redis-queue/batch-redis-queue.d.ts +0 -136
  41. package/output/redis-queue/batch-redis-queue.d.ts.map +0 -1
  42. package/output/redis-queue/batch-redis-queue.js +0 -573
  43. package/output/redis-queue/batch-redis-queue.test.d.ts +0 -2
  44. package/output/redis-queue/batch-redis-queue.test.d.ts.map +0 -1
  45. package/output/redis-queue/batch-redis-queue.test.js +0 -243
  46. package/output/redis-queue/redis-queue.d.ts +0 -129
  47. package/output/redis-queue/redis-queue.d.ts.map +0 -1
  48. package/output/redis-queue/redis-queue.js +0 -547
  49. package/output/redis-queue/redis-queue.test.d.ts +0 -2
  50. package/output/redis-queue/redis-queue.test.d.ts.map +0 -1
  51. package/output/redis-queue/redis-queue.test.js +0 -234
@@ -0,0 +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,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"}
@@ -0,0 +1,187 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { RedisQueueCommon } from './redis-queue-common';
3
+ /**
4
+ * Redis 队列生产者
5
+ *
6
+ * 只负责任务的入队和队列管理,不包含消费逻辑
7
+ *
8
+ * @template K 队列键类型
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * // 注册类型后使用
13
+ * const provider = new RedisQueueProvider({
14
+ * queueKey: 'email-queue', // 只能使用注册的键
15
+ * })
16
+ *
17
+ * await provider.enqueue({
18
+ * to: 'user@example.com', // 类型自动推断
19
+ * subject: 'Hello',
20
+ * body: 'World'
21
+ * })
22
+ * ```
23
+ */
24
+ export class RedisQueueProvider extends RedisQueueCommon {
25
+ processingDelay;
26
+ constructor(config) {
27
+ super({
28
+ redisUrl: config.redisUrl,
29
+ redisClient: config.redisClient,
30
+ queueKey: config.queueKey,
31
+ cleanupDelay: config.cleanupDelay,
32
+ });
33
+ this.processingDelay = config.processingDelay ?? 0; // 默认立即执行
34
+ }
35
+ getLogPrefix() {
36
+ return 'RedisQueueProvider';
37
+ }
38
+ async enqueue(data) {
39
+ if (!this.redis) {
40
+ console.warn('[RedisQueueProvider] Redis not available, skipping task enqueue');
41
+ return Array.isArray(data) ? [] : '';
42
+ }
43
+ // 统一处理为数组
44
+ const dataList = Array.isArray(data) ? data : [data];
45
+ const taskIds = [];
46
+ const tasks = [];
47
+ // 准备任务数据
48
+ for (const item of dataList) {
49
+ const customId = item.id;
50
+ const taskId = customId || randomUUID();
51
+ const task = {
52
+ id: taskId,
53
+ data: item,
54
+ retryCount: 0,
55
+ maxRetries: 3, // 默认值,实际由 Consumer 控制
56
+ createdTime: new Date().toISOString(),
57
+ status: 'pending',
58
+ ...(this.processingDelay > 0 && { delayUntil: Date.now() + this.processingDelay }),
59
+ };
60
+ taskIds.push(taskId);
61
+ tasks.push(task);
62
+ }
63
+ // 使用 Lua 脚本批量原子化入队
64
+ const batchEnqueueScript = `
65
+ local pendingQueue = KEYS[1]
66
+ local ttl = tonumber(ARGV[1])
67
+ local queueKeyPrefix = ARGV[2]
68
+ local successCount = 0
69
+
70
+ -- ARGV[3], ARGV[5], ARGV[7]... 是 taskId
71
+ -- ARGV[4], ARGV[6], ARGV[8]... 是 taskData
72
+ for i = 3, #ARGV, 2 do
73
+ local taskId = ARGV[i]
74
+ local taskData = ARGV[i + 1]
75
+ local taskKey = queueKeyPrefix .. ':task:' .. taskId
76
+
77
+ -- 检查任务是否已存在
78
+ local exists = redis.call('EXISTS', taskKey)
79
+ if exists == 0 then
80
+ -- 原子化操作: 创建任务 + 推入队列
81
+ redis.call('SETEX', taskKey, ttl, taskData)
82
+ redis.call('RPUSH', pendingQueue, taskId)
83
+ successCount = successCount + 1
84
+ end
85
+ end
86
+
87
+ return successCount
88
+ `;
89
+ // 构建参数数组
90
+ const args = [this.cleanupDelay.toString(), this.queueKey];
91
+ for (let i = 0; i < tasks.length; i++) {
92
+ args.push(taskIds[i]);
93
+ args.push(JSON.stringify(tasks[i]));
94
+ }
95
+ const successCount = await this.redis.eval(batchEnqueueScript, {
96
+ keys: [this.pendingQueue],
97
+ arguments: args,
98
+ });
99
+ if (successCount < tasks.length) {
100
+ console.log(`[RedisQueueProvider] ${tasks.length - successCount} tasks already exist, skipped`);
101
+ }
102
+ if (successCount > 0) {
103
+ console.log(`[RedisQueueProvider] Enqueued ${successCount} tasks`);
104
+ }
105
+ // 返回单个 ID 或 ID 数组
106
+ return Array.isArray(data) ? taskIds : taskIds[0];
107
+ }
108
+ /**
109
+ * 获取任务详情
110
+ */
111
+ async getTask(taskId) {
112
+ if (!this.redis)
113
+ return null;
114
+ const taskKey = `${this.queueKey}:task:${taskId}`;
115
+ const taskData = await this.redis.get(taskKey);
116
+ if (!taskData)
117
+ return null;
118
+ return JSON.parse(taskData);
119
+ }
120
+ /**
121
+ * 获取队列统计信息(O(1) 时间复杂度)
122
+ */
123
+ async statistics() {
124
+ if (!this.redis) {
125
+ return { pending: 0, processing: 0, completed: 0, failed: 0 };
126
+ }
127
+ const [pending, processing, completed, failed] = await Promise.all([
128
+ this.redis.lLen(this.pendingQueue),
129
+ this.redis.lLen(this.processingQueue),
130
+ this.redis.lLen(this.completedQueue),
131
+ this.redis.lLen(this.failedQueue),
132
+ ]);
133
+ return { pending, processing, completed, failed };
134
+ }
135
+ /**
136
+ * 清空所有队列和任务数据
137
+ * 使用 Lua 脚本确保原子性
138
+ */
139
+ async clear() {
140
+ if (!this.redis)
141
+ return;
142
+ // 使用 Lua 脚本原子化清空队列和任务
143
+ const clearScript = `
144
+ local queueKeyPrefix = ARGV[1]
145
+ local pendingQueue = KEYS[1]
146
+ local processingQueue = KEYS[2]
147
+ local completedQueue = KEYS[3]
148
+ local failedQueue = KEYS[4]
149
+
150
+ -- 获取所有队列中的任务 ID
151
+ local allTaskIds = {}
152
+ local pendingIds = redis.call('LRANGE', pendingQueue, 0, -1)
153
+ local processingIds = redis.call('LRANGE', processingQueue, 0, -1)
154
+ local completedIds = redis.call('LRANGE', completedQueue, 0, -1)
155
+ local failedIds = redis.call('LRANGE', failedQueue, 0, -1)
156
+
157
+ -- 合并所有任务 ID
158
+ for _, id in ipairs(pendingIds) do table.insert(allTaskIds, id) end
159
+ for _, id in ipairs(processingIds) do table.insert(allTaskIds, id) end
160
+ for _, id in ipairs(completedIds) do table.insert(allTaskIds, id) end
161
+ for _, id in ipairs(failedIds) do table.insert(allTaskIds, id) end
162
+
163
+ -- 删除所有任务数据
164
+ for _, taskId in ipairs(allTaskIds) do
165
+ local taskKey = queueKeyPrefix .. ':task:' .. taskId
166
+ redis.call('DEL', taskKey)
167
+ end
168
+
169
+ -- 删除所有队列
170
+ redis.call('DEL', pendingQueue, processingQueue, completedQueue, failedQueue)
171
+
172
+ return #allTaskIds
173
+ `;
174
+ const deletedCount = await this.redis.eval(clearScript, {
175
+ keys: [
176
+ this.pendingQueue,
177
+ this.processingQueue,
178
+ this.completedQueue,
179
+ this.failedQueue,
180
+ ],
181
+ arguments: [this.queueKey],
182
+ });
183
+ if (deletedCount > 0) {
184
+ console.log(`[RedisQueueProvider] Cleared ${deletedCount} tasks`);
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,7 @@
1
+ declare module './types' {
2
+ interface RedisQueueRegistry {
3
+ 'test-queue': Record<string, any>;
4
+ }
5
+ }
6
+ export {};
7
+ //# sourceMappingURL=redis-queue-provider.test.d.ts.map
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,114 @@
1
+ import { describe, it, expect, afterEach } from 'vitest';
2
+ import { RedisQueueProvider } from './redis-queue-provider';
3
+ const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379';
4
+ describe('RedisQueueProvider', () => {
5
+ const providers = [];
6
+ afterEach(async () => {
7
+ for (const provider of providers) {
8
+ try {
9
+ await provider.clear();
10
+ }
11
+ catch (error) {
12
+ // 忽略清理错误
13
+ }
14
+ provider.disconnect();
15
+ }
16
+ providers.length = 0;
17
+ });
18
+ const createProvider = (options) => {
19
+ const uniqueKey = `test:provider:${Date.now()}:${Math.random()}`;
20
+ const provider = new RedisQueueProvider({
21
+ redisUrl: REDIS_URL,
22
+ queueKey: uniqueKey,
23
+ ...options,
24
+ });
25
+ providers.push(provider);
26
+ return provider;
27
+ };
28
+ describe('连接管理', () => {
29
+ it('应该成功连接到 Redis', async () => {
30
+ const provider = createProvider();
31
+ await provider.connect();
32
+ const health = await provider.health();
33
+ expect(health).toBe(true);
34
+ });
35
+ });
36
+ describe('任务入队', () => {
37
+ it('应该成功将任务推入队列', async () => {
38
+ const provider = createProvider();
39
+ await provider.connect();
40
+ const taskId = await provider.enqueue({ foo: 'bar' });
41
+ expect(taskId).toBeTruthy();
42
+ const stats = await provider.statistics();
43
+ expect(stats.pending).toBe(1);
44
+ });
45
+ it('应该能够批量入队任务', async () => {
46
+ const provider = createProvider();
47
+ await provider.connect();
48
+ const taskIds = await provider.enqueue([
49
+ { value: 1 },
50
+ { value: 2 },
51
+ { value: 3 },
52
+ ]);
53
+ expect(taskIds).toHaveLength(3);
54
+ const stats = await provider.statistics();
55
+ expect(stats.pending).toBe(3);
56
+ });
57
+ it('入队的任务应该包含正确的元数据', async () => {
58
+ const provider = createProvider();
59
+ await provider.connect();
60
+ const taskId = await provider.enqueue({ test: 'data', number: 42 });
61
+ const task = await provider.getTask(taskId);
62
+ expect(task).toBeTruthy();
63
+ expect(task?.id).toBe(taskId);
64
+ expect(task?.data).toEqual({ test: 'data', number: 42 });
65
+ expect(task?.status).toBe('pending');
66
+ expect(task?.retryCount).toBe(0);
67
+ });
68
+ });
69
+ describe('队列操作', () => {
70
+ it('应该正确返回队列统计信息', async () => {
71
+ const provider = createProvider();
72
+ await provider.connect();
73
+ await provider.enqueue([
74
+ { test: '1' },
75
+ { test: '2' },
76
+ { test: '3' },
77
+ ]);
78
+ const stats = await provider.statistics();
79
+ expect(stats.pending).toBe(3);
80
+ expect(stats.processing).toBe(0);
81
+ expect(stats.completed).toBe(0);
82
+ expect(stats.failed).toBe(0);
83
+ });
84
+ it('应该能够清空队列', async () => {
85
+ const provider = createProvider();
86
+ await provider.connect();
87
+ await provider.enqueue([{ data: 1 }, { data: 2 }]);
88
+ await provider.clear();
89
+ const stats = await provider.statistics();
90
+ expect(stats.pending).toBe(0);
91
+ });
92
+ });
93
+ describe('幂等性', () => {
94
+ it('应该支持在 data 中指定 id 来实现幂等性', async () => {
95
+ const provider = createProvider();
96
+ await provider.connect();
97
+ await provider.enqueue({ id: 'unique-1', value: 1 });
98
+ await provider.enqueue({ id: 'unique-1', value: 2 });
99
+ await provider.enqueue({ id: 'unique-2', value: 3 });
100
+ const stats = await provider.statistics();
101
+ expect(stats.pending).toBe(2); // 只有 unique-1 和 unique-2
102
+ });
103
+ });
104
+ describe('延迟处理', () => {
105
+ it('应该支持延迟入队任务', async () => {
106
+ const provider = createProvider({ processingDelay: 2000 });
107
+ await provider.connect();
108
+ const taskId = await provider.enqueue({ test: true });
109
+ const task = await provider.getTask(taskId);
110
+ expect(task?.delayUntil).toBeDefined();
111
+ expect(task?.delayUntil).toBeGreaterThan(Date.now());
112
+ });
113
+ });
114
+ });
@@ -1,3 +1,41 @@
1
+ import { CatchResult } from '@taicode/common-base';
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
+ }
1
39
  /**
2
40
  * 任务状态
3
41
  */
@@ -5,8 +43,9 @@ export type Status = 'pending' | 'processing' | 'completed' | 'failed';
5
43
  /**
6
44
  * 任务数据类型,支持可选的 id 字段用于幂等性
7
45
  */
8
- export type TaskData = Record<string, unknown> & {
46
+ export type TaskData = {
9
47
  id?: string;
48
+ [key: string]: any;
10
49
  };
11
50
  /**
12
51
  * 任务数据结构
@@ -31,40 +70,59 @@ export interface QueueStats {
31
70
  processing: number;
32
71
  }
33
72
  /**
34
- * 任务队列配置
73
+ * Redis 队列基础配置
35
74
  */
36
- export interface TaskQueueConfig<T extends TaskData = TaskData> {
37
- redisUrl: string;
38
- queueKey: string;
39
- handler: TaskHandler<T>;
40
- maxRetries?: number;
41
- concurrency?: number;
75
+ export interface RedisQueueCommonConfig {
76
+ redisUrl?: string;
77
+ redisClient?: RedisClientType;
78
+ queueKey: keyof RedisQueueRegistry;
79
+ cleanupDelay?: number;
80
+ }
81
+ /**
82
+ * 队列生产者配置
83
+ */
84
+ export interface RedisQueueProviderConfig<K extends keyof RedisQueueRegistry = keyof RedisQueueRegistry> {
85
+ redisUrl?: string;
86
+ redisClient?: RedisClientType;
87
+ queueKey: K;
42
88
  cleanupDelay?: number;
43
89
  processingDelay?: number;
90
+ }
91
+ /**
92
+ * 队列消费者配置
93
+ */
94
+ export interface RedisQueueConsumerConfig<K extends keyof RedisQueueRegistry> {
95
+ redisUrl?: string;
96
+ redisClient?: RedisClientType;
97
+ queueKey: K;
98
+ handler: TaskHandler<RedisQueueRegistry[K]>;
99
+ maxRetries?: number;
100
+ concurrency?: number;
44
101
  consumerInterval?: number;
45
102
  processingTimeout?: number;
103
+ cleanupDelay?: number;
46
104
  }
47
105
  /**
48
- * 批量任务队列配置
106
+ * 任务处理器函数
49
107
  */
50
- export interface BatchTaskQueueConfig<T extends TaskData = TaskData> {
51
- redisUrl: string;
52
- queueKey: string;
53
- handler: BatchTaskHandler<T>;
108
+ export type TaskHandler<T extends TaskData = TaskData> = (data: T) => Promise<CatchResult<unknown, string>>;
109
+ /**
110
+ * 批量消费者配置
111
+ */
112
+ export interface BatchConsumerConfig<K extends keyof RedisQueueRegistry> {
113
+ redisUrl?: string;
114
+ redisClient?: RedisClientType;
115
+ queueKey: K;
116
+ handler: BatchTaskHandler<RedisQueueRegistry[K]>;
54
117
  batchSize?: number;
55
118
  maxRetries?: number;
56
119
  concurrency?: number;
57
120
  cleanupDelay?: number;
58
- processingDelay?: number;
59
121
  consumerInterval?: number;
60
122
  processingTimeout?: number;
61
123
  }
62
- /**
63
- * 任务处理器函数
64
- */
65
- export type TaskHandler<T extends TaskData = TaskData> = (data: T) => Promise<import('@taicode/common-base').CatchResult<unknown, string>>;
66
124
  /**
67
125
  * 批量任务处理器函数
68
126
  */
69
- 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>>;
70
128
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/redis-queue/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,MAAM,GAAG,SAAS,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAA;AAEtE;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhE;;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,eAAe,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IAC5D,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IACjE,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,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,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,OAAO,sBAAsB,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;AAE1I;;GAEG;AACH,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,CAAC,QAAQ,EAAE,CAAC,EAAE,KAAK,OAAO,CAAC,OAAO,sBAAsB,EAAE,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/redis-queue/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAA;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,kBAAkB;IAEjC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5B;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,QAAQ,EAAE,MAAM,kBAAkB,CAAA;IAClC,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB,CAAC,CAAC,SAAS,MAAM,kBAAkB,GAAG,MAAM,kBAAkB;IACrG,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,QAAQ,EAAE,CAAC,CAAA;IACX,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB,CAAC,CAAC,SAAS,MAAM,kBAAkB;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,QAAQ,EAAE,CAAC,CAAA;IACX,OAAO,EAAE,WAAW,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAA;IAC3C,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,CAAE,CAAC,SAAS,MAAM,kBAAkB;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,eAAe,CAAA;IAC7B,QAAQ,EAAE,CAAC,CAAA;IACX,OAAO,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAA;IAChD,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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taicode/common-server",
3
- "version": "1.0.11",
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":""}
@@ -1,87 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- import { FastifyLogger } from './logger';
3
- describe('FastifyLogger', () => {
4
- let printMock;
5
- let loggerOptions;
6
- beforeEach(() => {
7
- printMock = vi.fn();
8
- loggerOptions = {
9
- print: printMock,
10
- level: 'info'
11
- };
12
- });
13
- it('应该创建一个 FastifyLogger 实例', () => {
14
- const logger = new FastifyLogger(loggerOptions);
15
- expect(logger).toBeInstanceOf(FastifyLogger);
16
- });
17
- it('应该支持所有日志级别方法', () => {
18
- const logger = new FastifyLogger(loggerOptions);
19
- logger.trace('trace message');
20
- logger.debug('debug message');
21
- logger.info('info message');
22
- logger.warn('warn message');
23
- logger.error('error message');
24
- logger.fatal('fatal message');
25
- // 由于默认级别是 info,trace 和 debug 不应该被调用
26
- expect(printMock).not.toHaveBeenCalledWith('trace', 'trace message');
27
- expect(printMock).not.toHaveBeenCalledWith('debug', 'debug message');
28
- expect(printMock).toHaveBeenCalledWith('info', 'info message');
29
- expect(printMock).toHaveBeenCalledWith('warn', 'warn message');
30
- expect(printMock).toHaveBeenCalledWith('error', 'error message');
31
- expect(printMock).toHaveBeenCalledWith('fatal', 'fatal message');
32
- });
33
- it('应该支持 silent 方法', () => {
34
- const logger = new FastifyLogger(loggerOptions);
35
- // silent 方法不应该抛出错误
36
- expect(() => logger.silent('test')).not.toThrow();
37
- // silent 方法不应该调用 print
38
- expect(printMock).not.toHaveBeenCalled();
39
- });
40
- it('应该支持 level getter/setter', () => {
41
- const logger = new FastifyLogger(loggerOptions);
42
- expect(logger.level).toBe('info');
43
- logger.level = 'warn';
44
- expect(logger.level).toBe('warn');
45
- // 验证新级别生效
46
- printMock.mockClear();
47
- logger.info('info message');
48
- logger.warn('warn message');
49
- expect(printMock).not.toHaveBeenCalledWith('info', 'info message');
50
- expect(printMock).toHaveBeenCalledWith('warn', 'warn message');
51
- });
52
- it('应该忽略 silent 级别设置', () => {
53
- const logger = new FastifyLogger(loggerOptions);
54
- logger.level = 'silent';
55
- // silent 不是有效的 logger 级别,所以应该保持原来的级别
56
- expect(logger.level).toBe('info');
57
- });
58
- it('应该支持创建子 logger', () => {
59
- const logger = new FastifyLogger(loggerOptions);
60
- const childLogger = logger.child({ requestId: '123' });
61
- expect(childLogger).toBeInstanceOf(FastifyLogger);
62
- expect(childLogger).not.toBe(logger);
63
- });
64
- it('应该在子 logger 中保存 msgPrefix', () => {
65
- const logger = new FastifyLogger(loggerOptions);
66
- const childLogger = logger.child({ msgPrefix: '[Request] ' });
67
- expect(childLogger.msgPrefix).toBe('[Request] ');
68
- });
69
- it('子 logger 应该继承父 logger 的配置', () => {
70
- const logger = new FastifyLogger({ ...loggerOptions, level: 'warn' });
71
- const childLogger = logger.child({}, { level: 'debug' });
72
- // 子 logger 应该使用传入的 level
73
- expect(childLogger.level).toBe('debug');
74
- });
75
- it('应该支持多个参数的日志输出', () => {
76
- const logger = new FastifyLogger(loggerOptions);
77
- logger.info('message');
78
- expect(printMock).toHaveBeenCalledWith('info', 'message');
79
- });
80
- it('应该正确处理错误对象', () => {
81
- const logger = new FastifyLogger(loggerOptions);
82
- const error = new Error('test error');
83
- logger.error(error);
84
- // 验证 print 被调用,不验证具体的格式化内容
85
- expect(printMock).toHaveBeenCalledWith('error', expect.stringContaining('test error'));
86
- });
87
- });