@taicode/common-base 1.7.3 → 1.7.5
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/error/error.d.ts +104 -67
- package/output/error/error.d.ts.map +1 -1
- package/output/error/error.js +85 -59
- package/output/error/error.test.d.ts +0 -17
- package/output/error/error.test.d.ts.map +1 -1
- package/output/error/error.test.js +172 -99
- package/output/error/index.d.ts +2 -1
- package/output/error/index.d.ts.map +1 -1
- package/output/error/index.js +1 -1
- package/output/flow-queue/flow-queue.d.ts +79 -11
- package/output/flow-queue/flow-queue.d.ts.map +1 -1
- package/output/flow-queue/flow-queue.js +115 -42
- package/output/flow-queue/flow-queue.test.js +182 -11
- package/output/logger/formatter.d.ts +0 -1
- package/output/logger/formatter.d.ts.map +1 -1
- package/output/logger/formatter.js +1 -17
- package/output/logger/formatter.test.d.ts +0 -24
- package/output/logger/formatter.test.d.ts.map +1 -1
- package/output/logger/formatter.test.js +11 -30
- package/output/logger/logger.test.d.ts +0 -19
- package/output/logger/logger.test.d.ts.map +1 -1
- package/output/logger/logger.test.js +6 -16
- package/package.json +3 -2
|
@@ -75,6 +75,8 @@
|
|
|
75
75
|
* - **流量控制**:可配置任务间延迟,避免资源竞争
|
|
76
76
|
* - **状态追踪**:清晰的成功/失败状态管理
|
|
77
77
|
* - **背压处理**:避免内存溢出的队列管理
|
|
78
|
+
* - **并发控制**:支持配置并发处理数量,默认为 1
|
|
79
|
+
* - **链式调用**:支持流畅的链式 API 调用
|
|
78
80
|
*/
|
|
79
81
|
import { catchIt } from '../catch';
|
|
80
82
|
function delay(arg0) {
|
|
@@ -94,7 +96,8 @@ function delay(arg0) {
|
|
|
94
96
|
* const queue = new FlowQueue<InputType, OutputType>(
|
|
95
97
|
* {
|
|
96
98
|
* retry: { strategy: 'exponential', baseDelay: 1000, maxRetries: 3 },
|
|
97
|
-
* delay: { base: 100, jitter: true }
|
|
99
|
+
* delay: { base: 100, jitter: true },
|
|
100
|
+
* concurrency: 3 // 支持 3 个并发处理
|
|
98
101
|
* },
|
|
99
102
|
* async (input) => {
|
|
100
103
|
* // 处理逻辑
|
|
@@ -102,13 +105,25 @@ function delay(arg0) {
|
|
|
102
105
|
* }
|
|
103
106
|
* );
|
|
104
107
|
*
|
|
105
|
-
* //
|
|
108
|
+
* // 传统方式
|
|
106
109
|
* queue.input(data1, data2, data3);
|
|
107
110
|
* queue.onGenerate(result => console.log('处理完成:', result));
|
|
108
|
-
*
|
|
109
|
-
* // 等待所有处理完成
|
|
110
111
|
* queue.closeInput();
|
|
111
112
|
* await queue.awaitCompletion();
|
|
113
|
+
*
|
|
114
|
+
* // 链式调用方式
|
|
115
|
+
* await queue
|
|
116
|
+
* .input(data1, data2)
|
|
117
|
+
* .input(data3)
|
|
118
|
+
* .closeInput()
|
|
119
|
+
* .awaitCompletion();
|
|
120
|
+
*
|
|
121
|
+
* // 重置并重新开始
|
|
122
|
+
* await queue
|
|
123
|
+
* .reset()
|
|
124
|
+
* .input(newData1, newData2)
|
|
125
|
+
* .closeInput()
|
|
126
|
+
* .awaitCompletion();
|
|
112
127
|
* ```
|
|
113
128
|
*/
|
|
114
129
|
export class FlowQueue {
|
|
@@ -118,7 +133,7 @@ export class FlowQueue {
|
|
|
118
133
|
processedInputs = []; // 已处理
|
|
119
134
|
successOutputs = new Map();
|
|
120
135
|
erroredOutputs = new Map();
|
|
121
|
-
|
|
136
|
+
processingCount = 0; // 当前正在处理的任务数量
|
|
122
137
|
retryCount = new Map(); // 记录每个输入的重试次数
|
|
123
138
|
onGenerates = new Map();
|
|
124
139
|
isInputClosed = false; // 标记输入是否已关闭
|
|
@@ -134,14 +149,16 @@ export class FlowQueue {
|
|
|
134
149
|
* - 可以在处理过程中通过 this.input() 动态添加新任务
|
|
135
150
|
* - 支持异步处理,避免阻塞主线程
|
|
136
151
|
* - 失败时会根据重试策略自动重试
|
|
152
|
+
* - 支持并发处理,默认并发数为 1
|
|
137
153
|
*
|
|
138
154
|
* @example
|
|
139
155
|
* ```typescript
|
|
140
|
-
* //
|
|
156
|
+
* // 处理嵌套文件结构的示例(单线程)
|
|
141
157
|
* const fileQueue = new FlowQueue<FileNode, ProcessedFile>(
|
|
142
158
|
* {
|
|
143
159
|
* retry: { strategy: 'exponential', baseDelay: 1000, maxRetries: 3 },
|
|
144
|
-
* delay: { base: 100, jitter: true }
|
|
160
|
+
* delay: { base: 100, jitter: true },
|
|
161
|
+
* concurrency: 1 // 默认值,可省略
|
|
145
162
|
* },
|
|
146
163
|
* async (node) => {
|
|
147
164
|
* try {
|
|
@@ -158,6 +175,17 @@ export class FlowQueue {
|
|
|
158
175
|
* }
|
|
159
176
|
* }
|
|
160
177
|
* );
|
|
178
|
+
*
|
|
179
|
+
* // 并发处理示例
|
|
180
|
+
* const concurrentQueue = new FlowQueue<Task, Result>(
|
|
181
|
+
* {
|
|
182
|
+
* concurrency: 5, // 同时处理 5 个任务
|
|
183
|
+
* retry: { strategy: 'exponential', baseDelay: 1000, maxRetries: 3 }
|
|
184
|
+
* },
|
|
185
|
+
* async (task) => {
|
|
186
|
+
* return await processTask(task);
|
|
187
|
+
* }
|
|
188
|
+
* );
|
|
161
189
|
* ```
|
|
162
190
|
*/
|
|
163
191
|
constructor(options, processor) {
|
|
@@ -172,6 +200,8 @@ export class FlowQueue {
|
|
|
172
200
|
this.options.delay.base = this.options.delay.base ?? 0;
|
|
173
201
|
this.options.delay.jitter = this.options.delay.jitter ?? false;
|
|
174
202
|
this.options.delay.jitterRange = this.options.delay.jitterRange ?? 0.1;
|
|
203
|
+
// 设置并发配置默认值
|
|
204
|
+
this.options.concurrency = this.options.concurrency ?? 1;
|
|
175
205
|
}
|
|
176
206
|
/**
|
|
177
207
|
* 计算重试延迟时间
|
|
@@ -242,7 +272,9 @@ export class FlowQueue {
|
|
|
242
272
|
* 调度下一个处理任务
|
|
243
273
|
*/
|
|
244
274
|
scheduleNextProcessing() {
|
|
245
|
-
|
|
275
|
+
// 启动尽可能多的处理任务,直到达到并发限制
|
|
276
|
+
const tasksToStart = Math.min(this.pendingInputs.length, this.options.concurrency - this.processingCount);
|
|
277
|
+
for (let i = 0; i < tasksToStart; i++) {
|
|
246
278
|
// 计算任务间延迟
|
|
247
279
|
const taskDelay = this.calculateTaskDelay();
|
|
248
280
|
if (taskDelay > 0) {
|
|
@@ -254,10 +286,8 @@ export class FlowQueue {
|
|
|
254
286
|
setTimeout(() => this.processFlow(), 0);
|
|
255
287
|
}
|
|
256
288
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
this.checkAndTriggerCompletion();
|
|
260
|
-
}
|
|
289
|
+
// 检查是否已标记为完成
|
|
290
|
+
this.checkAndTriggerCompletion();
|
|
261
291
|
}
|
|
262
292
|
/**
|
|
263
293
|
* 延迟执行重试
|
|
@@ -265,12 +295,14 @@ export class FlowQueue {
|
|
|
265
295
|
scheduleRetry(input, delayTime) {
|
|
266
296
|
this.pendingInputs.unshift(input);
|
|
267
297
|
if (delayTime <= 0) {
|
|
268
|
-
//
|
|
269
|
-
|
|
298
|
+
// 立即重试,启动调度
|
|
299
|
+
this.scheduleNextProcessing();
|
|
270
300
|
}
|
|
271
301
|
else {
|
|
272
302
|
// 延迟重试
|
|
273
|
-
setTimeout(() =>
|
|
303
|
+
setTimeout(() => {
|
|
304
|
+
this.scheduleNextProcessing();
|
|
305
|
+
}, delayTime);
|
|
274
306
|
}
|
|
275
307
|
}
|
|
276
308
|
/**
|
|
@@ -278,7 +310,7 @@ export class FlowQueue {
|
|
|
278
310
|
*/
|
|
279
311
|
async checkAndTriggerCompletion() {
|
|
280
312
|
await delay(0); // 确保在当前执行栈完成后再检查
|
|
281
|
-
if (this.isInputClosed && this.pendingInputs.length === 0 &&
|
|
313
|
+
if (this.isInputClosed && this.pendingInputs.length === 0 && this.processingCount === 0 && this.completionResolve) {
|
|
282
314
|
this.completionResolve();
|
|
283
315
|
this.completionResolve = null;
|
|
284
316
|
}
|
|
@@ -290,19 +322,19 @@ export class FlowQueue {
|
|
|
290
322
|
* 可以设置失败的重试次数,如果超过最大重试次数则不再重试
|
|
291
323
|
*/
|
|
292
324
|
async processFlow() {
|
|
293
|
-
//
|
|
294
|
-
if (this.
|
|
295
|
-
|
|
325
|
+
// 检查并发限制
|
|
326
|
+
if (this.processingCount >= this.options.concurrency) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// 取出第一个待处理的输入
|
|
330
|
+
const input = this.pendingInputs.shift();
|
|
331
|
+
if (!input) {
|
|
296
332
|
this.checkAndTriggerCompletion();
|
|
297
333
|
return;
|
|
298
334
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const input = this.pendingInputs.shift();
|
|
303
|
-
if (!input) {
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
335
|
+
// 增加处理计数
|
|
336
|
+
this.processingCount++;
|
|
337
|
+
try {
|
|
306
338
|
// 获取当前输入的重试次数
|
|
307
339
|
const currentRetries = this.retryCount.get(input) || 0;
|
|
308
340
|
// 使用 processor 处理输入
|
|
@@ -319,8 +351,6 @@ export class FlowQueue {
|
|
|
319
351
|
this.erroredOutputs.set(input, result.error);
|
|
320
352
|
this.processedInputs.push(input);
|
|
321
353
|
this.retryCount.delete(input);
|
|
322
|
-
// 处理完当前输入后,继续处理下一个(如果有的话)
|
|
323
|
-
this.scheduleNextProcessing();
|
|
324
354
|
}
|
|
325
355
|
}
|
|
326
356
|
else {
|
|
@@ -332,17 +362,17 @@ export class FlowQueue {
|
|
|
332
362
|
this.erroredOutputs.delete(input);
|
|
333
363
|
// 通知所有注册的回调
|
|
334
364
|
await this.notifyOnGenerates(result.value);
|
|
335
|
-
// 处理完当前输入后,继续处理下一个(如果有的话)
|
|
336
|
-
this.scheduleNextProcessing();
|
|
337
365
|
}
|
|
338
|
-
});
|
|
339
|
-
// 处理 processFlow 中的错误
|
|
340
|
-
if (processResult.isError()) {
|
|
341
|
-
console.error('Error in processFlow:', processResult.error);
|
|
342
366
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
367
|
+
catch (error) {
|
|
368
|
+
console.error('Error in processFlow:', error);
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
371
|
+
// 减少正在处理的任务计数
|
|
372
|
+
this.processingCount--;
|
|
373
|
+
// 处理完当前输入后,尝试启动更多处理任务
|
|
374
|
+
this.scheduleNextProcessing();
|
|
375
|
+
}
|
|
346
376
|
}
|
|
347
377
|
/**
|
|
348
378
|
* 通知所有注册的回调函数
|
|
@@ -362,8 +392,10 @@ export class FlowQueue {
|
|
|
362
392
|
* - 在 processor 函数内部可以动态添加新的输入
|
|
363
393
|
* - 避免了深度递归调用栈
|
|
364
394
|
* - 支持批量添加多个输入
|
|
395
|
+
* - 支持链式调用
|
|
365
396
|
*
|
|
366
397
|
* @param inputs 要处理的输入数据
|
|
398
|
+
* @returns 返回 FlowQueue 实例以支持链式调用
|
|
367
399
|
*
|
|
368
400
|
* @example
|
|
369
401
|
* ```typescript
|
|
@@ -378,13 +410,20 @@ export class FlowQueue {
|
|
|
378
410
|
*
|
|
379
411
|
* return result;
|
|
380
412
|
* };
|
|
413
|
+
*
|
|
414
|
+
* // 链式调用示例
|
|
415
|
+
* queue
|
|
416
|
+
* .input(data1, data2)
|
|
417
|
+
* .input(data3)
|
|
418
|
+
* .closeInput();
|
|
381
419
|
* ```
|
|
382
420
|
*/
|
|
383
421
|
input(...inputs) {
|
|
384
422
|
if (this.isInputClosed)
|
|
385
|
-
return;
|
|
423
|
+
return this;
|
|
386
424
|
this.pendingInputs.push(...inputs);
|
|
387
425
|
this.processFlow();
|
|
426
|
+
return this;
|
|
388
427
|
}
|
|
389
428
|
/**
|
|
390
429
|
* 注册输出监听器
|
|
@@ -418,7 +457,7 @@ export class FlowQueue {
|
|
|
418
457
|
* 只读标记,代表是否当前已经 closeInput 并且所有输入都已经处理完毕
|
|
419
458
|
*/
|
|
420
459
|
get isCompleted() {
|
|
421
|
-
return this.isInputClosed && this.pendingInputs.length === 0 &&
|
|
460
|
+
return this.isInputClosed && this.pendingInputs.length === 0 && this.processingCount === 0;
|
|
422
461
|
}
|
|
423
462
|
/**
|
|
424
463
|
* 标记当前 FlowQueue 不会再有新的输入了
|
|
@@ -427,9 +466,12 @@ export class FlowQueue {
|
|
|
427
466
|
* - 队列将不再接受新的输入
|
|
428
467
|
* - 当所有现有输入处理完成后,队列将进入完成状态
|
|
429
468
|
* - 可以通过 awaitCompletion() 等待所有处理完成
|
|
469
|
+
* - 支持链式调用
|
|
430
470
|
*
|
|
431
471
|
* 在深度循环解耦场景中,这是控制处理边界的重要方法。
|
|
432
472
|
*
|
|
473
|
+
* @returns 返回 FlowQueue 实例以支持链式调用
|
|
474
|
+
*
|
|
433
475
|
* @example
|
|
434
476
|
* ```typescript
|
|
435
477
|
* // 添加所有初始数据
|
|
@@ -441,12 +483,19 @@ export class FlowQueue {
|
|
|
441
483
|
* // 等待所有处理完成
|
|
442
484
|
* await queue.awaitCompletion();
|
|
443
485
|
* console.log('所有数据处理完成');
|
|
486
|
+
*
|
|
487
|
+
* // 链式调用示例
|
|
488
|
+
* await queue
|
|
489
|
+
* .input(data1, data2)
|
|
490
|
+
* .closeInput()
|
|
491
|
+
* .awaitCompletion();
|
|
444
492
|
* ```
|
|
445
493
|
*/
|
|
446
494
|
closeInput() {
|
|
447
495
|
this.isInputClosed = true;
|
|
448
496
|
// 使用统一的完成检查方法
|
|
449
497
|
this.checkAndTriggerCompletion();
|
|
498
|
+
return this;
|
|
450
499
|
}
|
|
451
500
|
/**
|
|
452
501
|
* 返回一个 Promise,代表当前已经标记为输入关闭状态、所有输入都已经处理完毕
|
|
@@ -476,8 +525,8 @@ export class FlowQueue {
|
|
|
476
525
|
if (this.completionPromise) {
|
|
477
526
|
return this.completionPromise;
|
|
478
527
|
}
|
|
479
|
-
//
|
|
480
|
-
if (this.isInputClosed && this.pendingInputs.length === 0 &&
|
|
528
|
+
// 如果已经标记为输入关闭且没有待处理的输入和正在处理的任务,立即返回 resolved Promise
|
|
529
|
+
if (this.isInputClosed && this.pendingInputs.length === 0 && this.processingCount === 0) {
|
|
481
530
|
console.log('所有输入已处理完毕');
|
|
482
531
|
return Promise.resolve();
|
|
483
532
|
}
|
|
@@ -492,6 +541,17 @@ export class FlowQueue {
|
|
|
492
541
|
}
|
|
493
542
|
/**
|
|
494
543
|
* 移除尚未处理的输入并调用 closeInput
|
|
544
|
+
*
|
|
545
|
+
* @returns 返回 FlowQueue 实例以支持链式调用
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* ```typescript
|
|
549
|
+
* // 链式调用示例
|
|
550
|
+
* queue
|
|
551
|
+
* .input(data1, data2)
|
|
552
|
+
* .cancel()
|
|
553
|
+
* .reset();
|
|
554
|
+
* ```
|
|
495
555
|
*/
|
|
496
556
|
cancel() {
|
|
497
557
|
// 清除所有待处理的输入
|
|
@@ -500,11 +560,23 @@ export class FlowQueue {
|
|
|
500
560
|
this.retryCount.clear();
|
|
501
561
|
// 标记输入已关闭
|
|
502
562
|
this.closeInput();
|
|
563
|
+
return this;
|
|
503
564
|
}
|
|
504
565
|
/**
|
|
505
566
|
* 重置 FlowQueue 到初始状态
|
|
506
567
|
* 清空所有数据,包括待处理、已处理、输出、重试计数等
|
|
507
568
|
* 重置后可以重新开始处理
|
|
569
|
+
*
|
|
570
|
+
* @returns 返回 FlowQueue 实例以支持链式调用
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* ```typescript
|
|
574
|
+
* // 链式调用示例
|
|
575
|
+
* queue
|
|
576
|
+
* .reset()
|
|
577
|
+
* .input(newData1, newData2)
|
|
578
|
+
* .closeInput();
|
|
579
|
+
* ```
|
|
508
580
|
*/
|
|
509
581
|
reset() {
|
|
510
582
|
// 清空所有队列和状态
|
|
@@ -514,7 +586,7 @@ export class FlowQueue {
|
|
|
514
586
|
this.pendingInputs.length = 0;
|
|
515
587
|
this.processedInputs.length = 0;
|
|
516
588
|
// 重置处理状态
|
|
517
|
-
this.
|
|
589
|
+
this.processingCount = 0;
|
|
518
590
|
this.isInputClosed = false;
|
|
519
591
|
// 重置完成状态
|
|
520
592
|
if (this.completionResolve) {
|
|
@@ -522,6 +594,7 @@ export class FlowQueue {
|
|
|
522
594
|
this.completionResolve = null;
|
|
523
595
|
}
|
|
524
596
|
this.completionPromise = null;
|
|
597
|
+
return this;
|
|
525
598
|
}
|
|
526
599
|
/**
|
|
527
600
|
* 重试所有失败的输入
|
|
@@ -183,24 +183,98 @@ describe('FlowQueue', () => {
|
|
|
183
183
|
});
|
|
184
184
|
});
|
|
185
185
|
describe('并发处理', () => {
|
|
186
|
-
it('
|
|
186
|
+
it('应该支持配置的并发数量', async () => {
|
|
187
187
|
const inputs = [
|
|
188
188
|
{ id: 1, data: 'test1' },
|
|
189
|
-
{ id: 2, data: 'test2' }
|
|
189
|
+
{ id: 2, data: 'test2' },
|
|
190
|
+
{ id: 3, data: 'test3' },
|
|
191
|
+
{ id: 4, data: 'test4' }
|
|
190
192
|
];
|
|
191
|
-
|
|
193
|
+
let processingCount = 0;
|
|
194
|
+
let maxConcurrentProcessing = 0;
|
|
195
|
+
// 模拟较慢的处理器,用来测试并发
|
|
192
196
|
mockProcessor = vi.fn().mockImplementation(async (input) => {
|
|
197
|
+
processingCount++;
|
|
198
|
+
maxConcurrentProcessing = Math.max(maxConcurrentProcessing, processingCount);
|
|
199
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
200
|
+
processingCount--;
|
|
201
|
+
return { id: input.id, result: `processed_${input.data}`, timestamp: Date.now() };
|
|
202
|
+
});
|
|
203
|
+
// 创建支持 3 个并发的队列
|
|
204
|
+
flowQueue = new FlowQueue({
|
|
205
|
+
retry: { maxRetries: 3 },
|
|
206
|
+
concurrency: 3
|
|
207
|
+
}, mockProcessor);
|
|
208
|
+
// 添加所有输入
|
|
209
|
+
flowQueue.input(...inputs);
|
|
210
|
+
flowQueue.closeInput();
|
|
211
|
+
await flowQueue.awaitCompletion();
|
|
212
|
+
// 验证结果
|
|
213
|
+
expect(mockProcessor).toHaveBeenCalledTimes(4);
|
|
214
|
+
expect(flowQueue.successOutputs.size).toBe(4);
|
|
215
|
+
expect(maxConcurrentProcessing).toBe(3); // 应该达到配置的并发数
|
|
216
|
+
});
|
|
217
|
+
it('默认应该使用并发数 1(顺序处理)', async () => {
|
|
218
|
+
const inputs = [
|
|
219
|
+
{ id: 1, data: 'test1' },
|
|
220
|
+
{ id: 2, data: 'test2' },
|
|
221
|
+
{ id: 3, data: 'test3' }
|
|
222
|
+
];
|
|
223
|
+
let processingCount = 0;
|
|
224
|
+
let maxConcurrentProcessing = 0;
|
|
225
|
+
const processingOrder = [];
|
|
226
|
+
mockProcessor = vi.fn().mockImplementation(async (input) => {
|
|
227
|
+
processingCount++;
|
|
228
|
+
maxConcurrentProcessing = Math.max(maxConcurrentProcessing, processingCount);
|
|
229
|
+
processingOrder.push(input.id);
|
|
193
230
|
await new Promise(resolve => setTimeout(resolve, 30));
|
|
231
|
+
processingCount--;
|
|
194
232
|
return { id: input.id, result: `processed_${input.data}`, timestamp: Date.now() };
|
|
195
233
|
});
|
|
234
|
+
// 使用默认配置(并发数应该为 1)
|
|
196
235
|
flowQueue = new FlowQueue({ retry: { maxRetries: 3 } }, mockProcessor);
|
|
197
|
-
|
|
198
|
-
flowQueue.
|
|
199
|
-
flowQueue.
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
expect(
|
|
203
|
-
|
|
236
|
+
flowQueue.input(...inputs);
|
|
237
|
+
flowQueue.closeInput();
|
|
238
|
+
await flowQueue.awaitCompletion();
|
|
239
|
+
// 验证顺序处理
|
|
240
|
+
expect(maxConcurrentProcessing).toBe(1);
|
|
241
|
+
expect(processingOrder).toEqual([1, 2, 3]); // 应该按顺序处理
|
|
242
|
+
});
|
|
243
|
+
it('应该在达到并发限制时等待', async () => {
|
|
244
|
+
const inputs = [
|
|
245
|
+
{ id: 1, data: 'test1' },
|
|
246
|
+
{ id: 2, data: 'test2' },
|
|
247
|
+
{ id: 3, data: 'test3' },
|
|
248
|
+
{ id: 4, data: 'test4' },
|
|
249
|
+
{ id: 5, data: 'test5' }
|
|
250
|
+
];
|
|
251
|
+
let activeProcessing = 0;
|
|
252
|
+
let maxActiveProcessing = 0;
|
|
253
|
+
const processStartTimes = [];
|
|
254
|
+
mockProcessor = vi.fn().mockImplementation(async (input) => {
|
|
255
|
+
processStartTimes.push(Date.now());
|
|
256
|
+
activeProcessing++;
|
|
257
|
+
maxActiveProcessing = Math.max(maxActiveProcessing, activeProcessing);
|
|
258
|
+
await new Promise(resolve => setTimeout(resolve, 80));
|
|
259
|
+
activeProcessing--;
|
|
260
|
+
return { id: input.id, result: `processed_${input.data}`, timestamp: Date.now() };
|
|
261
|
+
});
|
|
262
|
+
// 创建并发数为 2 的队列
|
|
263
|
+
flowQueue = new FlowQueue({
|
|
264
|
+
retry: { maxRetries: 3 },
|
|
265
|
+
concurrency: 2
|
|
266
|
+
}, mockProcessor);
|
|
267
|
+
const startTime = Date.now();
|
|
268
|
+
flowQueue.input(...inputs);
|
|
269
|
+
flowQueue.closeInput();
|
|
270
|
+
await flowQueue.awaitCompletion();
|
|
271
|
+
// 验证并发限制生效
|
|
272
|
+
expect(maxActiveProcessing).toBe(2);
|
|
273
|
+
expect(flowQueue.successOutputs.size).toBe(5);
|
|
274
|
+
// 验证第3个任务等待了前面任务完成
|
|
275
|
+
// 由于并发数为2,第3个任务应该在第一批任务完成后才开始
|
|
276
|
+
const totalTime = Date.now() - startTime;
|
|
277
|
+
expect(totalTime).toBeGreaterThan(150); // 至少需要两轮处理
|
|
204
278
|
});
|
|
205
279
|
});
|
|
206
280
|
describe('边界情况', () => {
|
|
@@ -676,7 +750,7 @@ describe('FlowQueue', () => {
|
|
|
676
750
|
expect(flowQueue['processedInputs'].length).toBe(0);
|
|
677
751
|
expect(flowQueue.successOutputs.size).toBe(0);
|
|
678
752
|
expect(flowQueue['retryCount'].size).toBe(0);
|
|
679
|
-
expect(flowQueue['
|
|
753
|
+
expect(flowQueue['processingCount']).toBe(0);
|
|
680
754
|
expect(flowQueue['isInputClosed']).toBe(false);
|
|
681
755
|
expect(flowQueue['completionPromise']).toBe(null);
|
|
682
756
|
expect(flowQueue['completionResolve']).toBe(null);
|
|
@@ -1030,4 +1104,101 @@ describe('FlowQueue', () => {
|
|
|
1030
1104
|
});
|
|
1031
1105
|
});
|
|
1032
1106
|
});
|
|
1107
|
+
describe('链式调用', () => {
|
|
1108
|
+
it('应该支持 input 方法的链式调用', async () => {
|
|
1109
|
+
const inputs = [
|
|
1110
|
+
{ id: 1, data: 'test1' },
|
|
1111
|
+
{ id: 2, data: 'test2' },
|
|
1112
|
+
{ id: 3, data: 'test3' }
|
|
1113
|
+
];
|
|
1114
|
+
mockProcessor = vi.fn().mockImplementation(async (input) => {
|
|
1115
|
+
return { id: input.id, result: `processed_${input.data}`, timestamp: Date.now() };
|
|
1116
|
+
});
|
|
1117
|
+
flowQueue = new FlowQueue({ retry: { maxRetries: 3 } }, mockProcessor);
|
|
1118
|
+
// 测试链式调用并验证返回值
|
|
1119
|
+
const returnValue = flowQueue
|
|
1120
|
+
.input(inputs[0])
|
|
1121
|
+
.input(inputs[1], inputs[2]);
|
|
1122
|
+
expect(returnValue).toBe(flowQueue); // 验证返回的是同一个实例
|
|
1123
|
+
flowQueue.closeInput();
|
|
1124
|
+
await flowQueue.awaitCompletion();
|
|
1125
|
+
expect(flowQueue.successOutputs.size).toBe(3);
|
|
1126
|
+
});
|
|
1127
|
+
it('应该支持 closeInput 方法的链式调用', async () => {
|
|
1128
|
+
const testInput = { id: 1, data: 'test' };
|
|
1129
|
+
mockProcessor = vi.fn().mockImplementation(async (input) => {
|
|
1130
|
+
return { id: input.id, result: `processed_${input.data}`, timestamp: Date.now() };
|
|
1131
|
+
});
|
|
1132
|
+
flowQueue = new FlowQueue({ retry: { maxRetries: 3 } }, mockProcessor);
|
|
1133
|
+
// 测试链式调用
|
|
1134
|
+
const returnValue = flowQueue
|
|
1135
|
+
.input(testInput)
|
|
1136
|
+
.closeInput();
|
|
1137
|
+
expect(returnValue).toBe(flowQueue);
|
|
1138
|
+
await flowQueue.awaitCompletion();
|
|
1139
|
+
expect(flowQueue.successOutputs.size).toBe(1);
|
|
1140
|
+
});
|
|
1141
|
+
it('应该支持 reset 方法的链式调用', async () => {
|
|
1142
|
+
const firstInput = { id: 1, data: 'first' };
|
|
1143
|
+
const secondInput = { id: 2, data: 'second' };
|
|
1144
|
+
mockProcessor = vi.fn().mockImplementation(async (input) => {
|
|
1145
|
+
return { id: input.id, result: `processed_${input.data}`, timestamp: Date.now() };
|
|
1146
|
+
});
|
|
1147
|
+
flowQueue = new FlowQueue({ retry: { maxRetries: 3 } }, mockProcessor);
|
|
1148
|
+
// 第一轮处理
|
|
1149
|
+
flowQueue.input(firstInput);
|
|
1150
|
+
flowQueue.closeInput();
|
|
1151
|
+
await flowQueue.awaitCompletion();
|
|
1152
|
+
expect(flowQueue.successOutputs.size).toBe(1);
|
|
1153
|
+
// 测试链式调用重置并开始新处理
|
|
1154
|
+
const returnValue = flowQueue
|
|
1155
|
+
.reset()
|
|
1156
|
+
.input(secondInput)
|
|
1157
|
+
.closeInput();
|
|
1158
|
+
expect(returnValue).toBe(flowQueue);
|
|
1159
|
+
await flowQueue.awaitCompletion();
|
|
1160
|
+
expect(flowQueue.successOutputs.size).toBe(1);
|
|
1161
|
+
expect(flowQueue.successOutputs.get(secondInput)?.result).toBe('processed_second');
|
|
1162
|
+
});
|
|
1163
|
+
it('应该支持 cancel 方法的链式调用', async () => {
|
|
1164
|
+
const testInputs = [
|
|
1165
|
+
{ id: 1, data: 'test1' },
|
|
1166
|
+
{ id: 2, data: 'test2' }
|
|
1167
|
+
];
|
|
1168
|
+
flowQueue.input(...testInputs);
|
|
1169
|
+
// 测试链式调用
|
|
1170
|
+
const returnValue = flowQueue.cancel();
|
|
1171
|
+
expect(returnValue).toBe(flowQueue);
|
|
1172
|
+
expect(flowQueue['pendingInputs'].length).toBe(0);
|
|
1173
|
+
// 等待异步完成状态更新
|
|
1174
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
1175
|
+
expect(flowQueue.isCompleted).toBe(true);
|
|
1176
|
+
});
|
|
1177
|
+
it('应该支持完整的链式调用流程', async () => {
|
|
1178
|
+
const inputs = [
|
|
1179
|
+
{ id: 1, data: 'test1' },
|
|
1180
|
+
{ id: 2, data: 'test2' },
|
|
1181
|
+
{ id: 3, data: 'test3' }
|
|
1182
|
+
];
|
|
1183
|
+
mockProcessor = vi.fn().mockImplementation(async (input) => {
|
|
1184
|
+
return { id: input.id, result: `processed_${input.data}`, timestamp: Date.now() };
|
|
1185
|
+
});
|
|
1186
|
+
flowQueue = new FlowQueue({ retry: { maxRetries: 3 } }, mockProcessor);
|
|
1187
|
+
// 测试完整的链式调用流程
|
|
1188
|
+
await flowQueue
|
|
1189
|
+
.input(inputs[0], inputs[1])
|
|
1190
|
+
.input(inputs[2])
|
|
1191
|
+
.closeInput()
|
|
1192
|
+
.awaitCompletion();
|
|
1193
|
+
expect(flowQueue.successOutputs.size).toBe(3);
|
|
1194
|
+
expect(mockProcessor).toHaveBeenCalledTimes(3);
|
|
1195
|
+
// 测试重置后的链式调用
|
|
1196
|
+
await flowQueue
|
|
1197
|
+
.reset()
|
|
1198
|
+
.input(inputs[0])
|
|
1199
|
+
.closeInput()
|
|
1200
|
+
.awaitCompletion();
|
|
1201
|
+
expect(flowQueue.successOutputs.size).toBe(1);
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1033
1204
|
});
|
|
@@ -52,7 +52,6 @@ export declare class DefaultLogFormatter implements LogFormatter {
|
|
|
52
52
|
private formatError;
|
|
53
53
|
private formatUserError;
|
|
54
54
|
private formatSystemError;
|
|
55
|
-
private formatUnknownError;
|
|
56
55
|
private formatGenericError;
|
|
57
56
|
private formatUnknownValue;
|
|
58
57
|
private formatContext;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../source/logger/formatter.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,eAAe;IACf,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IAClC,aAAa;IACb,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IAC7B,aAAa;IACb,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAA;IAC7B,aAAa;IACb,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,MAAM,CAAA;IAC9B,aAAa;IACb,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,MAAM,CAAA;IACnC,eAAe;IACf,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAA;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,cAAc;IACd,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAA;IACxB,cAAc;IACd,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE,CAAA;CACvC;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,gBAAgB;IAChB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,sBAAsB;IACtB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACtD,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,MAAM,CAA0C;gBAE5C,MAAM,GAAE,eAAoB,EAAE,YAAY,GAAE,qBAA0B;IAgBlF;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM;IAwBxB;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE;IAItC;;OAEG;IACH,OAAO,CAAC,WAAW;
|
|
1
|
+
{"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../../source/logger/formatter.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,eAAe;IAC9B,eAAe;IACf,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAA;IAClC,aAAa;IACb,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,CAAA;IAC7B,aAAa;IACb,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAA;IAC7B,aAAa;IACb,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,MAAM,CAAA;IAC9B,aAAa;IACb,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,QAAQ,KAAK,MAAM,CAAA;IACnC,eAAe;IACf,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAA;CAC/B;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,cAAc;IACd,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAAA;IACxB,cAAc;IACd,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE,CAAA;CACvC;AAED,MAAM,WAAW,qBAAqB;IACpC,eAAe;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,gBAAgB;IAChB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,sBAAsB;IACtB,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACtD,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,MAAM,CAA0C;gBAE5C,MAAM,GAAE,eAAoB,EAAE,YAAY,GAAE,qBAA0B;IAgBlF;;OAEG;IACH,MAAM,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM;IAwBxB;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,EAAE;IAItC;;OAEG;IACH,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,iBAAiB;IAiBzB,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,QAAQ;IAehB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,WAAW;CAWpB;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,qBAA4B,CAAA"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 格式化器配置模块
|
|
3
3
|
* 提供各种类型数据的格式化功能
|
|
4
4
|
*/
|
|
5
|
-
import { UserError, SystemError
|
|
5
|
+
import { UserError, SystemError } from '../error';
|
|
6
6
|
/**
|
|
7
7
|
* 日志格式化器
|
|
8
8
|
* 负责根据配置格式化各种类型的参数
|
|
@@ -66,9 +66,6 @@ export class DefaultLogFormatter {
|
|
|
66
66
|
if (SystemError.is(error)) {
|
|
67
67
|
return this.formatSystemError(error, opts);
|
|
68
68
|
}
|
|
69
|
-
if (UnknownError.is(error)) {
|
|
70
|
-
return this.formatUnknownError(error, opts);
|
|
71
|
-
}
|
|
72
69
|
if (error instanceof Error) {
|
|
73
70
|
return this.formatGenericError(error, opts);
|
|
74
71
|
}
|
|
@@ -100,19 +97,6 @@ export class DefaultLogFormatter {
|
|
|
100
97
|
}
|
|
101
98
|
return lines.join('\n ');
|
|
102
99
|
}
|
|
103
|
-
formatUnknownError(error, opts) {
|
|
104
|
-
const lines = [
|
|
105
|
-
this.colorize('❓ Unknown Error', 'magenta', opts.useColors),
|
|
106
|
-
`Message: ${error.message}`,
|
|
107
|
-
];
|
|
108
|
-
if (opts.showContext && error.context) {
|
|
109
|
-
lines.push(`Context: ${this.formatContext(error.context)}`);
|
|
110
|
-
}
|
|
111
|
-
if (opts.showStack && error.stack) {
|
|
112
|
-
lines.push(`Stack: ${error.stack}`);
|
|
113
|
-
}
|
|
114
|
-
return lines.join('\n ');
|
|
115
|
-
}
|
|
116
100
|
formatGenericError(error, opts) {
|
|
117
101
|
const lines = [
|
|
118
102
|
this.colorize(`💥 ${error.name}`, 'red', opts.useColors),
|
|
@@ -1,26 +1,2 @@
|
|
|
1
|
-
declare module '../error' {
|
|
2
|
-
interface UserErrorTypes {
|
|
3
|
-
'validation-failed': {
|
|
4
|
-
field?: string;
|
|
5
|
-
value?: unknown;
|
|
6
|
-
rule?: string;
|
|
7
|
-
};
|
|
8
|
-
'test-error': {
|
|
9
|
-
field?: string;
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
interface SystemErrorTypes {
|
|
13
|
-
'database-error': {
|
|
14
|
-
query?: string;
|
|
15
|
-
table?: string;
|
|
16
|
-
operation?: string;
|
|
17
|
-
};
|
|
18
|
-
'network-error': {
|
|
19
|
-
url?: string;
|
|
20
|
-
method?: string;
|
|
21
|
-
status?: number;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
1
|
export {};
|
|
26
2
|
//# sourceMappingURL=formatter.test.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formatter.test.d.ts","sourceRoot":"","sources":["../../source/logger/formatter.test.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"formatter.test.d.ts","sourceRoot":"","sources":["../../source/logger/formatter.test.ts"],"names":[],"mappings":""}
|