@sejinjja/promise-pool-kit 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sejinjja
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # @sejinjja/promise-pool-kit
2
+
3
+ Lightweight TypeScript utilities for async workloads:
4
+
5
+ - retry with exponential backoff and jitter
6
+ - task-level timeout
7
+ - concurrency-limited promise pool
8
+ - abort signal support
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm i @sejinjja/promise-pool-kit
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ```ts
19
+ import { runPool } from "@sejinjja/promise-pool-kit";
20
+
21
+ const urls = [
22
+ "https://api.example.com/a",
23
+ "https://api.example.com/b",
24
+ "https://api.example.com/c"
25
+ ];
26
+
27
+ const result = await runPool(
28
+ urls,
29
+ async (url, { signal }) => {
30
+ const response = await fetch(url, { signal });
31
+ if (!response.ok) throw new Error(`Request failed: ${response.status}`);
32
+ return response.json();
33
+ },
34
+ {
35
+ concurrency: 4,
36
+ timeoutMs: 5000,
37
+ retry: {
38
+ retries: 2,
39
+ minDelayMs: 200,
40
+ maxDelayMs: 2000,
41
+ jitter: "full"
42
+ }
43
+ }
44
+ );
45
+
46
+ if (result.hasErrors) {
47
+ console.error(result.rejected);
48
+ } else {
49
+ console.log(result.fulfilled.map((item) => item.value));
50
+ }
51
+ ```
52
+
53
+ ## API
54
+
55
+ ### `retry(operation, options?)`
56
+
57
+ Retry a single async operation.
58
+
59
+ ```ts
60
+ import { retry } from "@sejinjja/promise-pool-kit";
61
+
62
+ const token = await retry(
63
+ async (attempt) => {
64
+ const response = await fetch("https://example.com/token");
65
+ if (!response.ok) throw new Error(`Failed attempt ${attempt}`);
66
+ return response.text();
67
+ },
68
+ {
69
+ retries: 3,
70
+ minDelayMs: 100,
71
+ maxDelayMs: 2000,
72
+ jitter: "full",
73
+ timeoutMs: 3000
74
+ }
75
+ );
76
+ ```
77
+
78
+ ### `runPool(inputs, worker, options?)`
79
+
80
+ Run tasks with controlled concurrency.
81
+
82
+ - `concurrency` default: `5`
83
+ - `stopOnError` default: `false`
84
+ - `timeoutMs` applies per task attempt
85
+ - `retry` applies per task
86
+ - `onProgress` receives current summary and last settled task
87
+
88
+ ### `assertPoolSuccess(result)`
89
+
90
+ Throw `AggregateError` when `runPool` contains rejected tasks.
91
+
92
+ ## Error Types
93
+
94
+ - `PoolAbortedError`
95
+ - `TaskTimeoutError`
96
+
97
+ ## Local Development
98
+
99
+ ```bash
100
+ npm install
101
+ npm run check
102
+ ```
103
+
104
+ ## Open Source Workflow
105
+
106
+ 1. Open an issue or discussion.
107
+ 2. Create a branch and add tests for behavior changes.
108
+ 3. Submit a pull request using the template.
109
+
110
+ ## Publishing
111
+
112
+ ```bash
113
+ npm run check
114
+ npm publish --access public
115
+ ```
116
+
117
+ If the package is scoped and first publish fails, verify your npm scope ownership:
118
+
119
+ ```bash
120
+ npm whoami
121
+ npm access ls-packages <your-npm-id>
122
+ ```
123
+
124
+ ## License
125
+
126
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,424 @@
1
+ 'use strict';
2
+
3
+ // src/index.ts
4
+ var PoolAbortedError = class extends Error {
5
+ constructor(message = "Operation aborted.") {
6
+ super(message);
7
+ this.name = "PoolAbortedError";
8
+ }
9
+ };
10
+ var TaskTimeoutError = class extends Error {
11
+ constructor(taskIndex, timeoutMs) {
12
+ super(
13
+ taskIndex >= 0 ? `Task ${taskIndex} timed out after ${timeoutMs}ms.` : `Operation timed out after ${timeoutMs}ms.`
14
+ );
15
+ this.name = "TaskTimeoutError";
16
+ this.taskIndex = taskIndex;
17
+ this.timeoutMs = timeoutMs;
18
+ }
19
+ };
20
+ var DEFAULT_RETRY = {
21
+ retries: 0,
22
+ minDelayMs: 100,
23
+ maxDelayMs: 5e3,
24
+ factor: 2,
25
+ jitter: "none",
26
+ shouldRetry: (error) => !(error instanceof PoolAbortedError)
27
+ };
28
+ function toAbortError(reason, fallbackMessage) {
29
+ if (reason instanceof Error) {
30
+ return reason;
31
+ }
32
+ if (typeof reason === "string" && reason.trim().length > 0) {
33
+ return new PoolAbortedError(reason);
34
+ }
35
+ return new PoolAbortedError(fallbackMessage);
36
+ }
37
+ function asNonNegativeInteger(value, name, fallback) {
38
+ if (value === void 0) {
39
+ return fallback;
40
+ }
41
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
42
+ throw new TypeError(`${name} must be a non-negative integer.`);
43
+ }
44
+ return value;
45
+ }
46
+ function asPositiveInteger(value, name, fallback) {
47
+ if (value === void 0) {
48
+ return fallback;
49
+ }
50
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
51
+ throw new TypeError(`${name} must be a positive integer.`);
52
+ }
53
+ return value;
54
+ }
55
+ function asPositiveNumber(value, name, fallback) {
56
+ if (value === void 0) {
57
+ return fallback;
58
+ }
59
+ if (!Number.isFinite(value) || value <= 0) {
60
+ throw new TypeError(`${name} must be a positive number.`);
61
+ }
62
+ return value;
63
+ }
64
+ function asOptionalPositiveInteger(value, name) {
65
+ if (value === void 0) {
66
+ return void 0;
67
+ }
68
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
69
+ throw new TypeError(`${name} must be a positive integer.`);
70
+ }
71
+ return value;
72
+ }
73
+ function normalizeRetryOptions(options) {
74
+ const retries = asNonNegativeInteger(options?.retries, "retries", DEFAULT_RETRY.retries);
75
+ const minDelayMs = asPositiveInteger(options?.minDelayMs, "minDelayMs", DEFAULT_RETRY.minDelayMs);
76
+ const maxDelayMs = asPositiveInteger(options?.maxDelayMs, "maxDelayMs", DEFAULT_RETRY.maxDelayMs);
77
+ const factor = asPositiveNumber(options?.factor, "factor", DEFAULT_RETRY.factor);
78
+ const jitter = options?.jitter ?? DEFAULT_RETRY.jitter;
79
+ if (jitter !== "none" && jitter !== "full") {
80
+ throw new TypeError("jitter must be either 'none' or 'full'.");
81
+ }
82
+ if (minDelayMs > maxDelayMs) {
83
+ throw new TypeError("minDelayMs cannot be larger than maxDelayMs.");
84
+ }
85
+ return {
86
+ retries,
87
+ minDelayMs,
88
+ maxDelayMs,
89
+ factor,
90
+ jitter,
91
+ shouldRetry: options?.shouldRetry ?? DEFAULT_RETRY.shouldRetry
92
+ };
93
+ }
94
+ function computeDelayMs(options, retryAttempt) {
95
+ const exponential = options.minDelayMs * options.factor ** Math.max(retryAttempt - 1, 0);
96
+ const bounded = Math.min(exponential, options.maxDelayMs);
97
+ if (options.jitter === "full") {
98
+ return Math.floor(Math.random() * (bounded + 1));
99
+ }
100
+ return Math.floor(bounded);
101
+ }
102
+ function createLinkedAbortController(signals, fallbackMessage) {
103
+ const controller = new AbortController();
104
+ const listeners = [];
105
+ for (const signal of signals) {
106
+ if (!signal) {
107
+ continue;
108
+ }
109
+ if (signal.aborted) {
110
+ controller.abort(toAbortError(signal.reason, fallbackMessage));
111
+ break;
112
+ }
113
+ const listener = () => {
114
+ if (!controller.signal.aborted) {
115
+ controller.abort(toAbortError(signal.reason, fallbackMessage));
116
+ }
117
+ };
118
+ signal.addEventListener("abort", listener, { once: true });
119
+ listeners.push({ signal, listener });
120
+ }
121
+ const cleanup = () => {
122
+ for (const { signal, listener } of listeners) {
123
+ signal.removeEventListener("abort", listener);
124
+ }
125
+ };
126
+ return { controller, cleanup };
127
+ }
128
+ function createAbortPromise(signal, fallbackMessage) {
129
+ let listener;
130
+ const promise = new Promise((_, reject) => {
131
+ listener = () => reject(toAbortError(signal.reason, fallbackMessage));
132
+ if (signal.aborted) {
133
+ listener();
134
+ return;
135
+ }
136
+ signal.addEventListener("abort", listener, { once: true });
137
+ });
138
+ return {
139
+ promise,
140
+ cleanup: () => {
141
+ if (listener) {
142
+ signal.removeEventListener("abort", listener);
143
+ }
144
+ }
145
+ };
146
+ }
147
+ async function sleep(delayMs, signal, abortMessage) {
148
+ if (delayMs <= 0) {
149
+ return;
150
+ }
151
+ await new Promise((resolve, reject) => {
152
+ if (signal.aborted) {
153
+ reject(toAbortError(signal.reason, abortMessage));
154
+ return;
155
+ }
156
+ const timer = setTimeout(() => {
157
+ signal.removeEventListener("abort", onAbort);
158
+ resolve();
159
+ }, delayMs);
160
+ const onAbort = () => {
161
+ clearTimeout(timer);
162
+ signal.removeEventListener("abort", onAbort);
163
+ reject(toAbortError(signal.reason, abortMessage));
164
+ };
165
+ signal.addEventListener("abort", onAbort, { once: true });
166
+ });
167
+ }
168
+ async function runWithAttemptTimeout(operation, options) {
169
+ const { controller, cleanup } = createLinkedAbortController([options.signal], options.abortMessage);
170
+ let timeoutHandle;
171
+ let timeoutPromise;
172
+ const operationPromise = Promise.resolve().then(() => operation(controller.signal));
173
+ const { promise: abortPromise, cleanup: cleanupAbortPromise } = createAbortPromise(
174
+ controller.signal,
175
+ options.abortMessage
176
+ );
177
+ if (typeof options.timeoutMs === "number") {
178
+ timeoutPromise = new Promise((_, reject) => {
179
+ timeoutHandle = setTimeout(() => {
180
+ const timeoutError = options.timeoutErrorFactory();
181
+ if (!controller.signal.aborted) {
182
+ controller.abort(timeoutError);
183
+ }
184
+ reject(timeoutError);
185
+ }, options.timeoutMs);
186
+ });
187
+ }
188
+ try {
189
+ const contenders = [operationPromise, abortPromise];
190
+ if (timeoutPromise) {
191
+ contenders.push(timeoutPromise);
192
+ }
193
+ return await Promise.race(contenders);
194
+ } finally {
195
+ cleanupAbortPromise();
196
+ cleanup();
197
+ if (timeoutHandle) {
198
+ clearTimeout(timeoutHandle);
199
+ }
200
+ }
201
+ }
202
+ async function retry(operation, options = {}) {
203
+ const normalized = normalizeRetryOptions(options);
204
+ const timeoutMs = asOptionalPositiveInteger(options.timeoutMs, "timeoutMs");
205
+ const { controller, cleanup } = createLinkedAbortController([options.signal], "Retry operation aborted.");
206
+ const maxAttempts = normalized.retries + 1;
207
+ try {
208
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
209
+ try {
210
+ return await runWithAttemptTimeout(
211
+ (signal) => operation(attempt, signal),
212
+ {
213
+ signal: controller.signal,
214
+ timeoutMs,
215
+ timeoutErrorFactory: () => new TaskTimeoutError(-1, timeoutMs ?? 0),
216
+ abortMessage: "Retry operation aborted."
217
+ }
218
+ );
219
+ } catch (error) {
220
+ if (attempt >= maxAttempts) {
221
+ throw error;
222
+ }
223
+ const shouldRetry = await normalized.shouldRetry(error, attempt);
224
+ if (!shouldRetry) {
225
+ throw error;
226
+ }
227
+ const delayMs = computeDelayMs(normalized, attempt);
228
+ options.onRetry?.({
229
+ attempt,
230
+ nextAttempt: attempt + 1,
231
+ delayMs,
232
+ error
233
+ });
234
+ await sleep(delayMs, controller.signal, "Retry wait aborted.");
235
+ }
236
+ }
237
+ } finally {
238
+ cleanup();
239
+ }
240
+ throw new Error("Unreachable retry state.");
241
+ }
242
+ function toPoolResultError(reason) {
243
+ if (reason instanceof Error) {
244
+ return reason;
245
+ }
246
+ if (typeof reason === "string" && reason.trim().length > 0) {
247
+ return new Error(reason);
248
+ }
249
+ return reason;
250
+ }
251
+ function isRejected(result) {
252
+ return result.status === "rejected";
253
+ }
254
+ function isFulfilled(result) {
255
+ return result.status === "fulfilled";
256
+ }
257
+ async function runPool(inputs, worker, options = {}) {
258
+ const items = Array.isArray(inputs) ? inputs.slice() : Array.from(inputs);
259
+ const total = items.length;
260
+ if (total === 0) {
261
+ return {
262
+ total: 0,
263
+ completed: 0,
264
+ succeeded: 0,
265
+ failed: 0,
266
+ hasErrors: false,
267
+ results: [],
268
+ fulfilled: [],
269
+ rejected: []
270
+ };
271
+ }
272
+ const retryOptions = normalizeRetryOptions(options.retry);
273
+ const timeoutMs = asOptionalPositiveInteger(options.timeoutMs, "timeoutMs");
274
+ const stopOnError = options.stopOnError ?? false;
275
+ const concurrency = Math.min(asPositiveInteger(options.concurrency, "concurrency", 5), total);
276
+ const { controller: poolController, cleanup } = createLinkedAbortController(
277
+ [options.signal],
278
+ "Pool execution aborted."
279
+ );
280
+ const results = new Array(total);
281
+ let nextIndex = 0;
282
+ let running = 0;
283
+ let completed = 0;
284
+ let succeeded = 0;
285
+ let failed = 0;
286
+ const emitProgress = (lastResult) => {
287
+ options.onProgress?.({
288
+ total,
289
+ completed,
290
+ succeeded,
291
+ failed,
292
+ running,
293
+ pending: Math.max(total - completed - running, 0),
294
+ percentage: Math.round(completed / total * 100),
295
+ lastResult
296
+ });
297
+ };
298
+ const runSingle = async (index) => {
299
+ const input = items[index];
300
+ const startedAt = Date.now();
301
+ let attempts = 0;
302
+ let settled;
303
+ running += 1;
304
+ try {
305
+ const value = await retry(
306
+ async (attempt, signal) => {
307
+ attempts = attempt;
308
+ return worker(input, { index, attempt, signal });
309
+ },
310
+ {
311
+ ...retryOptions,
312
+ signal: poolController.signal,
313
+ timeoutMs
314
+ }
315
+ );
316
+ settled = {
317
+ status: "fulfilled",
318
+ index,
319
+ input,
320
+ value,
321
+ attempts,
322
+ durationMs: Date.now() - startedAt
323
+ };
324
+ succeeded += 1;
325
+ completed += 1;
326
+ results[index] = settled;
327
+ } catch (error) {
328
+ settled = {
329
+ status: "rejected",
330
+ index,
331
+ input,
332
+ reason: toPoolResultError(error),
333
+ attempts,
334
+ durationMs: Date.now() - startedAt
335
+ };
336
+ failed += 1;
337
+ completed += 1;
338
+ results[index] = settled;
339
+ if (stopOnError && !poolController.signal.aborted) {
340
+ poolController.abort(new PoolAbortedError(`Pool aborted because task ${index} failed.`));
341
+ }
342
+ } finally {
343
+ running -= 1;
344
+ if (settled) {
345
+ emitProgress(settled);
346
+ }
347
+ }
348
+ };
349
+ const runner = async () => {
350
+ while (!poolController.signal.aborted) {
351
+ const index = nextIndex;
352
+ if (index >= total) {
353
+ return;
354
+ }
355
+ nextIndex += 1;
356
+ await runSingle(index);
357
+ }
358
+ };
359
+ try {
360
+ await Promise.all(Array.from({ length: concurrency }, () => runner()));
361
+ } finally {
362
+ cleanup();
363
+ }
364
+ if (poolController.signal.aborted) {
365
+ const abortError = toAbortError(poolController.signal.reason, "Pool execution aborted.");
366
+ for (let index = 0; index < total; index += 1) {
367
+ if (results[index]) {
368
+ continue;
369
+ }
370
+ const settled = {
371
+ status: "rejected",
372
+ index,
373
+ input: items[index],
374
+ reason: abortError,
375
+ attempts: 0,
376
+ durationMs: 0
377
+ };
378
+ results[index] = settled;
379
+ failed += 1;
380
+ completed += 1;
381
+ emitProgress(settled);
382
+ }
383
+ }
384
+ const settledResults = results.map((result, index) => {
385
+ if (result) {
386
+ return result;
387
+ }
388
+ return {
389
+ status: "rejected",
390
+ index,
391
+ input: items[index],
392
+ reason: new PoolAbortedError("Task did not execute."),
393
+ attempts: 0,
394
+ durationMs: 0
395
+ };
396
+ });
397
+ const fulfilled = settledResults.filter(isFulfilled);
398
+ const rejected = settledResults.filter(isRejected);
399
+ return {
400
+ total,
401
+ completed: settledResults.length,
402
+ succeeded: fulfilled.length,
403
+ failed: rejected.length,
404
+ hasErrors: rejected.length > 0,
405
+ results: settledResults,
406
+ fulfilled,
407
+ rejected
408
+ };
409
+ }
410
+ function assertPoolSuccess(result) {
411
+ if (!result.hasErrors) {
412
+ return;
413
+ }
414
+ const reasons = result.rejected.map((entry) => entry.reason);
415
+ throw new AggregateError(reasons, `${result.failed} task(s) failed in runPool.`);
416
+ }
417
+
418
+ exports.PoolAbortedError = PoolAbortedError;
419
+ exports.TaskTimeoutError = TaskTimeoutError;
420
+ exports.assertPoolSuccess = assertPoolSuccess;
421
+ exports.retry = retry;
422
+ exports.runPool = runPool;
423
+ //# sourceMappingURL=index.cjs.map
424
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["export type RetryJitter = \"none\" | \"full\";\n\nexport interface BaseRetryOptions {\n retries?: number;\n minDelayMs?: number;\n maxDelayMs?: number;\n factor?: number;\n jitter?: RetryJitter;\n shouldRetry?: (error: unknown, attempt: number) => boolean | Promise<boolean>;\n}\n\nexport interface RetryOptions extends BaseRetryOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n onRetry?: (event: RetryEvent) => void;\n}\n\nexport interface RetryEvent {\n attempt: number;\n nextAttempt: number;\n delayMs: number;\n error: unknown;\n}\n\nexport interface PoolWorkerContext {\n index: number;\n attempt: number;\n signal: AbortSignal;\n}\n\nexport interface PoolOptions<TInput, TResult> {\n concurrency?: number;\n retry?: BaseRetryOptions;\n timeoutMs?: number;\n signal?: AbortSignal;\n stopOnError?: boolean;\n onProgress?: (progress: PoolProgress<TInput, TResult>) => void;\n}\n\ninterface NormalizedRetryOptions {\n retries: number;\n minDelayMs: number;\n maxDelayMs: number;\n factor: number;\n jitter: RetryJitter;\n shouldRetry: (error: unknown, attempt: number) => boolean | Promise<boolean>;\n}\n\nexport interface PoolBaseResult<TInput> {\n status: \"fulfilled\" | \"rejected\";\n index: number;\n input: TInput;\n attempts: number;\n durationMs: number;\n}\n\nexport interface PoolFulfilledResult<TInput, TResult> extends PoolBaseResult<TInput> {\n status: \"fulfilled\";\n value: TResult;\n}\n\nexport interface PoolRejectedResult<TInput> extends PoolBaseResult<TInput> {\n status: \"rejected\";\n reason: unknown;\n}\n\nexport type PoolSettledResult<TInput, TResult> =\n | PoolFulfilledResult<TInput, TResult>\n | PoolRejectedResult<TInput>;\n\nexport interface PoolProgress<TInput, TResult> {\n total: number;\n completed: number;\n succeeded: number;\n failed: number;\n running: number;\n pending: number;\n percentage: number;\n lastResult?: PoolSettledResult<TInput, TResult>;\n}\n\nexport interface PoolRunResult<TInput, TResult> {\n total: number;\n completed: number;\n succeeded: number;\n failed: number;\n hasErrors: boolean;\n results: Array<PoolSettledResult<TInput, TResult>>;\n fulfilled: Array<PoolFulfilledResult<TInput, TResult>>;\n rejected: Array<PoolRejectedResult<TInput>>;\n}\n\nexport class PoolAbortedError extends Error {\n constructor(message = \"Operation aborted.\") {\n super(message);\n this.name = \"PoolAbortedError\";\n }\n}\n\nexport class TaskTimeoutError extends Error {\n readonly taskIndex: number;\n readonly timeoutMs: number;\n\n constructor(taskIndex: number, timeoutMs: number) {\n super(\n taskIndex >= 0\n ? `Task ${taskIndex} timed out after ${timeoutMs}ms.`\n : `Operation timed out after ${timeoutMs}ms.`\n );\n this.name = \"TaskTimeoutError\";\n this.taskIndex = taskIndex;\n this.timeoutMs = timeoutMs;\n }\n}\n\nconst DEFAULT_RETRY: NormalizedRetryOptions = {\n retries: 0,\n minDelayMs: 100,\n maxDelayMs: 5000,\n factor: 2,\n jitter: \"none\",\n shouldRetry: (error: unknown) => !(error instanceof PoolAbortedError)\n};\n\nfunction toAbortError(reason: unknown, fallbackMessage: string): Error {\n if (reason instanceof Error) {\n return reason;\n }\n if (typeof reason === \"string\" && reason.trim().length > 0) {\n return new PoolAbortedError(reason);\n }\n return new PoolAbortedError(fallbackMessage);\n}\n\nfunction asNonNegativeInteger(value: number | undefined, name: string, fallback: number): number {\n if (value === undefined) {\n return fallback;\n }\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {\n throw new TypeError(`${name} must be a non-negative integer.`);\n }\n return value;\n}\n\nfunction asPositiveInteger(value: number | undefined, name: string, fallback: number): number {\n if (value === undefined) {\n return fallback;\n }\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {\n throw new TypeError(`${name} must be a positive integer.`);\n }\n return value;\n}\n\nfunction asPositiveNumber(value: number | undefined, name: string, fallback: number): number {\n if (value === undefined) {\n return fallback;\n }\n if (!Number.isFinite(value) || value <= 0) {\n throw new TypeError(`${name} must be a positive number.`);\n }\n return value;\n}\n\nfunction asOptionalPositiveInteger(value: number | undefined, name: string): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {\n throw new TypeError(`${name} must be a positive integer.`);\n }\n return value;\n}\n\nfunction normalizeRetryOptions(options: BaseRetryOptions | undefined): NormalizedRetryOptions {\n const retries = asNonNegativeInteger(options?.retries, \"retries\", DEFAULT_RETRY.retries);\n const minDelayMs = asPositiveInteger(options?.minDelayMs, \"minDelayMs\", DEFAULT_RETRY.minDelayMs);\n const maxDelayMs = asPositiveInteger(options?.maxDelayMs, \"maxDelayMs\", DEFAULT_RETRY.maxDelayMs);\n const factor = asPositiveNumber(options?.factor, \"factor\", DEFAULT_RETRY.factor);\n const jitter = options?.jitter ?? DEFAULT_RETRY.jitter;\n\n if (jitter !== \"none\" && jitter !== \"full\") {\n throw new TypeError(\"jitter must be either 'none' or 'full'.\");\n }\n if (minDelayMs > maxDelayMs) {\n throw new TypeError(\"minDelayMs cannot be larger than maxDelayMs.\");\n }\n\n return {\n retries,\n minDelayMs,\n maxDelayMs,\n factor,\n jitter,\n shouldRetry: options?.shouldRetry ?? DEFAULT_RETRY.shouldRetry\n };\n}\n\nfunction computeDelayMs(options: NormalizedRetryOptions, retryAttempt: number): number {\n const exponential = options.minDelayMs * options.factor ** Math.max(retryAttempt - 1, 0);\n const bounded = Math.min(exponential, options.maxDelayMs);\n if (options.jitter === \"full\") {\n return Math.floor(Math.random() * (bounded + 1));\n }\n return Math.floor(bounded);\n}\n\nfunction createLinkedAbortController(\n signals: Array<AbortSignal | undefined>,\n fallbackMessage: string\n): { controller: AbortController; cleanup: () => void } {\n const controller = new AbortController();\n const listeners: Array<{ signal: AbortSignal; listener: () => void }> = [];\n\n for (const signal of signals) {\n if (!signal) {\n continue;\n }\n if (signal.aborted) {\n controller.abort(toAbortError(signal.reason, fallbackMessage));\n break;\n }\n const listener = (): void => {\n if (!controller.signal.aborted) {\n controller.abort(toAbortError(signal.reason, fallbackMessage));\n }\n };\n signal.addEventListener(\"abort\", listener, { once: true });\n listeners.push({ signal, listener });\n }\n\n const cleanup = (): void => {\n for (const { signal, listener } of listeners) {\n signal.removeEventListener(\"abort\", listener);\n }\n };\n\n return { controller, cleanup };\n}\n\nfunction createAbortPromise(\n signal: AbortSignal,\n fallbackMessage: string\n): { promise: Promise<never>; cleanup: () => void } {\n let listener: (() => void) | undefined;\n const promise = new Promise<never>((_, reject) => {\n listener = (): void => reject(toAbortError(signal.reason, fallbackMessage));\n if (signal.aborted) {\n listener();\n return;\n }\n signal.addEventListener(\"abort\", listener, { once: true });\n });\n\n return {\n promise,\n cleanup: (): void => {\n if (listener) {\n signal.removeEventListener(\"abort\", listener);\n }\n }\n };\n}\n\nasync function sleep(delayMs: number, signal: AbortSignal, abortMessage: string): Promise<void> {\n if (delayMs <= 0) {\n return;\n }\n await new Promise<void>((resolve, reject) => {\n if (signal.aborted) {\n reject(toAbortError(signal.reason, abortMessage));\n return;\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, delayMs);\n\n const onAbort = (): void => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", onAbort);\n reject(toAbortError(signal.reason, abortMessage));\n };\n\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nasync function runWithAttemptTimeout<T>(\n operation: (signal: AbortSignal) => Promise<T>,\n options: {\n signal: AbortSignal;\n timeoutMs?: number;\n timeoutErrorFactory: () => Error;\n abortMessage: string;\n }\n): Promise<T> {\n const { controller, cleanup } = createLinkedAbortController([options.signal], options.abortMessage);\n let timeoutHandle: NodeJS.Timeout | undefined;\n let timeoutPromise: Promise<never> | undefined;\n const operationPromise = Promise.resolve().then(() => operation(controller.signal));\n const { promise: abortPromise, cleanup: cleanupAbortPromise } = createAbortPromise(\n controller.signal,\n options.abortMessage\n );\n\n if (typeof options.timeoutMs === \"number\") {\n timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => {\n const timeoutError = options.timeoutErrorFactory();\n if (!controller.signal.aborted) {\n controller.abort(timeoutError);\n }\n reject(timeoutError);\n }, options.timeoutMs);\n });\n }\n\n try {\n const contenders: Array<Promise<T>> = [operationPromise, abortPromise as Promise<T>];\n if (timeoutPromise) {\n contenders.push(timeoutPromise as Promise<T>);\n }\n return await Promise.race(contenders);\n } finally {\n cleanupAbortPromise();\n cleanup();\n if (timeoutHandle) {\n clearTimeout(timeoutHandle);\n }\n }\n}\n\nexport async function retry<T>(\n operation: (attempt: number, signal: AbortSignal) => Promise<T>,\n options: RetryOptions = {}\n): Promise<T> {\n const normalized = normalizeRetryOptions(options);\n const timeoutMs = asOptionalPositiveInteger(options.timeoutMs, \"timeoutMs\");\n const { controller, cleanup } = createLinkedAbortController([options.signal], \"Retry operation aborted.\");\n const maxAttempts = normalized.retries + 1;\n\n try {\n for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n try {\n return await runWithAttemptTimeout(\n (signal) => operation(attempt, signal),\n {\n signal: controller.signal,\n timeoutMs,\n timeoutErrorFactory: () => new TaskTimeoutError(-1, timeoutMs ?? 0),\n abortMessage: \"Retry operation aborted.\"\n }\n );\n } catch (error) {\n if (attempt >= maxAttempts) {\n throw error;\n }\n const shouldRetry = await normalized.shouldRetry(error, attempt);\n if (!shouldRetry) {\n throw error;\n }\n const delayMs = computeDelayMs(normalized, attempt);\n options.onRetry?.({\n attempt,\n nextAttempt: attempt + 1,\n delayMs,\n error\n });\n await sleep(delayMs, controller.signal, \"Retry wait aborted.\");\n }\n }\n } finally {\n cleanup();\n }\n\n throw new Error(\"Unreachable retry state.\");\n}\n\nfunction toPoolResultError(reason: unknown): unknown {\n if (reason instanceof Error) {\n return reason;\n }\n if (typeof reason === \"string\" && reason.trim().length > 0) {\n return new Error(reason);\n }\n return reason;\n}\n\nfunction isRejected<TInput, TResult>(\n result: PoolSettledResult<TInput, TResult>\n): result is PoolRejectedResult<TInput> {\n return result.status === \"rejected\";\n}\n\nfunction isFulfilled<TInput, TResult>(\n result: PoolSettledResult<TInput, TResult>\n): result is PoolFulfilledResult<TInput, TResult> {\n return result.status === \"fulfilled\";\n}\n\nexport async function runPool<TInput, TResult>(\n inputs: Iterable<TInput>,\n worker: (input: TInput, context: PoolWorkerContext) => Promise<TResult>,\n options: PoolOptions<TInput, TResult> = {}\n): Promise<PoolRunResult<TInput, TResult>> {\n const items = Array.isArray(inputs) ? inputs.slice() : Array.from(inputs);\n const total = items.length;\n\n if (total === 0) {\n return {\n total: 0,\n completed: 0,\n succeeded: 0,\n failed: 0,\n hasErrors: false,\n results: [],\n fulfilled: [],\n rejected: []\n };\n }\n\n const retryOptions = normalizeRetryOptions(options.retry);\n const timeoutMs = asOptionalPositiveInteger(options.timeoutMs, \"timeoutMs\");\n const stopOnError = options.stopOnError ?? false;\n const concurrency = Math.min(asPositiveInteger(options.concurrency, \"concurrency\", 5), total);\n const { controller: poolController, cleanup } = createLinkedAbortController(\n [options.signal],\n \"Pool execution aborted.\"\n );\n\n const results = new Array<PoolSettledResult<TInput, TResult> | undefined>(total);\n let nextIndex = 0;\n let running = 0;\n let completed = 0;\n let succeeded = 0;\n let failed = 0;\n\n const emitProgress = (lastResult?: PoolSettledResult<TInput, TResult>): void => {\n options.onProgress?.({\n total,\n completed,\n succeeded,\n failed,\n running,\n pending: Math.max(total - completed - running, 0),\n percentage: Math.round((completed / total) * 100),\n lastResult\n });\n };\n\n const runSingle = async (index: number): Promise<void> => {\n const input = items[index];\n const startedAt = Date.now();\n let attempts = 0;\n let settled: PoolSettledResult<TInput, TResult> | undefined;\n running += 1;\n\n try {\n const value = await retry(\n async (attempt, signal) => {\n attempts = attempt;\n return worker(input, { index, attempt, signal });\n },\n {\n ...retryOptions,\n signal: poolController.signal,\n timeoutMs\n }\n );\n\n settled = {\n status: \"fulfilled\",\n index,\n input,\n value,\n attempts,\n durationMs: Date.now() - startedAt\n };\n succeeded += 1;\n completed += 1;\n results[index] = settled;\n } catch (error) {\n settled = {\n status: \"rejected\",\n index,\n input,\n reason: toPoolResultError(error),\n attempts,\n durationMs: Date.now() - startedAt\n };\n failed += 1;\n completed += 1;\n results[index] = settled;\n\n if (stopOnError && !poolController.signal.aborted) {\n poolController.abort(new PoolAbortedError(`Pool aborted because task ${index} failed.`));\n }\n } finally {\n running -= 1;\n if (settled) {\n emitProgress(settled);\n }\n }\n };\n\n const runner = async (): Promise<void> => {\n while (!poolController.signal.aborted) {\n const index = nextIndex;\n if (index >= total) {\n return;\n }\n nextIndex += 1;\n await runSingle(index);\n }\n };\n\n try {\n await Promise.all(Array.from({ length: concurrency }, () => runner()));\n } finally {\n cleanup();\n }\n\n if (poolController.signal.aborted) {\n const abortError = toAbortError(poolController.signal.reason, \"Pool execution aborted.\");\n for (let index = 0; index < total; index += 1) {\n if (results[index]) {\n continue;\n }\n const settled: PoolRejectedResult<TInput> = {\n status: \"rejected\",\n index,\n input: items[index],\n reason: abortError,\n attempts: 0,\n durationMs: 0\n };\n results[index] = settled;\n failed += 1;\n completed += 1;\n emitProgress(settled);\n }\n }\n\n const settledResults = results.map((result, index) => {\n if (result) {\n return result;\n }\n return {\n status: \"rejected\",\n index,\n input: items[index],\n reason: new PoolAbortedError(\"Task did not execute.\"),\n attempts: 0,\n durationMs: 0\n } satisfies PoolRejectedResult<TInput>;\n });\n\n const fulfilled = settledResults.filter(isFulfilled);\n const rejected = settledResults.filter(isRejected);\n\n return {\n total,\n completed: settledResults.length,\n succeeded: fulfilled.length,\n failed: rejected.length,\n hasErrors: rejected.length > 0,\n results: settledResults,\n fulfilled,\n rejected\n };\n}\n\nexport function assertPoolSuccess<TInput, TResult>(result: PoolRunResult<TInput, TResult>): void {\n if (!result.hasErrors) {\n return;\n }\n const reasons = result.rejected.map((entry) => entry.reason);\n throw new AggregateError(reasons, `${result.failed} task(s) failed in runPool.`);\n}\n"]}
@@ -0,0 +1,82 @@
1
+ type RetryJitter = "none" | "full";
2
+ interface BaseRetryOptions {
3
+ retries?: number;
4
+ minDelayMs?: number;
5
+ maxDelayMs?: number;
6
+ factor?: number;
7
+ jitter?: RetryJitter;
8
+ shouldRetry?: (error: unknown, attempt: number) => boolean | Promise<boolean>;
9
+ }
10
+ interface RetryOptions extends BaseRetryOptions {
11
+ signal?: AbortSignal;
12
+ timeoutMs?: number;
13
+ onRetry?: (event: RetryEvent) => void;
14
+ }
15
+ interface RetryEvent {
16
+ attempt: number;
17
+ nextAttempt: number;
18
+ delayMs: number;
19
+ error: unknown;
20
+ }
21
+ interface PoolWorkerContext {
22
+ index: number;
23
+ attempt: number;
24
+ signal: AbortSignal;
25
+ }
26
+ interface PoolOptions<TInput, TResult> {
27
+ concurrency?: number;
28
+ retry?: BaseRetryOptions;
29
+ timeoutMs?: number;
30
+ signal?: AbortSignal;
31
+ stopOnError?: boolean;
32
+ onProgress?: (progress: PoolProgress<TInput, TResult>) => void;
33
+ }
34
+ interface PoolBaseResult<TInput> {
35
+ status: "fulfilled" | "rejected";
36
+ index: number;
37
+ input: TInput;
38
+ attempts: number;
39
+ durationMs: number;
40
+ }
41
+ interface PoolFulfilledResult<TInput, TResult> extends PoolBaseResult<TInput> {
42
+ status: "fulfilled";
43
+ value: TResult;
44
+ }
45
+ interface PoolRejectedResult<TInput> extends PoolBaseResult<TInput> {
46
+ status: "rejected";
47
+ reason: unknown;
48
+ }
49
+ type PoolSettledResult<TInput, TResult> = PoolFulfilledResult<TInput, TResult> | PoolRejectedResult<TInput>;
50
+ interface PoolProgress<TInput, TResult> {
51
+ total: number;
52
+ completed: number;
53
+ succeeded: number;
54
+ failed: number;
55
+ running: number;
56
+ pending: number;
57
+ percentage: number;
58
+ lastResult?: PoolSettledResult<TInput, TResult>;
59
+ }
60
+ interface PoolRunResult<TInput, TResult> {
61
+ total: number;
62
+ completed: number;
63
+ succeeded: number;
64
+ failed: number;
65
+ hasErrors: boolean;
66
+ results: Array<PoolSettledResult<TInput, TResult>>;
67
+ fulfilled: Array<PoolFulfilledResult<TInput, TResult>>;
68
+ rejected: Array<PoolRejectedResult<TInput>>;
69
+ }
70
+ declare class PoolAbortedError extends Error {
71
+ constructor(message?: string);
72
+ }
73
+ declare class TaskTimeoutError extends Error {
74
+ readonly taskIndex: number;
75
+ readonly timeoutMs: number;
76
+ constructor(taskIndex: number, timeoutMs: number);
77
+ }
78
+ declare function retry<T>(operation: (attempt: number, signal: AbortSignal) => Promise<T>, options?: RetryOptions): Promise<T>;
79
+ declare function runPool<TInput, TResult>(inputs: Iterable<TInput>, worker: (input: TInput, context: PoolWorkerContext) => Promise<TResult>, options?: PoolOptions<TInput, TResult>): Promise<PoolRunResult<TInput, TResult>>;
80
+ declare function assertPoolSuccess<TInput, TResult>(result: PoolRunResult<TInput, TResult>): void;
81
+
82
+ export { type BaseRetryOptions, PoolAbortedError, type PoolBaseResult, type PoolFulfilledResult, type PoolOptions, type PoolProgress, type PoolRejectedResult, type PoolRunResult, type PoolSettledResult, type PoolWorkerContext, type RetryEvent, type RetryJitter, type RetryOptions, TaskTimeoutError, assertPoolSuccess, retry, runPool };
@@ -0,0 +1,82 @@
1
+ type RetryJitter = "none" | "full";
2
+ interface BaseRetryOptions {
3
+ retries?: number;
4
+ minDelayMs?: number;
5
+ maxDelayMs?: number;
6
+ factor?: number;
7
+ jitter?: RetryJitter;
8
+ shouldRetry?: (error: unknown, attempt: number) => boolean | Promise<boolean>;
9
+ }
10
+ interface RetryOptions extends BaseRetryOptions {
11
+ signal?: AbortSignal;
12
+ timeoutMs?: number;
13
+ onRetry?: (event: RetryEvent) => void;
14
+ }
15
+ interface RetryEvent {
16
+ attempt: number;
17
+ nextAttempt: number;
18
+ delayMs: number;
19
+ error: unknown;
20
+ }
21
+ interface PoolWorkerContext {
22
+ index: number;
23
+ attempt: number;
24
+ signal: AbortSignal;
25
+ }
26
+ interface PoolOptions<TInput, TResult> {
27
+ concurrency?: number;
28
+ retry?: BaseRetryOptions;
29
+ timeoutMs?: number;
30
+ signal?: AbortSignal;
31
+ stopOnError?: boolean;
32
+ onProgress?: (progress: PoolProgress<TInput, TResult>) => void;
33
+ }
34
+ interface PoolBaseResult<TInput> {
35
+ status: "fulfilled" | "rejected";
36
+ index: number;
37
+ input: TInput;
38
+ attempts: number;
39
+ durationMs: number;
40
+ }
41
+ interface PoolFulfilledResult<TInput, TResult> extends PoolBaseResult<TInput> {
42
+ status: "fulfilled";
43
+ value: TResult;
44
+ }
45
+ interface PoolRejectedResult<TInput> extends PoolBaseResult<TInput> {
46
+ status: "rejected";
47
+ reason: unknown;
48
+ }
49
+ type PoolSettledResult<TInput, TResult> = PoolFulfilledResult<TInput, TResult> | PoolRejectedResult<TInput>;
50
+ interface PoolProgress<TInput, TResult> {
51
+ total: number;
52
+ completed: number;
53
+ succeeded: number;
54
+ failed: number;
55
+ running: number;
56
+ pending: number;
57
+ percentage: number;
58
+ lastResult?: PoolSettledResult<TInput, TResult>;
59
+ }
60
+ interface PoolRunResult<TInput, TResult> {
61
+ total: number;
62
+ completed: number;
63
+ succeeded: number;
64
+ failed: number;
65
+ hasErrors: boolean;
66
+ results: Array<PoolSettledResult<TInput, TResult>>;
67
+ fulfilled: Array<PoolFulfilledResult<TInput, TResult>>;
68
+ rejected: Array<PoolRejectedResult<TInput>>;
69
+ }
70
+ declare class PoolAbortedError extends Error {
71
+ constructor(message?: string);
72
+ }
73
+ declare class TaskTimeoutError extends Error {
74
+ readonly taskIndex: number;
75
+ readonly timeoutMs: number;
76
+ constructor(taskIndex: number, timeoutMs: number);
77
+ }
78
+ declare function retry<T>(operation: (attempt: number, signal: AbortSignal) => Promise<T>, options?: RetryOptions): Promise<T>;
79
+ declare function runPool<TInput, TResult>(inputs: Iterable<TInput>, worker: (input: TInput, context: PoolWorkerContext) => Promise<TResult>, options?: PoolOptions<TInput, TResult>): Promise<PoolRunResult<TInput, TResult>>;
80
+ declare function assertPoolSuccess<TInput, TResult>(result: PoolRunResult<TInput, TResult>): void;
81
+
82
+ export { type BaseRetryOptions, PoolAbortedError, type PoolBaseResult, type PoolFulfilledResult, type PoolOptions, type PoolProgress, type PoolRejectedResult, type PoolRunResult, type PoolSettledResult, type PoolWorkerContext, type RetryEvent, type RetryJitter, type RetryOptions, TaskTimeoutError, assertPoolSuccess, retry, runPool };
package/dist/index.js ADDED
@@ -0,0 +1,418 @@
1
+ // src/index.ts
2
+ var PoolAbortedError = class extends Error {
3
+ constructor(message = "Operation aborted.") {
4
+ super(message);
5
+ this.name = "PoolAbortedError";
6
+ }
7
+ };
8
+ var TaskTimeoutError = class extends Error {
9
+ constructor(taskIndex, timeoutMs) {
10
+ super(
11
+ taskIndex >= 0 ? `Task ${taskIndex} timed out after ${timeoutMs}ms.` : `Operation timed out after ${timeoutMs}ms.`
12
+ );
13
+ this.name = "TaskTimeoutError";
14
+ this.taskIndex = taskIndex;
15
+ this.timeoutMs = timeoutMs;
16
+ }
17
+ };
18
+ var DEFAULT_RETRY = {
19
+ retries: 0,
20
+ minDelayMs: 100,
21
+ maxDelayMs: 5e3,
22
+ factor: 2,
23
+ jitter: "none",
24
+ shouldRetry: (error) => !(error instanceof PoolAbortedError)
25
+ };
26
+ function toAbortError(reason, fallbackMessage) {
27
+ if (reason instanceof Error) {
28
+ return reason;
29
+ }
30
+ if (typeof reason === "string" && reason.trim().length > 0) {
31
+ return new PoolAbortedError(reason);
32
+ }
33
+ return new PoolAbortedError(fallbackMessage);
34
+ }
35
+ function asNonNegativeInteger(value, name, fallback) {
36
+ if (value === void 0) {
37
+ return fallback;
38
+ }
39
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {
40
+ throw new TypeError(`${name} must be a non-negative integer.`);
41
+ }
42
+ return value;
43
+ }
44
+ function asPositiveInteger(value, name, fallback) {
45
+ if (value === void 0) {
46
+ return fallback;
47
+ }
48
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
49
+ throw new TypeError(`${name} must be a positive integer.`);
50
+ }
51
+ return value;
52
+ }
53
+ function asPositiveNumber(value, name, fallback) {
54
+ if (value === void 0) {
55
+ return fallback;
56
+ }
57
+ if (!Number.isFinite(value) || value <= 0) {
58
+ throw new TypeError(`${name} must be a positive number.`);
59
+ }
60
+ return value;
61
+ }
62
+ function asOptionalPositiveInteger(value, name) {
63
+ if (value === void 0) {
64
+ return void 0;
65
+ }
66
+ if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {
67
+ throw new TypeError(`${name} must be a positive integer.`);
68
+ }
69
+ return value;
70
+ }
71
+ function normalizeRetryOptions(options) {
72
+ const retries = asNonNegativeInteger(options?.retries, "retries", DEFAULT_RETRY.retries);
73
+ const minDelayMs = asPositiveInteger(options?.minDelayMs, "minDelayMs", DEFAULT_RETRY.minDelayMs);
74
+ const maxDelayMs = asPositiveInteger(options?.maxDelayMs, "maxDelayMs", DEFAULT_RETRY.maxDelayMs);
75
+ const factor = asPositiveNumber(options?.factor, "factor", DEFAULT_RETRY.factor);
76
+ const jitter = options?.jitter ?? DEFAULT_RETRY.jitter;
77
+ if (jitter !== "none" && jitter !== "full") {
78
+ throw new TypeError("jitter must be either 'none' or 'full'.");
79
+ }
80
+ if (minDelayMs > maxDelayMs) {
81
+ throw new TypeError("minDelayMs cannot be larger than maxDelayMs.");
82
+ }
83
+ return {
84
+ retries,
85
+ minDelayMs,
86
+ maxDelayMs,
87
+ factor,
88
+ jitter,
89
+ shouldRetry: options?.shouldRetry ?? DEFAULT_RETRY.shouldRetry
90
+ };
91
+ }
92
+ function computeDelayMs(options, retryAttempt) {
93
+ const exponential = options.minDelayMs * options.factor ** Math.max(retryAttempt - 1, 0);
94
+ const bounded = Math.min(exponential, options.maxDelayMs);
95
+ if (options.jitter === "full") {
96
+ return Math.floor(Math.random() * (bounded + 1));
97
+ }
98
+ return Math.floor(bounded);
99
+ }
100
+ function createLinkedAbortController(signals, fallbackMessage) {
101
+ const controller = new AbortController();
102
+ const listeners = [];
103
+ for (const signal of signals) {
104
+ if (!signal) {
105
+ continue;
106
+ }
107
+ if (signal.aborted) {
108
+ controller.abort(toAbortError(signal.reason, fallbackMessage));
109
+ break;
110
+ }
111
+ const listener = () => {
112
+ if (!controller.signal.aborted) {
113
+ controller.abort(toAbortError(signal.reason, fallbackMessage));
114
+ }
115
+ };
116
+ signal.addEventListener("abort", listener, { once: true });
117
+ listeners.push({ signal, listener });
118
+ }
119
+ const cleanup = () => {
120
+ for (const { signal, listener } of listeners) {
121
+ signal.removeEventListener("abort", listener);
122
+ }
123
+ };
124
+ return { controller, cleanup };
125
+ }
126
+ function createAbortPromise(signal, fallbackMessage) {
127
+ let listener;
128
+ const promise = new Promise((_, reject) => {
129
+ listener = () => reject(toAbortError(signal.reason, fallbackMessage));
130
+ if (signal.aborted) {
131
+ listener();
132
+ return;
133
+ }
134
+ signal.addEventListener("abort", listener, { once: true });
135
+ });
136
+ return {
137
+ promise,
138
+ cleanup: () => {
139
+ if (listener) {
140
+ signal.removeEventListener("abort", listener);
141
+ }
142
+ }
143
+ };
144
+ }
145
+ async function sleep(delayMs, signal, abortMessage) {
146
+ if (delayMs <= 0) {
147
+ return;
148
+ }
149
+ await new Promise((resolve, reject) => {
150
+ if (signal.aborted) {
151
+ reject(toAbortError(signal.reason, abortMessage));
152
+ return;
153
+ }
154
+ const timer = setTimeout(() => {
155
+ signal.removeEventListener("abort", onAbort);
156
+ resolve();
157
+ }, delayMs);
158
+ const onAbort = () => {
159
+ clearTimeout(timer);
160
+ signal.removeEventListener("abort", onAbort);
161
+ reject(toAbortError(signal.reason, abortMessage));
162
+ };
163
+ signal.addEventListener("abort", onAbort, { once: true });
164
+ });
165
+ }
166
+ async function runWithAttemptTimeout(operation, options) {
167
+ const { controller, cleanup } = createLinkedAbortController([options.signal], options.abortMessage);
168
+ let timeoutHandle;
169
+ let timeoutPromise;
170
+ const operationPromise = Promise.resolve().then(() => operation(controller.signal));
171
+ const { promise: abortPromise, cleanup: cleanupAbortPromise } = createAbortPromise(
172
+ controller.signal,
173
+ options.abortMessage
174
+ );
175
+ if (typeof options.timeoutMs === "number") {
176
+ timeoutPromise = new Promise((_, reject) => {
177
+ timeoutHandle = setTimeout(() => {
178
+ const timeoutError = options.timeoutErrorFactory();
179
+ if (!controller.signal.aborted) {
180
+ controller.abort(timeoutError);
181
+ }
182
+ reject(timeoutError);
183
+ }, options.timeoutMs);
184
+ });
185
+ }
186
+ try {
187
+ const contenders = [operationPromise, abortPromise];
188
+ if (timeoutPromise) {
189
+ contenders.push(timeoutPromise);
190
+ }
191
+ return await Promise.race(contenders);
192
+ } finally {
193
+ cleanupAbortPromise();
194
+ cleanup();
195
+ if (timeoutHandle) {
196
+ clearTimeout(timeoutHandle);
197
+ }
198
+ }
199
+ }
200
+ async function retry(operation, options = {}) {
201
+ const normalized = normalizeRetryOptions(options);
202
+ const timeoutMs = asOptionalPositiveInteger(options.timeoutMs, "timeoutMs");
203
+ const { controller, cleanup } = createLinkedAbortController([options.signal], "Retry operation aborted.");
204
+ const maxAttempts = normalized.retries + 1;
205
+ try {
206
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
207
+ try {
208
+ return await runWithAttemptTimeout(
209
+ (signal) => operation(attempt, signal),
210
+ {
211
+ signal: controller.signal,
212
+ timeoutMs,
213
+ timeoutErrorFactory: () => new TaskTimeoutError(-1, timeoutMs ?? 0),
214
+ abortMessage: "Retry operation aborted."
215
+ }
216
+ );
217
+ } catch (error) {
218
+ if (attempt >= maxAttempts) {
219
+ throw error;
220
+ }
221
+ const shouldRetry = await normalized.shouldRetry(error, attempt);
222
+ if (!shouldRetry) {
223
+ throw error;
224
+ }
225
+ const delayMs = computeDelayMs(normalized, attempt);
226
+ options.onRetry?.({
227
+ attempt,
228
+ nextAttempt: attempt + 1,
229
+ delayMs,
230
+ error
231
+ });
232
+ await sleep(delayMs, controller.signal, "Retry wait aborted.");
233
+ }
234
+ }
235
+ } finally {
236
+ cleanup();
237
+ }
238
+ throw new Error("Unreachable retry state.");
239
+ }
240
+ function toPoolResultError(reason) {
241
+ if (reason instanceof Error) {
242
+ return reason;
243
+ }
244
+ if (typeof reason === "string" && reason.trim().length > 0) {
245
+ return new Error(reason);
246
+ }
247
+ return reason;
248
+ }
249
+ function isRejected(result) {
250
+ return result.status === "rejected";
251
+ }
252
+ function isFulfilled(result) {
253
+ return result.status === "fulfilled";
254
+ }
255
+ async function runPool(inputs, worker, options = {}) {
256
+ const items = Array.isArray(inputs) ? inputs.slice() : Array.from(inputs);
257
+ const total = items.length;
258
+ if (total === 0) {
259
+ return {
260
+ total: 0,
261
+ completed: 0,
262
+ succeeded: 0,
263
+ failed: 0,
264
+ hasErrors: false,
265
+ results: [],
266
+ fulfilled: [],
267
+ rejected: []
268
+ };
269
+ }
270
+ const retryOptions = normalizeRetryOptions(options.retry);
271
+ const timeoutMs = asOptionalPositiveInteger(options.timeoutMs, "timeoutMs");
272
+ const stopOnError = options.stopOnError ?? false;
273
+ const concurrency = Math.min(asPositiveInteger(options.concurrency, "concurrency", 5), total);
274
+ const { controller: poolController, cleanup } = createLinkedAbortController(
275
+ [options.signal],
276
+ "Pool execution aborted."
277
+ );
278
+ const results = new Array(total);
279
+ let nextIndex = 0;
280
+ let running = 0;
281
+ let completed = 0;
282
+ let succeeded = 0;
283
+ let failed = 0;
284
+ const emitProgress = (lastResult) => {
285
+ options.onProgress?.({
286
+ total,
287
+ completed,
288
+ succeeded,
289
+ failed,
290
+ running,
291
+ pending: Math.max(total - completed - running, 0),
292
+ percentage: Math.round(completed / total * 100),
293
+ lastResult
294
+ });
295
+ };
296
+ const runSingle = async (index) => {
297
+ const input = items[index];
298
+ const startedAt = Date.now();
299
+ let attempts = 0;
300
+ let settled;
301
+ running += 1;
302
+ try {
303
+ const value = await retry(
304
+ async (attempt, signal) => {
305
+ attempts = attempt;
306
+ return worker(input, { index, attempt, signal });
307
+ },
308
+ {
309
+ ...retryOptions,
310
+ signal: poolController.signal,
311
+ timeoutMs
312
+ }
313
+ );
314
+ settled = {
315
+ status: "fulfilled",
316
+ index,
317
+ input,
318
+ value,
319
+ attempts,
320
+ durationMs: Date.now() - startedAt
321
+ };
322
+ succeeded += 1;
323
+ completed += 1;
324
+ results[index] = settled;
325
+ } catch (error) {
326
+ settled = {
327
+ status: "rejected",
328
+ index,
329
+ input,
330
+ reason: toPoolResultError(error),
331
+ attempts,
332
+ durationMs: Date.now() - startedAt
333
+ };
334
+ failed += 1;
335
+ completed += 1;
336
+ results[index] = settled;
337
+ if (stopOnError && !poolController.signal.aborted) {
338
+ poolController.abort(new PoolAbortedError(`Pool aborted because task ${index} failed.`));
339
+ }
340
+ } finally {
341
+ running -= 1;
342
+ if (settled) {
343
+ emitProgress(settled);
344
+ }
345
+ }
346
+ };
347
+ const runner = async () => {
348
+ while (!poolController.signal.aborted) {
349
+ const index = nextIndex;
350
+ if (index >= total) {
351
+ return;
352
+ }
353
+ nextIndex += 1;
354
+ await runSingle(index);
355
+ }
356
+ };
357
+ try {
358
+ await Promise.all(Array.from({ length: concurrency }, () => runner()));
359
+ } finally {
360
+ cleanup();
361
+ }
362
+ if (poolController.signal.aborted) {
363
+ const abortError = toAbortError(poolController.signal.reason, "Pool execution aborted.");
364
+ for (let index = 0; index < total; index += 1) {
365
+ if (results[index]) {
366
+ continue;
367
+ }
368
+ const settled = {
369
+ status: "rejected",
370
+ index,
371
+ input: items[index],
372
+ reason: abortError,
373
+ attempts: 0,
374
+ durationMs: 0
375
+ };
376
+ results[index] = settled;
377
+ failed += 1;
378
+ completed += 1;
379
+ emitProgress(settled);
380
+ }
381
+ }
382
+ const settledResults = results.map((result, index) => {
383
+ if (result) {
384
+ return result;
385
+ }
386
+ return {
387
+ status: "rejected",
388
+ index,
389
+ input: items[index],
390
+ reason: new PoolAbortedError("Task did not execute."),
391
+ attempts: 0,
392
+ durationMs: 0
393
+ };
394
+ });
395
+ const fulfilled = settledResults.filter(isFulfilled);
396
+ const rejected = settledResults.filter(isRejected);
397
+ return {
398
+ total,
399
+ completed: settledResults.length,
400
+ succeeded: fulfilled.length,
401
+ failed: rejected.length,
402
+ hasErrors: rejected.length > 0,
403
+ results: settledResults,
404
+ fulfilled,
405
+ rejected
406
+ };
407
+ }
408
+ function assertPoolSuccess(result) {
409
+ if (!result.hasErrors) {
410
+ return;
411
+ }
412
+ const reasons = result.rejected.map((entry) => entry.reason);
413
+ throw new AggregateError(reasons, `${result.failed} task(s) failed in runPool.`);
414
+ }
415
+
416
+ export { PoolAbortedError, TaskTimeoutError, assertPoolSuccess, retry, runPool };
417
+ //# sourceMappingURL=index.js.map
418
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export type RetryJitter = \"none\" | \"full\";\n\nexport interface BaseRetryOptions {\n retries?: number;\n minDelayMs?: number;\n maxDelayMs?: number;\n factor?: number;\n jitter?: RetryJitter;\n shouldRetry?: (error: unknown, attempt: number) => boolean | Promise<boolean>;\n}\n\nexport interface RetryOptions extends BaseRetryOptions {\n signal?: AbortSignal;\n timeoutMs?: number;\n onRetry?: (event: RetryEvent) => void;\n}\n\nexport interface RetryEvent {\n attempt: number;\n nextAttempt: number;\n delayMs: number;\n error: unknown;\n}\n\nexport interface PoolWorkerContext {\n index: number;\n attempt: number;\n signal: AbortSignal;\n}\n\nexport interface PoolOptions<TInput, TResult> {\n concurrency?: number;\n retry?: BaseRetryOptions;\n timeoutMs?: number;\n signal?: AbortSignal;\n stopOnError?: boolean;\n onProgress?: (progress: PoolProgress<TInput, TResult>) => void;\n}\n\ninterface NormalizedRetryOptions {\n retries: number;\n minDelayMs: number;\n maxDelayMs: number;\n factor: number;\n jitter: RetryJitter;\n shouldRetry: (error: unknown, attempt: number) => boolean | Promise<boolean>;\n}\n\nexport interface PoolBaseResult<TInput> {\n status: \"fulfilled\" | \"rejected\";\n index: number;\n input: TInput;\n attempts: number;\n durationMs: number;\n}\n\nexport interface PoolFulfilledResult<TInput, TResult> extends PoolBaseResult<TInput> {\n status: \"fulfilled\";\n value: TResult;\n}\n\nexport interface PoolRejectedResult<TInput> extends PoolBaseResult<TInput> {\n status: \"rejected\";\n reason: unknown;\n}\n\nexport type PoolSettledResult<TInput, TResult> =\n | PoolFulfilledResult<TInput, TResult>\n | PoolRejectedResult<TInput>;\n\nexport interface PoolProgress<TInput, TResult> {\n total: number;\n completed: number;\n succeeded: number;\n failed: number;\n running: number;\n pending: number;\n percentage: number;\n lastResult?: PoolSettledResult<TInput, TResult>;\n}\n\nexport interface PoolRunResult<TInput, TResult> {\n total: number;\n completed: number;\n succeeded: number;\n failed: number;\n hasErrors: boolean;\n results: Array<PoolSettledResult<TInput, TResult>>;\n fulfilled: Array<PoolFulfilledResult<TInput, TResult>>;\n rejected: Array<PoolRejectedResult<TInput>>;\n}\n\nexport class PoolAbortedError extends Error {\n constructor(message = \"Operation aborted.\") {\n super(message);\n this.name = \"PoolAbortedError\";\n }\n}\n\nexport class TaskTimeoutError extends Error {\n readonly taskIndex: number;\n readonly timeoutMs: number;\n\n constructor(taskIndex: number, timeoutMs: number) {\n super(\n taskIndex >= 0\n ? `Task ${taskIndex} timed out after ${timeoutMs}ms.`\n : `Operation timed out after ${timeoutMs}ms.`\n );\n this.name = \"TaskTimeoutError\";\n this.taskIndex = taskIndex;\n this.timeoutMs = timeoutMs;\n }\n}\n\nconst DEFAULT_RETRY: NormalizedRetryOptions = {\n retries: 0,\n minDelayMs: 100,\n maxDelayMs: 5000,\n factor: 2,\n jitter: \"none\",\n shouldRetry: (error: unknown) => !(error instanceof PoolAbortedError)\n};\n\nfunction toAbortError(reason: unknown, fallbackMessage: string): Error {\n if (reason instanceof Error) {\n return reason;\n }\n if (typeof reason === \"string\" && reason.trim().length > 0) {\n return new PoolAbortedError(reason);\n }\n return new PoolAbortedError(fallbackMessage);\n}\n\nfunction asNonNegativeInteger(value: number | undefined, name: string, fallback: number): number {\n if (value === undefined) {\n return fallback;\n }\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) {\n throw new TypeError(`${name} must be a non-negative integer.`);\n }\n return value;\n}\n\nfunction asPositiveInteger(value: number | undefined, name: string, fallback: number): number {\n if (value === undefined) {\n return fallback;\n }\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {\n throw new TypeError(`${name} must be a positive integer.`);\n }\n return value;\n}\n\nfunction asPositiveNumber(value: number | undefined, name: string, fallback: number): number {\n if (value === undefined) {\n return fallback;\n }\n if (!Number.isFinite(value) || value <= 0) {\n throw new TypeError(`${name} must be a positive number.`);\n }\n return value;\n}\n\nfunction asOptionalPositiveInteger(value: number | undefined, name: string): number | undefined {\n if (value === undefined) {\n return undefined;\n }\n if (!Number.isFinite(value) || !Number.isInteger(value) || value < 1) {\n throw new TypeError(`${name} must be a positive integer.`);\n }\n return value;\n}\n\nfunction normalizeRetryOptions(options: BaseRetryOptions | undefined): NormalizedRetryOptions {\n const retries = asNonNegativeInteger(options?.retries, \"retries\", DEFAULT_RETRY.retries);\n const minDelayMs = asPositiveInteger(options?.minDelayMs, \"minDelayMs\", DEFAULT_RETRY.minDelayMs);\n const maxDelayMs = asPositiveInteger(options?.maxDelayMs, \"maxDelayMs\", DEFAULT_RETRY.maxDelayMs);\n const factor = asPositiveNumber(options?.factor, \"factor\", DEFAULT_RETRY.factor);\n const jitter = options?.jitter ?? DEFAULT_RETRY.jitter;\n\n if (jitter !== \"none\" && jitter !== \"full\") {\n throw new TypeError(\"jitter must be either 'none' or 'full'.\");\n }\n if (minDelayMs > maxDelayMs) {\n throw new TypeError(\"minDelayMs cannot be larger than maxDelayMs.\");\n }\n\n return {\n retries,\n minDelayMs,\n maxDelayMs,\n factor,\n jitter,\n shouldRetry: options?.shouldRetry ?? DEFAULT_RETRY.shouldRetry\n };\n}\n\nfunction computeDelayMs(options: NormalizedRetryOptions, retryAttempt: number): number {\n const exponential = options.minDelayMs * options.factor ** Math.max(retryAttempt - 1, 0);\n const bounded = Math.min(exponential, options.maxDelayMs);\n if (options.jitter === \"full\") {\n return Math.floor(Math.random() * (bounded + 1));\n }\n return Math.floor(bounded);\n}\n\nfunction createLinkedAbortController(\n signals: Array<AbortSignal | undefined>,\n fallbackMessage: string\n): { controller: AbortController; cleanup: () => void } {\n const controller = new AbortController();\n const listeners: Array<{ signal: AbortSignal; listener: () => void }> = [];\n\n for (const signal of signals) {\n if (!signal) {\n continue;\n }\n if (signal.aborted) {\n controller.abort(toAbortError(signal.reason, fallbackMessage));\n break;\n }\n const listener = (): void => {\n if (!controller.signal.aborted) {\n controller.abort(toAbortError(signal.reason, fallbackMessage));\n }\n };\n signal.addEventListener(\"abort\", listener, { once: true });\n listeners.push({ signal, listener });\n }\n\n const cleanup = (): void => {\n for (const { signal, listener } of listeners) {\n signal.removeEventListener(\"abort\", listener);\n }\n };\n\n return { controller, cleanup };\n}\n\nfunction createAbortPromise(\n signal: AbortSignal,\n fallbackMessage: string\n): { promise: Promise<never>; cleanup: () => void } {\n let listener: (() => void) | undefined;\n const promise = new Promise<never>((_, reject) => {\n listener = (): void => reject(toAbortError(signal.reason, fallbackMessage));\n if (signal.aborted) {\n listener();\n return;\n }\n signal.addEventListener(\"abort\", listener, { once: true });\n });\n\n return {\n promise,\n cleanup: (): void => {\n if (listener) {\n signal.removeEventListener(\"abort\", listener);\n }\n }\n };\n}\n\nasync function sleep(delayMs: number, signal: AbortSignal, abortMessage: string): Promise<void> {\n if (delayMs <= 0) {\n return;\n }\n await new Promise<void>((resolve, reject) => {\n if (signal.aborted) {\n reject(toAbortError(signal.reason, abortMessage));\n return;\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener(\"abort\", onAbort);\n resolve();\n }, delayMs);\n\n const onAbort = (): void => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", onAbort);\n reject(toAbortError(signal.reason, abortMessage));\n };\n\n signal.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\nasync function runWithAttemptTimeout<T>(\n operation: (signal: AbortSignal) => Promise<T>,\n options: {\n signal: AbortSignal;\n timeoutMs?: number;\n timeoutErrorFactory: () => Error;\n abortMessage: string;\n }\n): Promise<T> {\n const { controller, cleanup } = createLinkedAbortController([options.signal], options.abortMessage);\n let timeoutHandle: NodeJS.Timeout | undefined;\n let timeoutPromise: Promise<never> | undefined;\n const operationPromise = Promise.resolve().then(() => operation(controller.signal));\n const { promise: abortPromise, cleanup: cleanupAbortPromise } = createAbortPromise(\n controller.signal,\n options.abortMessage\n );\n\n if (typeof options.timeoutMs === \"number\") {\n timeoutPromise = new Promise<never>((_, reject) => {\n timeoutHandle = setTimeout(() => {\n const timeoutError = options.timeoutErrorFactory();\n if (!controller.signal.aborted) {\n controller.abort(timeoutError);\n }\n reject(timeoutError);\n }, options.timeoutMs);\n });\n }\n\n try {\n const contenders: Array<Promise<T>> = [operationPromise, abortPromise as Promise<T>];\n if (timeoutPromise) {\n contenders.push(timeoutPromise as Promise<T>);\n }\n return await Promise.race(contenders);\n } finally {\n cleanupAbortPromise();\n cleanup();\n if (timeoutHandle) {\n clearTimeout(timeoutHandle);\n }\n }\n}\n\nexport async function retry<T>(\n operation: (attempt: number, signal: AbortSignal) => Promise<T>,\n options: RetryOptions = {}\n): Promise<T> {\n const normalized = normalizeRetryOptions(options);\n const timeoutMs = asOptionalPositiveInteger(options.timeoutMs, \"timeoutMs\");\n const { controller, cleanup } = createLinkedAbortController([options.signal], \"Retry operation aborted.\");\n const maxAttempts = normalized.retries + 1;\n\n try {\n for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {\n try {\n return await runWithAttemptTimeout(\n (signal) => operation(attempt, signal),\n {\n signal: controller.signal,\n timeoutMs,\n timeoutErrorFactory: () => new TaskTimeoutError(-1, timeoutMs ?? 0),\n abortMessage: \"Retry operation aborted.\"\n }\n );\n } catch (error) {\n if (attempt >= maxAttempts) {\n throw error;\n }\n const shouldRetry = await normalized.shouldRetry(error, attempt);\n if (!shouldRetry) {\n throw error;\n }\n const delayMs = computeDelayMs(normalized, attempt);\n options.onRetry?.({\n attempt,\n nextAttempt: attempt + 1,\n delayMs,\n error\n });\n await sleep(delayMs, controller.signal, \"Retry wait aborted.\");\n }\n }\n } finally {\n cleanup();\n }\n\n throw new Error(\"Unreachable retry state.\");\n}\n\nfunction toPoolResultError(reason: unknown): unknown {\n if (reason instanceof Error) {\n return reason;\n }\n if (typeof reason === \"string\" && reason.trim().length > 0) {\n return new Error(reason);\n }\n return reason;\n}\n\nfunction isRejected<TInput, TResult>(\n result: PoolSettledResult<TInput, TResult>\n): result is PoolRejectedResult<TInput> {\n return result.status === \"rejected\";\n}\n\nfunction isFulfilled<TInput, TResult>(\n result: PoolSettledResult<TInput, TResult>\n): result is PoolFulfilledResult<TInput, TResult> {\n return result.status === \"fulfilled\";\n}\n\nexport async function runPool<TInput, TResult>(\n inputs: Iterable<TInput>,\n worker: (input: TInput, context: PoolWorkerContext) => Promise<TResult>,\n options: PoolOptions<TInput, TResult> = {}\n): Promise<PoolRunResult<TInput, TResult>> {\n const items = Array.isArray(inputs) ? inputs.slice() : Array.from(inputs);\n const total = items.length;\n\n if (total === 0) {\n return {\n total: 0,\n completed: 0,\n succeeded: 0,\n failed: 0,\n hasErrors: false,\n results: [],\n fulfilled: [],\n rejected: []\n };\n }\n\n const retryOptions = normalizeRetryOptions(options.retry);\n const timeoutMs = asOptionalPositiveInteger(options.timeoutMs, \"timeoutMs\");\n const stopOnError = options.stopOnError ?? false;\n const concurrency = Math.min(asPositiveInteger(options.concurrency, \"concurrency\", 5), total);\n const { controller: poolController, cleanup } = createLinkedAbortController(\n [options.signal],\n \"Pool execution aborted.\"\n );\n\n const results = new Array<PoolSettledResult<TInput, TResult> | undefined>(total);\n let nextIndex = 0;\n let running = 0;\n let completed = 0;\n let succeeded = 0;\n let failed = 0;\n\n const emitProgress = (lastResult?: PoolSettledResult<TInput, TResult>): void => {\n options.onProgress?.({\n total,\n completed,\n succeeded,\n failed,\n running,\n pending: Math.max(total - completed - running, 0),\n percentage: Math.round((completed / total) * 100),\n lastResult\n });\n };\n\n const runSingle = async (index: number): Promise<void> => {\n const input = items[index];\n const startedAt = Date.now();\n let attempts = 0;\n let settled: PoolSettledResult<TInput, TResult> | undefined;\n running += 1;\n\n try {\n const value = await retry(\n async (attempt, signal) => {\n attempts = attempt;\n return worker(input, { index, attempt, signal });\n },\n {\n ...retryOptions,\n signal: poolController.signal,\n timeoutMs\n }\n );\n\n settled = {\n status: \"fulfilled\",\n index,\n input,\n value,\n attempts,\n durationMs: Date.now() - startedAt\n };\n succeeded += 1;\n completed += 1;\n results[index] = settled;\n } catch (error) {\n settled = {\n status: \"rejected\",\n index,\n input,\n reason: toPoolResultError(error),\n attempts,\n durationMs: Date.now() - startedAt\n };\n failed += 1;\n completed += 1;\n results[index] = settled;\n\n if (stopOnError && !poolController.signal.aborted) {\n poolController.abort(new PoolAbortedError(`Pool aborted because task ${index} failed.`));\n }\n } finally {\n running -= 1;\n if (settled) {\n emitProgress(settled);\n }\n }\n };\n\n const runner = async (): Promise<void> => {\n while (!poolController.signal.aborted) {\n const index = nextIndex;\n if (index >= total) {\n return;\n }\n nextIndex += 1;\n await runSingle(index);\n }\n };\n\n try {\n await Promise.all(Array.from({ length: concurrency }, () => runner()));\n } finally {\n cleanup();\n }\n\n if (poolController.signal.aborted) {\n const abortError = toAbortError(poolController.signal.reason, \"Pool execution aborted.\");\n for (let index = 0; index < total; index += 1) {\n if (results[index]) {\n continue;\n }\n const settled: PoolRejectedResult<TInput> = {\n status: \"rejected\",\n index,\n input: items[index],\n reason: abortError,\n attempts: 0,\n durationMs: 0\n };\n results[index] = settled;\n failed += 1;\n completed += 1;\n emitProgress(settled);\n }\n }\n\n const settledResults = results.map((result, index) => {\n if (result) {\n return result;\n }\n return {\n status: \"rejected\",\n index,\n input: items[index],\n reason: new PoolAbortedError(\"Task did not execute.\"),\n attempts: 0,\n durationMs: 0\n } satisfies PoolRejectedResult<TInput>;\n });\n\n const fulfilled = settledResults.filter(isFulfilled);\n const rejected = settledResults.filter(isRejected);\n\n return {\n total,\n completed: settledResults.length,\n succeeded: fulfilled.length,\n failed: rejected.length,\n hasErrors: rejected.length > 0,\n results: settledResults,\n fulfilled,\n rejected\n };\n}\n\nexport function assertPoolSuccess<TInput, TResult>(result: PoolRunResult<TInput, TResult>): void {\n if (!result.hasErrors) {\n return;\n }\n const reasons = result.rejected.map((entry) => entry.reason);\n throw new AggregateError(reasons, `${result.failed} task(s) failed in runPool.`);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@sejinjja/promise-pool-kit",
3
+ "version": "0.1.0",
4
+ "description": "A tiny TypeScript toolkit for retry, timeout, and controlled concurrency in async workloads.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "sideEffects": false,
23
+ "scripts": {
24
+ "build": "tsup",
25
+ "typecheck": "tsc --noEmit",
26
+ "test": "vitest run",
27
+ "test:watch": "vitest",
28
+ "check": "npm run typecheck && npm run test && npm run build",
29
+ "prepublishOnly": "npm run check"
30
+ },
31
+ "keywords": [
32
+ "promise",
33
+ "pool",
34
+ "concurrency",
35
+ "retry",
36
+ "timeout",
37
+ "async",
38
+ "typescript"
39
+ ],
40
+ "author": "sejinjja",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/sejinjja/promise-pool-kit.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/sejinjja/promise-pool-kit/issues"
47
+ },
48
+ "homepage": "https://github.com/sejinjja/promise-pool-kit#readme",
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^24.0.0",
57
+ "tsup": "^8.5.0",
58
+ "typescript": "^5.8.0",
59
+ "vitest": "^3.2.0"
60
+ }
61
+ }