async-flex-loop 1.0.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/dist/index.js ADDED
@@ -0,0 +1,440 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/errors.ts
6
+ var TimeoutError = class extends Error {
7
+ constructor(item, index, timeoutMs) {
8
+ super(`Task at index ${index} timed out after ${timeoutMs}ms`);
9
+ this.item = item;
10
+ this.index = index;
11
+ this.timeoutMs = timeoutMs;
12
+ this.name = "TimeoutError";
13
+ }
14
+ };
15
+ var MaxRetryError = class extends Error {
16
+ constructor(item, index, retryCount, originalError) {
17
+ super(`Task at index ${index} failed after ${retryCount} retries: ${originalError.message}`);
18
+ this.item = item;
19
+ this.index = index;
20
+ this.retryCount = retryCount;
21
+ this.originalError = originalError;
22
+ this.name = "MaxRetryError";
23
+ }
24
+ };
25
+ var QueueAbortError = class extends Error {
26
+ constructor(message = "Queue was aborted") {
27
+ super(message);
28
+ this.name = "QueueAbortError";
29
+ }
30
+ };
31
+
32
+ // src/utils/delay.ts
33
+ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
34
+ var yieldToEventLoop = () => new Promise((resolve) => setTimeout(resolve, 0));
35
+ var calculateRetryDelay = (baseDelay, backoff, attemptNumber) => {
36
+ return baseDelay * Math.pow(backoff, attemptNumber - 1);
37
+ };
38
+
39
+ // src/utils/timeout.ts
40
+ var withTimeout = (promise, ms, item, index) => {
41
+ let timeoutId;
42
+ const timeoutPromise = new Promise((_, reject) => {
43
+ timeoutId = setTimeout(() => {
44
+ reject(new TimeoutError(item, index, ms));
45
+ }, ms);
46
+ });
47
+ return Promise.race([promise.finally(() => clearTimeout(timeoutId)), timeoutPromise]);
48
+ };
49
+
50
+ // src/enums.ts
51
+ var QueueState = /* @__PURE__ */ ((QueueState2) => {
52
+ QueueState2["Idle"] = "idle";
53
+ QueueState2["Pending"] = "pending";
54
+ QueueState2["Processing"] = "processing";
55
+ QueueState2["Paused"] = "paused";
56
+ return QueueState2;
57
+ })(QueueState || {});
58
+
59
+ // src/AsyncFlexLoop.ts
60
+ var AsyncFlexLoop = class {
61
+ /**
62
+ * Constructor with required parameters
63
+ */
64
+ constructor(initialItems, onItem, options) {
65
+ // ========== Private State ==========
66
+ __publicField(this, "state", "pending" /* Pending */);
67
+ __publicField(this, "queue", []);
68
+ __publicField(this, "activeCount", 0);
69
+ __publicField(this, "nextIndex", 0);
70
+ // Results storage
71
+ __publicField(this, "results", /* @__PURE__ */ new Map());
72
+ // Promises for waiting
73
+ __publicField(this, "idleResolvers", []);
74
+ __publicField(this, "idleRejectors", []);
75
+ // Statistics
76
+ __publicField(this, "stats", {
77
+ totalProcessed: 0,
78
+ totalSuccess: 0,
79
+ totalFailed: 0,
80
+ totalProcessingTime: 0,
81
+ successRate: 0
82
+ });
83
+ // ========== Handler & Options ==========
84
+ __publicField(this, "onItemHandler");
85
+ __publicField(this, "options");
86
+ this.onItemHandler = onItem;
87
+ this.options = {
88
+ concurrency: options?.concurrency ?? Infinity,
89
+ autoStart: options?.autoStart ?? true,
90
+ retry: options?.retry ?? 0,
91
+ retryDelay: options?.retryDelay ?? 0,
92
+ retryBackoff: options?.retryBackoff ?? 1,
93
+ delayAfterTask: options?.delayAfterTask ?? 0,
94
+ timeout: options?.timeout,
95
+ yieldLoop: options?.yieldLoop ?? true,
96
+ throwOnError: options?.throwOnError ?? true,
97
+ onError: options?.onError,
98
+ onTaskComplete: options?.onTaskComplete,
99
+ onProgress: options?.onProgress,
100
+ onIdle: options?.onIdle
101
+ };
102
+ if (initialItems.length > 0) {
103
+ this.push(...initialItems);
104
+ }
105
+ if (this.options.autoStart) {
106
+ this.start();
107
+ }
108
+ }
109
+ // ========== Public Methods ==========
110
+ /**
111
+ * Add items to the queue
112
+ * @returns Array of assigned indices
113
+ */
114
+ push(...items) {
115
+ const indices = [];
116
+ for (const item of items) {
117
+ const queueItem = {
118
+ item,
119
+ index: this.nextIndex,
120
+ retryCount: 0,
121
+ addedAt: Date.now()
122
+ };
123
+ this.queue.push(queueItem);
124
+ indices.push(this.nextIndex);
125
+ this.nextIndex++;
126
+ }
127
+ if (this.state === "idle" /* Idle */) {
128
+ this.start();
129
+ } else if (this.state === "processing" /* Processing */) {
130
+ this.processNext();
131
+ }
132
+ return indices;
133
+ }
134
+ /**
135
+ * Start processing the queue
136
+ */
137
+ start() {
138
+ if (this.state === "processing" /* Processing */) {
139
+ return;
140
+ }
141
+ if (this.state === "paused" /* Paused */) {
142
+ this.resume();
143
+ return;
144
+ }
145
+ this.state = "processing" /* Processing */;
146
+ this.processNext();
147
+ }
148
+ /**
149
+ * Pause processing (currently running tasks will finish)
150
+ */
151
+ pause() {
152
+ this.state = "paused" /* Paused */;
153
+ }
154
+ /**
155
+ * Resume processing after a pause
156
+ */
157
+ resume() {
158
+ if (this.state === "paused" /* Paused */) {
159
+ this.state = "processing" /* Processing */;
160
+ this.processNext();
161
+ }
162
+ }
163
+ /**
164
+ * Clear all remaining items in the queue
165
+ */
166
+ clear() {
167
+ this.queue = [];
168
+ }
169
+ /**
170
+ * Wait until the queue is empty and all tasks have completed
171
+ */
172
+ onIdle() {
173
+ if (this.state === "idle" /* Idle */ || this.queue.length === 0 && this.activeCount === 0) {
174
+ return Promise.resolve();
175
+ }
176
+ return new Promise((resolve, reject) => {
177
+ this.idleResolvers.push(resolve);
178
+ this.idleRejectors.push(reject);
179
+ });
180
+ }
181
+ getNextItem() {
182
+ return this.queue[0]?.item;
183
+ }
184
+ /**
185
+ * Get results (waits for queue to finish)
186
+ * @returns Array with undefined for failed tasks
187
+ */
188
+ async getResults() {
189
+ await this.onIdle();
190
+ return this.buildResultsArray();
191
+ }
192
+ /**
193
+ * Get raw results with full information (waits for queue to finish)
194
+ */
195
+ async getRawResults() {
196
+ await this.onIdle();
197
+ return Array.from(this.results.values()).sort((a, b) => a.index - b.index);
198
+ }
199
+ /**
200
+ * Get only successful results (waits for queue to finish)
201
+ */
202
+ async getCompletedResultsAsync() {
203
+ await this.onIdle();
204
+ return this.getCompletedResults();
205
+ }
206
+ /**
207
+ * Get current successful results (does not wait)
208
+ */
209
+ getCompletedResults() {
210
+ const completed = [];
211
+ for (const result of this.results.values()) {
212
+ if (result.status === "fulfilled") {
213
+ completed.push(result.value);
214
+ }
215
+ }
216
+ return completed;
217
+ }
218
+ /**
219
+ * Number of items remaining in the queue
220
+ */
221
+ getPendingCount() {
222
+ return this.queue.length;
223
+ }
224
+ /**
225
+ * Number of items that have been processed
226
+ */
227
+ getProcessedCount() {
228
+ return this.results.size;
229
+ }
230
+ /**
231
+ * Check whether the queue is currently processing
232
+ */
233
+ isRunning() {
234
+ return this.state === "processing" /* Processing */;
235
+ }
236
+ /**
237
+ * Get the current state
238
+ */
239
+ getState() {
240
+ return this.state;
241
+ }
242
+ /**
243
+ * Get statistics
244
+ */
245
+ getStats() {
246
+ return {
247
+ totalProcessed: this.stats.totalProcessed,
248
+ totalSuccess: this.stats.totalSuccess,
249
+ totalFailed: this.stats.totalFailed,
250
+ avgProcessingTime: this.stats.totalProcessed > 0 ? this.stats.totalProcessingTime / this.stats.totalProcessed : 0,
251
+ successRate: this.stats.totalProcessed > 0 ? this.stats.totalSuccess / this.stats.totalProcessed : 0
252
+ };
253
+ }
254
+ // ========== Private Methods ==========
255
+ /**
256
+ * Process the next items if there are available slots
257
+ */
258
+ async processNext() {
259
+ if (this.state !== "processing" /* Processing */) {
260
+ return;
261
+ }
262
+ while (this.queue.length > 0 && this.activeCount < this.options.concurrency) {
263
+ const queueItem = this.queue.shift();
264
+ if (queueItem) {
265
+ this.activeCount++;
266
+ this.processItem(queueItem).catch(() => {
267
+ });
268
+ }
269
+ }
270
+ await this.checkIdle();
271
+ }
272
+ /**
273
+ * Process a single item
274
+ */
275
+ async processItem(queueItem) {
276
+ const processStartTime = Date.now();
277
+ try {
278
+ if (this.options.yieldLoop) {
279
+ await yieldToEventLoop();
280
+ }
281
+ const result = await this.executeTask(queueItem.item, queueItem.index);
282
+ this.handleSuccess({ queueItem, result, processStartTime });
283
+ } catch (error) {
284
+ await this.handleError({ queueItem, error, processStartTime });
285
+ }
286
+ await this.finalizeTask();
287
+ }
288
+ /**
289
+ * Execute the task with optional timeout
290
+ */
291
+ async executeTask(item, index) {
292
+ const taskPromise = this.onItemHandler(item, index);
293
+ return this.options.timeout ? await withTimeout(taskPromise, this.options.timeout, item, index) : await taskPromise;
294
+ }
295
+ /**
296
+ * Handle successful task execution
297
+ */
298
+ async handleSuccess(params) {
299
+ const { queueItem, result, processStartTime } = params;
300
+ const totalTime = Date.now() - processStartTime;
301
+ this.recordSuccess({ index: queueItem.index, value: result, totalTime });
302
+ await this.options.onTaskComplete?.call(this, result, queueItem.item, queueItem.index);
303
+ }
304
+ /**
305
+ * Handle task execution error
306
+ */
307
+ async handleError(params) {
308
+ const { queueItem, error, processStartTime } = params;
309
+ const { item, index, retryCount } = queueItem;
310
+ const err = error instanceof Error ? error : new Error(String(error));
311
+ const totalTime = Date.now() - processStartTime;
312
+ if (err instanceof TimeoutError) {
313
+ await this.recordFailure({ index, item, error: err, totalTime });
314
+ await this.options.onTaskComplete?.call(this, void 0, item, index);
315
+ if (this.options.throwOnError) {
316
+ this.handleFatalError(err);
317
+ }
318
+ return;
319
+ }
320
+ if (retryCount < this.options.retry) {
321
+ await this.scheduleRetry(queueItem, err);
322
+ return;
323
+ }
324
+ const finalError = this.options.retry > 0 ? new MaxRetryError(item, index, retryCount, err) : err;
325
+ await this.recordFailure({ index, item, error: finalError, totalTime });
326
+ await this.options.onError?.call(this, finalError, item, index);
327
+ await this.options.onTaskComplete?.call(this, void 0, item, index);
328
+ if (this.options.throwOnError) {
329
+ this.handleFatalError(finalError);
330
+ }
331
+ }
332
+ /**
333
+ * Finalize task processing
334
+ */
335
+ async finalizeTask() {
336
+ if (this.options.delayAfterTask > 0) {
337
+ await delay(this.options.delayAfterTask);
338
+ }
339
+ this.activeCount--;
340
+ await this.processNext();
341
+ }
342
+ /**
343
+ * Schedule a retry for a failed task
344
+ */
345
+ async scheduleRetry(queueItem, error) {
346
+ const retryDelay = calculateRetryDelay(
347
+ this.options.retryDelay,
348
+ this.options.retryBackoff,
349
+ queueItem.retryCount + 1
350
+ );
351
+ if (retryDelay > 0) {
352
+ await delay(retryDelay);
353
+ }
354
+ queueItem.retryCount++;
355
+ this.queue.unshift(queueItem);
356
+ this.activeCount--;
357
+ this.processNext();
358
+ }
359
+ /**
360
+ * Record successful result
361
+ */
362
+ async recordSuccess(params) {
363
+ const { index, value, totalTime } = params;
364
+ this.results.set(index, { status: "fulfilled", value, index });
365
+ this.updateStats({ success: true, processingTime: totalTime });
366
+ await this.options.onProgress?.call(this);
367
+ }
368
+ /**
369
+ * Record failed result
370
+ */
371
+ async recordFailure(params) {
372
+ const { index, item, error, totalTime } = params;
373
+ this.results.set(index, { status: "rejected", reason: error, item, index });
374
+ this.updateStats({ success: false, processingTime: totalTime });
375
+ await this.options.onProgress?.call(this);
376
+ }
377
+ /**
378
+ * Update statistics
379
+ */
380
+ updateStats(params) {
381
+ const { success, processingTime } = params;
382
+ this.stats.totalProcessed++;
383
+ if (success) {
384
+ this.stats.totalSuccess++;
385
+ } else {
386
+ this.stats.totalFailed++;
387
+ }
388
+ this.stats.totalProcessingTime += processingTime;
389
+ }
390
+ /**
391
+ * Handle fatal error (throwOnError=true)
392
+ */
393
+ handleFatalError(error) {
394
+ this.state = "idle" /* Idle */;
395
+ this.activeCount = 0;
396
+ const rejectors = this.idleRejectors;
397
+ this.idleRejectors = [];
398
+ this.idleResolvers = [];
399
+ rejectors.forEach((reject) => reject(error));
400
+ throw error;
401
+ }
402
+ /**
403
+ * Check if queue is idle
404
+ */
405
+ async checkIdle() {
406
+ if (this.queue.length === 0 && this.activeCount === 0) {
407
+ this.state = "idle" /* Idle */;
408
+ this.resolveIdleWaiters();
409
+ await this.options.onIdle?.call(this);
410
+ await this.options.onIdle?.();
411
+ }
412
+ }
413
+ /**
414
+ * Resolve all idle waiters
415
+ */
416
+ resolveIdleWaiters() {
417
+ const resolvers = this.idleResolvers;
418
+ this.idleResolvers = [];
419
+ resolvers.forEach((resolve) => resolve());
420
+ }
421
+ /**
422
+ * Build results array maintaining order
423
+ */
424
+ buildResultsArray() {
425
+ const results = [];
426
+ for (let i = 0; i < this.nextIndex; i++) {
427
+ const result = this.results.get(i);
428
+ if (result?.status === "fulfilled") {
429
+ results[i] = result.value;
430
+ } else {
431
+ results[i] = void 0;
432
+ }
433
+ }
434
+ return results;
435
+ }
436
+ };
437
+
438
+ export { AsyncFlexLoop, MaxRetryError, QueueAbortError, QueueState, TimeoutError, calculateRetryDelay, delay, withTimeout, yieldToEventLoop };
439
+ //# sourceMappingURL=index.js.map
440
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/utils/delay.ts","../src/utils/timeout.ts","../src/enums.ts","../src/AsyncFlexLoop.ts"],"names":["QueueState"],"mappings":"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","file":"index.js","sourcesContent":["// src/errors.ts\n\nexport class TimeoutError extends Error {\n constructor(\n public readonly item: unknown,\n public readonly index: number,\n public readonly timeoutMs: number,\n ) {\n super(`Task at index ${index} timed out after ${timeoutMs}ms`);\n this.name = \"TimeoutError\";\n }\n}\n\nexport class MaxRetryError extends Error {\n constructor(\n public readonly item: unknown,\n public readonly index: number,\n public readonly retryCount: number,\n public readonly originalError: Error,\n ) {\n super(`Task at index ${index} failed after ${retryCount} retries: ${originalError.message}`);\n this.name = \"MaxRetryError\";\n }\n}\n\nexport class QueueAbortError extends Error {\n constructor(message = \"Queue was aborted\") {\n super(message);\n this.name = \"QueueAbortError\";\n }\n}\n","// src/utils/delay.ts\nexport const delay = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms));\n\nexport const yieldToEventLoop = (): Promise<void> => new Promise((resolve) => setTimeout(resolve, 0));\n\nexport const calculateRetryDelay = (baseDelay: number, backoff: number, attemptNumber: number): number => {\n return baseDelay * Math.pow(backoff, attemptNumber - 1);\n};\n","// src/utils/timeout.ts\n\nimport { TimeoutError } from \"../errors\";\n\nexport const withTimeout = <T>(promise: Promise<T>, ms: number, item: unknown, index: number): Promise<T> => {\n let timeoutId: ReturnType<typeof setTimeout>;\n\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new TimeoutError(item, index, ms));\n }, ms);\n });\n\n return Promise.race([promise.finally(() => clearTimeout(timeoutId)), timeoutPromise]);\n};\n","export enum QueueState {\n Idle = \"idle\",\n Pending = \"pending\",\n Processing = \"processing\",\n Paused = \"paused\",\n}\n","// src/AsyncFlexLoop.ts\n\nimport type { AsyncFlexLoopOptions, QueueItem, TaskResult, QueueStats } from \"./types\";\nimport { TimeoutError, MaxRetryError } from \"./errors\";\nimport { delay, yieldToEventLoop, calculateRetryDelay } from \"./utils/delay\";\nimport { withTimeout } from \"./utils/timeout\";\nimport { QueueState } from \"./enums\";\n\n/**\n * AsyncFlexLoop - Flexible async array processing with dynamic concurrency\n *\n * @example\n * ```typescript\n * const queue = new AsyncFlexLoop(\n * [1, 2, 3],\n * async (item) => item * 2,\n * { concurrency: 2 }\n * );\n *\n * await queue.onIdle();\n * const results = await queue.getResults();\n * ```\n */\nexport class AsyncFlexLoop<InputType, ResponseType> {\n // ========== Private State ==========\n private state: QueueState = QueueState.Pending;\n private queue: QueueItem<InputType>[] = [];\n private activeCount = 0;\n private nextIndex = 0;\n\n // Results storage\n private readonly results: Map<number, TaskResult<ResponseType, InputType>> = new Map();\n\n // Promises for waiting\n private idleResolvers: Array<() => void> = [];\n private idleRejectors: Array<(error: Error) => void> = [];\n\n // Statistics\n private readonly stats = {\n totalProcessed: 0,\n totalSuccess: 0,\n totalFailed: 0,\n totalProcessingTime: 0,\n successRate: 0,\n };\n\n // ========== Handler & Options ==========\n private readonly onItemHandler: (item: InputType, index: number) => Promise<ResponseType>;\n\n private readonly options: Required<\n Omit<\n AsyncFlexLoopOptions<InputType, ResponseType>,\n \"onError\" | \"onTaskComplete\" | \"onProgress\" | \"onIdle\" | \"timeout\"\n >\n > & {\n timeout?: number;\n onError?: AsyncFlexLoopOptions<InputType, ResponseType>[\"onError\"];\n onTaskComplete?: AsyncFlexLoopOptions<InputType, ResponseType>[\"onTaskComplete\"];\n onProgress?: AsyncFlexLoopOptions<InputType, ResponseType>[\"onProgress\"];\n onIdle?: AsyncFlexLoopOptions<InputType, ResponseType>[\"onIdle\"];\n };\n\n /**\n * Constructor with required parameters\n */\n constructor(\n initialItems: InputType[],\n onItem: (item: InputType, index: number) => Promise<ResponseType>,\n options?: AsyncFlexLoopOptions<InputType, ResponseType>,\n ) {\n this.onItemHandler = onItem;\n\n // Merge with defaults\n this.options = {\n concurrency: options?.concurrency ?? Infinity,\n autoStart: options?.autoStart ?? true,\n retry: options?.retry ?? 0,\n retryDelay: options?.retryDelay ?? 0,\n retryBackoff: options?.retryBackoff ?? 1,\n delayAfterTask: options?.delayAfterTask ?? 0,\n timeout: options?.timeout,\n yieldLoop: options?.yieldLoop ?? true,\n throwOnError: options?.throwOnError ?? true,\n onError: options?.onError,\n onTaskComplete: options?.onTaskComplete,\n onProgress: options?.onProgress,\n onIdle: options?.onIdle,\n };\n\n // Add initial items\n if (initialItems.length > 0) {\n this.push(...initialItems);\n }\n\n // Auto start if enabled and has items\n if (this.options.autoStart) {\n this.start();\n }\n }\n\n // ========== Public Methods ==========\n\n /**\n * Add items to the queue\n * @returns Array of assigned indices\n */\n push(...items: InputType[]): number[] {\n const indices: number[] = [];\n\n for (const item of items) {\n const queueItem: QueueItem<InputType> = {\n item,\n index: this.nextIndex,\n retryCount: 0,\n addedAt: Date.now(),\n };\n\n this.queue.push(queueItem);\n indices.push(this.nextIndex);\n this.nextIndex++;\n }\n\n // Auto-resume if was idle (queue finished and now has new work)\n if (this.state === QueueState.Idle) {\n this.start();\n }\n // Continue processing if already running\n else if (this.state === QueueState.Processing) {\n this.processNext();\n }\n // If pending or paused, do nothing (user controls start/resume)\n\n return indices;\n }\n\n /**\n * Start processing the queue\n */\n start(): void {\n // Ignore if already processing\n if (this.state === QueueState.Processing) {\n return;\n }\n\n // If paused, just resume\n if (this.state === QueueState.Paused) {\n this.resume();\n return;\n }\n\n this.state = QueueState.Processing;\n this.processNext();\n }\n\n /**\n * Pause processing (currently running tasks will finish)\n */\n pause(): void {\n this.state = QueueState.Paused;\n }\n\n /**\n * Resume processing after a pause\n */\n resume(): void {\n if (this.state === QueueState.Paused) {\n this.state = QueueState.Processing;\n this.processNext();\n }\n }\n\n /**\n * Clear all remaining items in the queue\n */\n clear(): void {\n this.queue = [];\n }\n\n /**\n * Wait until the queue is empty and all tasks have completed\n */\n onIdle(): Promise<void> {\n if (this.state === QueueState.Idle || (this.queue.length === 0 && this.activeCount === 0)) {\n return Promise.resolve();\n }\n\n return new Promise((resolve, reject) => {\n this.idleResolvers.push(resolve);\n this.idleRejectors.push(reject);\n });\n }\n\n getNextItem(): InputType | undefined {\n return this.queue[0]?.item;\n }\n\n /**\n * Get results (waits for queue to finish)\n * @returns Array with undefined for failed tasks\n */\n async getResults(): Promise<(ResponseType | undefined)[]> {\n await this.onIdle();\n return this.buildResultsArray();\n }\n\n /**\n * Get raw results with full information (waits for queue to finish)\n */\n async getRawResults(): Promise<TaskResult<ResponseType, InputType>[]> {\n await this.onIdle();\n return Array.from(this.results.values()).sort((a, b) => a.index - b.index);\n }\n\n /**\n * Get only successful results (waits for queue to finish)\n */\n async getCompletedResultsAsync(): Promise<ResponseType[]> {\n await this.onIdle();\n return this.getCompletedResults();\n }\n\n /**\n * Get current successful results (does not wait)\n */\n getCompletedResults(): ResponseType[] {\n const completed: ResponseType[] = [];\n for (const result of this.results.values()) {\n if (result.status === \"fulfilled\") {\n completed.push(result.value);\n }\n }\n return completed;\n }\n\n /**\n * Number of items remaining in the queue\n */\n getPendingCount(): number {\n return this.queue.length;\n }\n\n /**\n * Number of items that have been processed\n */\n getProcessedCount(): number {\n return this.results.size;\n }\n\n /**\n * Check whether the queue is currently processing\n */\n isRunning(): boolean {\n return this.state === QueueState.Processing;\n }\n\n /**\n * Get the current state\n */\n getState(): QueueState {\n return this.state;\n }\n\n /**\n * Get statistics\n */\n getStats(): QueueStats {\n return {\n totalProcessed: this.stats.totalProcessed,\n totalSuccess: this.stats.totalSuccess,\n totalFailed: this.stats.totalFailed,\n avgProcessingTime: this.stats.totalProcessed > 0 ? this.stats.totalProcessingTime / this.stats.totalProcessed : 0,\n successRate: this.stats.totalProcessed > 0 ? this.stats.totalSuccess / this.stats.totalProcessed : 0,\n };\n }\n\n // ========== Private Methods ==========\n\n /**\n * Process the next items if there are available slots\n */\n private async processNext(): Promise<void> {\n if (this.state !== QueueState.Processing) {\n return;\n }\n\n while (this.queue.length > 0 && this.activeCount < this.options.concurrency) {\n const queueItem = this.queue.shift();\n if (queueItem) {\n this.activeCount++;\n this.processItem(queueItem).catch(() => {\n // Errors are already handled internally (rejecting idle waiters via handleFatalError)\n });\n }\n }\n\n await this.checkIdle();\n }\n\n /**\n * Process a single item\n */\n private async processItem(queueItem: QueueItem<InputType>): Promise<void> {\n const processStartTime = Date.now();\n\n try {\n if (this.options.yieldLoop) {\n await yieldToEventLoop();\n }\n\n const result = await this.executeTask(queueItem.item, queueItem.index);\n this.handleSuccess({ queueItem, result, processStartTime });\n } catch (error) {\n await this.handleError({ queueItem, error, processStartTime });\n }\n\n await this.finalizeTask();\n }\n\n /**\n * Execute the task with optional timeout\n */\n private async executeTask(item: InputType, index: number): Promise<ResponseType> {\n const taskPromise = this.onItemHandler(item, index);\n\n return this.options.timeout ? await withTimeout(taskPromise, this.options.timeout, item, index) : await taskPromise;\n }\n\n /**\n * Handle successful task execution\n */\n private async handleSuccess(params: {\n queueItem: QueueItem<InputType>;\n result: ResponseType;\n processStartTime: number;\n }): Promise<void> {\n const { queueItem, result, processStartTime } = params;\n const totalTime = Date.now() - processStartTime;\n\n this.recordSuccess({ index: queueItem.index, value: result, totalTime });\n await this.options.onTaskComplete?.call(this, result, queueItem.item, queueItem.index);\n }\n\n /**\n * Handle task execution error\n */\n private async handleError(params: {\n queueItem: QueueItem<InputType>;\n error: unknown;\n processStartTime: number;\n }): Promise<void> {\n const { queueItem, error, processStartTime } = params;\n const { item, index, retryCount } = queueItem;\n const err = error instanceof Error ? error : new Error(String(error));\n const totalTime = Date.now() - processStartTime;\n\n // Handle timeout separately (no retry)\n if (err instanceof TimeoutError) {\n await this.recordFailure({ index, item, error: err, totalTime });\n await this.options.onTaskComplete?.call(this, undefined, item, index);\n\n if (this.options.throwOnError) {\n this.handleFatalError(err);\n }\n return;\n }\n\n // Check retry\n if (retryCount < this.options.retry) {\n await this.scheduleRetry(queueItem, err);\n return;\n }\n\n // Max retries reached\n const finalError = this.options.retry > 0 ? new MaxRetryError(item, index, retryCount, err) : err;\n\n await this.recordFailure({ index, item, error: finalError, totalTime });\n await this.options.onError?.call(this, finalError, item, index);\n await this.options.onTaskComplete?.call(this, undefined, item, index);\n\n if (this.options.throwOnError) {\n this.handleFatalError(finalError);\n }\n }\n\n /**\n * Finalize task processing\n */\n private async finalizeTask(): Promise<void> {\n // Delay after task\n if (this.options.delayAfterTask > 0) {\n await delay(this.options.delayAfterTask);\n }\n\n // Process next (only if still processing)\n this.activeCount--;\n await this.processNext();\n }\n\n /**\n * Schedule a retry for a failed task\n */\n private async scheduleRetry(queueItem: QueueItem<InputType>, error: Error): Promise<void> {\n const retryDelay = calculateRetryDelay(\n this.options.retryDelay,\n this.options.retryBackoff,\n queueItem.retryCount + 1,\n );\n\n if (retryDelay > 0) {\n await delay(retryDelay);\n }\n\n // Re-queue with incremented retry count\n queueItem.retryCount++;\n this.queue.unshift(queueItem); // Add to front\n\n this.activeCount--;\n this.processNext();\n }\n\n /**\n * Record successful result\n */\n private async recordSuccess(params: { index: number; value: ResponseType; totalTime: number }): Promise<void> {\n const { index, value, totalTime } = params;\n\n this.results.set(index, { status: \"fulfilled\", value, index });\n this.updateStats({ success: true, processingTime: totalTime });\n await this.options.onProgress?.call(this);\n }\n\n /**\n * Record failed result\n */\n private async recordFailure(params: {\n index: number;\n item: InputType;\n error: Error;\n totalTime: number;\n }): Promise<void> {\n const { index, item, error, totalTime } = params;\n\n this.results.set(index, { status: \"rejected\", reason: error, item, index });\n this.updateStats({ success: false, processingTime: totalTime });\n await this.options.onProgress?.call(this);\n }\n\n /**\n * Update statistics\n */\n private updateStats(params: { success: boolean; processingTime: number }): void {\n const { success, processingTime } = params;\n\n this.stats.totalProcessed++;\n if (success) {\n this.stats.totalSuccess++;\n } else {\n this.stats.totalFailed++;\n }\n\n this.stats.totalProcessingTime += processingTime;\n }\n\n /**\n * Handle fatal error (throwOnError=true)\n */\n private handleFatalError(error: Error): void {\n this.state = QueueState.Idle;\n this.activeCount = 0;\n\n // Reject all idle waiters\n const rejectors = this.idleRejectors;\n this.idleRejectors = [];\n this.idleResolvers = [];\n rejectors.forEach((reject) => reject(error));\n\n throw error;\n }\n\n /**\n * Check if queue is idle\n */\n private async checkIdle(): Promise<void> {\n if (this.queue.length === 0 && this.activeCount === 0) {\n this.state = QueueState.Idle;\n this.resolveIdleWaiters();\n await this.options.onIdle?.call(this);\n\n // Fire onIdle callback\n await this.options.onIdle?.();\n }\n }\n\n /**\n * Resolve all idle waiters\n */\n private resolveIdleWaiters(): void {\n const resolvers = this.idleResolvers;\n this.idleResolvers = [];\n resolvers.forEach((resolve) => resolve());\n }\n\n /**\n * Build results array maintaining order\n */\n private buildResultsArray(): (ResponseType | undefined)[] {\n const results: (ResponseType | undefined)[] = [];\n for (let i = 0; i < this.nextIndex; i++) {\n const result = this.results.get(i);\n if (result?.status === \"fulfilled\") {\n results[i] = result.value;\n } else {\n results[i] = undefined;\n }\n }\n return results;\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "async-flex-loop",
3
+ "version": "1.0.0",
4
+ "description": "Flexible async array processing with dynamic concurrency",
5
+ "author": "Tan Mac Duc <tanducmac@gmail.com>",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "main": "./dist/index.cjs",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "keywords": [
12
+ "async",
13
+ "queue",
14
+ "concurrency",
15
+ "loop",
16
+ "array"
17
+ ],
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "prepublishOnly": "bun run build",
23
+ "build": "tsup",
24
+ "test": "bun test",
25
+ "test:watch": "bun test --watch",
26
+ "prettier": "prettier --check .",
27
+ "prettier:fix": "prettier --write .",
28
+ "lint-staged": "lint-staged",
29
+ "prepare": "husky"
30
+ },
31
+ "devDependencies": {
32
+ "@types/bun": "latest",
33
+ "husky": "^9.1.7",
34
+ "lint-staged": "^16.2.7",
35
+ "prettier": "^3.8.0",
36
+ "tsup": "^8.5.1",
37
+ "typescript": "^5.9.3"
38
+ }
39
+ }