@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 +21 -0
- package/README.md +126 -0
- package/dist/index.cjs +424 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +82 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +418 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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":";AA4FO,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA,EAC1C,WAAA,CAAY,UAAU,oBAAA,EAAsB;AAC1C,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AAAA,EACd;AACF;AAEO,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA,EAI1C,WAAA,CAAY,WAAmB,SAAA,EAAmB;AAChD,IAAA,KAAA;AAAA,MACE,SAAA,IAAa,IACT,CAAA,KAAA,EAAQ,SAAS,oBAAoB,SAAS,CAAA,GAAA,CAAA,GAC9C,6BAA6B,SAAS,CAAA,GAAA;AAAA,KAC5C;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AACZ,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AACjB,IAAA,IAAA,CAAK,SAAA,GAAY,SAAA;AAAA,EACnB;AACF;AAEA,IAAM,aAAA,GAAwC;AAAA,EAC5C,OAAA,EAAS,CAAA;AAAA,EACT,UAAA,EAAY,GAAA;AAAA,EACZ,UAAA,EAAY,GAAA;AAAA,EACZ,MAAA,EAAQ,CAAA;AAAA,EACR,MAAA,EAAQ,MAAA;AAAA,EACR,WAAA,EAAa,CAAC,KAAA,KAAmB,EAAE,KAAA,YAAiB,gBAAA;AACtD,CAAA;AAEA,SAAS,YAAA,CAAa,QAAiB,eAAA,EAAgC;AACrE,EAAA,IAAI,kBAAkB,KAAA,EAAO;AAC3B,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,OAAO,IAAA,EAAK,CAAE,SAAS,CAAA,EAAG;AAC1D,IAAA,OAAO,IAAI,iBAAiB,MAAM,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,IAAI,iBAAiB,eAAe,CAAA;AAC7C;AAEA,SAAS,oBAAA,CAAqB,KAAA,EAA2B,IAAA,EAAc,QAAA,EAA0B;AAC/F,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,IAAK,CAAC,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,IAAK,KAAA,GAAQ,CAAA,EAAG;AACpE,IAAA,MAAM,IAAI,SAAA,CAAU,CAAA,EAAG,IAAI,CAAA,gCAAA,CAAkC,CAAA;AAAA,EAC/D;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,iBAAA,CAAkB,KAAA,EAA2B,IAAA,EAAc,QAAA,EAA0B;AAC5F,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,IAAK,CAAC,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,IAAK,KAAA,GAAQ,CAAA,EAAG;AACpE,IAAA,MAAM,IAAI,SAAA,CAAU,CAAA,EAAG,IAAI,CAAA,4BAAA,CAA8B,CAAA;AAAA,EAC3D;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,gBAAA,CAAiB,KAAA,EAA2B,IAAA,EAAc,QAAA,EAA0B;AAC3F,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,IAAK,SAAS,CAAA,EAAG;AACzC,IAAA,MAAM,IAAI,SAAA,CAAU,CAAA,EAAG,IAAI,CAAA,2BAAA,CAA6B,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,yBAAA,CAA0B,OAA2B,IAAA,EAAkC;AAC9F,EAAA,IAAI,UAAU,MAAA,EAAW;AACvB,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,MAAA,CAAO,QAAA,CAAS,KAAK,CAAA,IAAK,CAAC,MAAA,CAAO,SAAA,CAAU,KAAK,CAAA,IAAK,KAAA,GAAQ,CAAA,EAAG;AACpE,IAAA,MAAM,IAAI,SAAA,CAAU,CAAA,EAAG,IAAI,CAAA,4BAAA,CAA8B,CAAA;AAAA,EAC3D;AACA,EAAA,OAAO,KAAA;AACT;AAEA,SAAS,sBAAsB,OAAA,EAA+D;AAC5F,EAAA,MAAM,UAAU,oBAAA,CAAqB,OAAA,EAAS,OAAA,EAAS,SAAA,EAAW,cAAc,OAAO,CAAA;AACvF,EAAA,MAAM,aAAa,iBAAA,CAAkB,OAAA,EAAS,UAAA,EAAY,YAAA,EAAc,cAAc,UAAU,CAAA;AAChG,EAAA,MAAM,aAAa,iBAAA,CAAkB,OAAA,EAAS,UAAA,EAAY,YAAA,EAAc,cAAc,UAAU,CAAA;AAChG,EAAA,MAAM,SAAS,gBAAA,CAAiB,OAAA,EAAS,MAAA,EAAQ,QAAA,EAAU,cAAc,MAAM,CAAA;AAC/E,EAAA,MAAM,MAAA,GAAS,OAAA,EAAS,MAAA,IAAU,aAAA,CAAc,MAAA;AAEhD,EAAA,IAAI,MAAA,KAAW,MAAA,IAAU,MAAA,KAAW,MAAA,EAAQ;AAC1C,IAAA,MAAM,IAAI,UAAU,yCAAyC,CAAA;AAAA,EAC/D;AACA,EAAA,IAAI,aAAa,UAAA,EAAY;AAC3B,IAAA,MAAM,IAAI,UAAU,8CAA8C,CAAA;AAAA,EACpE;AAEA,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,UAAA;AAAA,IACA,UAAA;AAAA,IACA,MAAA;AAAA,IACA,MAAA;AAAA,IACA,WAAA,EAAa,OAAA,EAAS,WAAA,IAAe,aAAA,CAAc;AAAA,GACrD;AACF;AAEA,SAAS,cAAA,CAAe,SAAiC,YAAA,EAA8B;AACrF,EAAA,MAAM,WAAA,GAAc,QAAQ,UAAA,GAAa,OAAA,CAAQ,UAAU,IAAA,CAAK,GAAA,CAAI,YAAA,GAAe,CAAA,EAAG,CAAC,CAAA;AACvF,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,CAAI,WAAA,EAAa,QAAQ,UAAU,CAAA;AACxD,EAAA,IAAI,OAAA,CAAQ,WAAW,MAAA,EAAQ;AAC7B,IAAA,OAAO,KAAK,KAAA,CAAM,IAAA,CAAK,MAAA,EAAO,IAAK,UAAU,CAAA,CAAE,CAAA;AAAA,EACjD;AACA,EAAA,OAAO,IAAA,CAAK,MAAM,OAAO,CAAA;AAC3B;AAEA,SAAS,2BAAA,CACP,SACA,eAAA,EACsD;AACtD,EAAA,MAAM,UAAA,GAAa,IAAI,eAAA,EAAgB;AACvC,EAAA,MAAM,YAAkE,EAAC;AAEzE,EAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA;AAAA,IACF;AACA,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,UAAA,CAAW,KAAA,CAAM,YAAA,CAAa,MAAA,CAAO,MAAA,EAAQ,eAAe,CAAC,CAAA;AAC7D,MAAA;AAAA,IACF;AACA,IAAA,MAAM,WAAW,MAAY;AAC3B,MAAA,IAAI,CAAC,UAAA,CAAW,MAAA,CAAO,OAAA,EAAS;AAC9B,QAAA,UAAA,CAAW,KAAA,CAAM,YAAA,CAAa,MAAA,CAAO,MAAA,EAAQ,eAAe,CAAC,CAAA;AAAA,MAC/D;AAAA,IACF,CAAA;AACA,IAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,QAAA,EAAU,EAAE,IAAA,EAAM,MAAM,CAAA;AACzD,IAAA,SAAA,CAAU,IAAA,CAAK,EAAE,MAAA,EAAQ,QAAA,EAAU,CAAA;AAAA,EACrC;AAEA,EAAA,MAAM,UAAU,MAAY;AAC1B,IAAA,KAAA,MAAW,EAAE,MAAA,EAAQ,QAAA,EAAS,IAAK,SAAA,EAAW;AAC5C,MAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,QAAQ,CAAA;AAAA,IAC9C;AAAA,EACF,CAAA;AAEA,EAAA,OAAO,EAAE,YAAY,OAAA,EAAQ;AAC/B;AAEA,SAAS,kBAAA,CACP,QACA,eAAA,EACkD;AAClD,EAAA,IAAI,QAAA;AACJ,EAAA,MAAM,OAAA,GAAU,IAAI,OAAA,CAAe,CAAC,GAAG,MAAA,KAAW;AAChD,IAAA,QAAA,GAAW,MAAY,MAAA,CAAO,YAAA,CAAa,MAAA,CAAO,MAAA,EAAQ,eAAe,CAAC,CAAA;AAC1E,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,QAAA,EAAS;AACT,MAAA;AAAA,IACF;AACA,IAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,QAAA,EAAU,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,EAC3D,CAAC,CAAA;AAED,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,SAAS,MAAY;AACnB,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,QAAQ,CAAA;AAAA,MAC9C;AAAA,IACF;AAAA,GACF;AACF;AAEA,eAAe,KAAA,CAAM,OAAA,EAAiB,MAAA,EAAqB,YAAA,EAAqC;AAC9F,EAAA,IAAI,WAAW,CAAA,EAAG;AAChB,IAAA;AAAA,EACF;AACA,EAAA,MAAM,IAAI,OAAA,CAAc,CAAC,OAAA,EAAS,MAAA,KAAW;AAC3C,IAAA,IAAI,OAAO,OAAA,EAAS;AAClB,MAAA,MAAA,CAAO,YAAA,CAAa,MAAA,CAAO,MAAA,EAAQ,YAAY,CAAC,CAAA;AAChD,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,MAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAC3C,MAAA,OAAA,EAAQ;AAAA,IACV,GAAG,OAAO,CAAA;AAEV,IAAA,MAAM,UAAU,MAAY;AAC1B,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,MAAA,CAAO,mBAAA,CAAoB,SAAS,OAAO,CAAA;AAC3C,MAAA,MAAA,CAAO,YAAA,CAAa,MAAA,CAAO,MAAA,EAAQ,YAAY,CAAC,CAAA;AAAA,IAClD,CAAA;AAEA,IAAA,MAAA,CAAO,iBAAiB,OAAA,EAAS,OAAA,EAAS,EAAE,IAAA,EAAM,MAAM,CAAA;AAAA,EAC1D,CAAC,CAAA;AACH;AAEA,eAAe,qBAAA,CACb,WACA,OAAA,EAMY;AACZ,EAAA,MAAM,EAAE,UAAA,EAAY,OAAA,EAAQ,GAAI,2BAAA,CAA4B,CAAC,OAAA,CAAQ,MAAM,CAAA,EAAG,OAAA,CAAQ,YAAY,CAAA;AAClG,EAAA,IAAI,aAAA;AACJ,EAAA,IAAI,cAAA;AACJ,EAAA,MAAM,gBAAA,GAAmB,QAAQ,OAAA,EAAQ,CAAE,KAAK,MAAM,SAAA,CAAU,UAAA,CAAW,MAAM,CAAC,CAAA;AAClF,EAAA,MAAM,EAAE,OAAA,EAAS,YAAA,EAAc,OAAA,EAAS,qBAAoB,GAAI,kBAAA;AAAA,IAC9D,UAAA,CAAW,MAAA;AAAA,IACX,OAAA,CAAQ;AAAA,GACV;AAEA,EAAA,IAAI,OAAO,OAAA,CAAQ,SAAA,KAAc,QAAA,EAAU;AACzC,IAAA,cAAA,GAAiB,IAAI,OAAA,CAAe,CAAC,CAAA,EAAG,MAAA,KAAW;AACjD,MAAA,aAAA,GAAgB,WAAW,MAAM;AAC/B,QAAA,MAAM,YAAA,GAAe,QAAQ,mBAAA,EAAoB;AACjD,QAAA,IAAI,CAAC,UAAA,CAAW,MAAA,CAAO,OAAA,EAAS;AAC9B,UAAA,UAAA,CAAW,MAAM,YAAY,CAAA;AAAA,QAC/B;AACA,QAAA,MAAA,CAAO,YAAY,CAAA;AAAA,MACrB,CAAA,EAAG,QAAQ,SAAS,CAAA;AAAA,IACtB,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,UAAA,GAAgC,CAAC,gBAAA,EAAkB,YAA0B,CAAA;AACnF,IAAA,IAAI,cAAA,EAAgB;AAClB,MAAA,UAAA,CAAW,KAAK,cAA4B,CAAA;AAAA,IAC9C;AACA,IAAA,OAAO,MAAM,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA;AAAA,EACtC,CAAA,SAAE;AACA,IAAA,mBAAA,EAAoB;AACpB,IAAA,OAAA,EAAQ;AACR,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,YAAA,CAAa,aAAa,CAAA;AAAA,IAC5B;AAAA,EACF;AACF;AAEA,eAAsB,KAAA,CACpB,SAAA,EACA,OAAA,GAAwB,EAAC,EACb;AACZ,EAAA,MAAM,UAAA,GAAa,sBAAsB,OAAO,CAAA;AAChD,EAAA,MAAM,SAAA,GAAY,yBAAA,CAA0B,OAAA,CAAQ,SAAA,EAAW,WAAW,CAAA;AAC1E,EAAA,MAAM,EAAE,YAAY,OAAA,EAAQ,GAAI,4BAA4B,CAAC,OAAA,CAAQ,MAAM,CAAA,EAAG,0BAA0B,CAAA;AACxG,EAAA,MAAM,WAAA,GAAc,WAAW,OAAA,GAAU,CAAA;AAEzC,EAAA,IAAI;AACF,IAAA,KAAA,IAAS,OAAA,GAAU,CAAA,EAAG,OAAA,IAAW,WAAA,EAAa,WAAW,CAAA,EAAG;AAC1D,MAAA,IAAI;AACF,QAAA,OAAO,MAAM,qBAAA;AAAA,UACX,CAAC,MAAA,KAAW,SAAA,CAAU,OAAA,EAAS,MAAM,CAAA;AAAA,UACrC;AAAA,YACE,QAAQ,UAAA,CAAW,MAAA;AAAA,YACnB,SAAA;AAAA,YACA,qBAAqB,MAAM,IAAI,gBAAA,CAAiB,CAAA,CAAA,EAAI,aAAa,CAAC,CAAA;AAAA,YAClE,YAAA,EAAc;AAAA;AAChB,SACF;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,IAAI,WAAW,WAAA,EAAa;AAC1B,UAAA,MAAM,KAAA;AAAA,QACR;AACA,QAAA,MAAM,WAAA,GAAc,MAAM,UAAA,CAAW,WAAA,CAAY,OAAO,OAAO,CAAA;AAC/D,QAAA,IAAI,CAAC,WAAA,EAAa;AAChB,UAAA,MAAM,KAAA;AAAA,QACR;AACA,QAAA,MAAM,OAAA,GAAU,cAAA,CAAe,UAAA,EAAY,OAAO,CAAA;AAClD,QAAA,OAAA,CAAQ,OAAA,GAAU;AAAA,UAChB,OAAA;AAAA,UACA,aAAa,OAAA,GAAU,CAAA;AAAA,UACvB,OAAA;AAAA,UACA;AAAA,SACD,CAAA;AACD,QAAA,MAAM,KAAA,CAAM,OAAA,EAAS,UAAA,CAAW,MAAA,EAAQ,qBAAqB,CAAA;AAAA,MAC/D;AAAA,IACF;AAAA,EACF,CAAA,SAAE;AACA,IAAA,OAAA,EAAQ;AAAA,EACV;AAEA,EAAA,MAAM,IAAI,MAAM,0BAA0B,CAAA;AAC5C;AAEA,SAAS,kBAAkB,MAAA,EAA0B;AACnD,EAAA,IAAI,kBAAkB,KAAA,EAAO;AAC3B,IAAA,OAAO,MAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,IAAY,OAAO,IAAA,EAAK,CAAE,SAAS,CAAA,EAAG;AAC1D,IAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAAA,EACzB;AACA,EAAA,OAAO,MAAA;AACT;AAEA,SAAS,WACP,MAAA,EACsC;AACtC,EAAA,OAAO,OAAO,MAAA,KAAW,UAAA;AAC3B;AAEA,SAAS,YACP,MAAA,EACgD;AAChD,EAAA,OAAO,OAAO,MAAA,KAAW,WAAA;AAC3B;AAEA,eAAsB,OAAA,CACpB,MAAA,EACA,MAAA,EACA,OAAA,GAAwC,EAAC,EACA;AACzC,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,MAAM,CAAA,GAAI,OAAO,KAAA,EAAM,GAAI,KAAA,CAAM,IAAA,CAAK,MAAM,CAAA;AACxE,EAAA,MAAM,QAAQ,KAAA,CAAM,MAAA;AAEpB,EAAA,IAAI,UAAU,CAAA,EAAG;AACf,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,CAAA;AAAA,MACP,SAAA,EAAW,CAAA;AAAA,MACX,SAAA,EAAW,CAAA;AAAA,MACX,MAAA,EAAQ,CAAA;AAAA,MACR,SAAA,EAAW,KAAA;AAAA,MACX,SAAS,EAAC;AAAA,MACV,WAAW,EAAC;AAAA,MACZ,UAAU;AAAC,KACb;AAAA,EACF;AAEA,EAAA,MAAM,YAAA,GAAe,qBAAA,CAAsB,OAAA,CAAQ,KAAK,CAAA;AACxD,EAAA,MAAM,SAAA,GAAY,yBAAA,CAA0B,OAAA,CAAQ,SAAA,EAAW,WAAW,CAAA;AAC1E,EAAA,MAAM,WAAA,GAAc,QAAQ,WAAA,IAAe,KAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,KAAK,GAAA,CAAI,iBAAA,CAAkB,QAAQ,WAAA,EAAa,aAAA,EAAe,CAAC,CAAA,EAAG,KAAK,CAAA;AAC5F,EAAA,MAAM,EAAE,UAAA,EAAY,cAAA,EAAgB,OAAA,EAAQ,GAAI,2BAAA;AAAA,IAC9C,CAAC,QAAQ,MAAM,CAAA;AAAA,IACf;AAAA,GACF;AAEA,EAAA,MAAM,OAAA,GAAU,IAAI,KAAA,CAAsD,KAAK,CAAA;AAC/E,EAAA,IAAI,SAAA,GAAY,CAAA;AAChB,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,IAAI,SAAA,GAAY,CAAA;AAChB,EAAA,IAAI,SAAA,GAAY,CAAA;AAChB,EAAA,IAAI,MAAA,GAAS,CAAA;AAEb,EAAA,MAAM,YAAA,GAAe,CAAC,UAAA,KAA0D;AAC9E,IAAA,OAAA,CAAQ,UAAA,GAAa;AAAA,MACnB,KAAA;AAAA,MACA,SAAA;AAAA,MACA,SAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA;AAAA,MACA,SAAS,IAAA,CAAK,GAAA,CAAI,KAAA,GAAQ,SAAA,GAAY,SAAS,CAAC,CAAA;AAAA,MAChD,UAAA,EAAY,IAAA,CAAK,KAAA,CAAO,SAAA,GAAY,QAAS,GAAG,CAAA;AAAA,MAChD;AAAA,KACD,CAAA;AAAA,EACH,CAAA;AAEA,EAAA,MAAM,SAAA,GAAY,OAAO,KAAA,KAAiC;AACxD,IAAA,MAAM,KAAA,GAAQ,MAAM,KAAK,CAAA;AACzB,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,IAAI,QAAA,GAAW,CAAA;AACf,IAAA,IAAI,OAAA;AACJ,IAAA,OAAA,IAAW,CAAA;AAEX,IAAA,IAAI;AACF,MAAA,MAAM,QAAQ,MAAM,KAAA;AAAA,QAClB,OAAO,SAAS,MAAA,KAAW;AACzB,UAAA,QAAA,GAAW,OAAA;AACX,UAAA,OAAO,OAAO,KAAA,EAAO,EAAE,KAAA,EAAO,OAAA,EAAS,QAAQ,CAAA;AAAA,QACjD,CAAA;AAAA,QACA;AAAA,UACE,GAAG,YAAA;AAAA,UACH,QAAQ,cAAA,CAAe,MAAA;AAAA,UACvB;AAAA;AACF,OACF;AAEA,MAAA,OAAA,GAAU;AAAA,QACR,MAAA,EAAQ,WAAA;AAAA,QACR,KAAA;AAAA,QACA,KAAA;AAAA,QACA,KAAA;AAAA,QACA,QAAA;AAAA,QACA,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,OAC3B;AACA,MAAA,SAAA,IAAa,CAAA;AACb,MAAA,SAAA,IAAa,CAAA;AACb,MAAA,OAAA,CAAQ,KAAK,CAAA,GAAI,OAAA;AAAA,IACnB,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,GAAU;AAAA,QACR,MAAA,EAAQ,UAAA;AAAA,QACR,KAAA;AAAA,QACA,KAAA;AAAA,QACA,MAAA,EAAQ,kBAAkB,KAAK,CAAA;AAAA,QAC/B,QAAA;AAAA,QACA,UAAA,EAAY,IAAA,CAAK,GAAA,EAAI,GAAI;AAAA,OAC3B;AACA,MAAA,MAAA,IAAU,CAAA;AACV,MAAA,SAAA,IAAa,CAAA;AACb,MAAA,OAAA,CAAQ,KAAK,CAAA,GAAI,OAAA;AAEjB,MAAA,IAAI,WAAA,IAAe,CAAC,cAAA,CAAe,MAAA,CAAO,OAAA,EAAS;AACjD,QAAA,cAAA,CAAe,MAAM,IAAI,gBAAA,CAAiB,CAAA,0BAAA,EAA6B,KAAK,UAAU,CAAC,CAAA;AAAA,MACzF;AAAA,IACF,CAAA,SAAE;AACA,MAAA,OAAA,IAAW,CAAA;AACX,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,YAAA,CAAa,OAAO,CAAA;AAAA,MACtB;AAAA,IACF;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,SAAS,YAA2B;AACxC,IAAA,OAAO,CAAC,cAAA,CAAe,MAAA,CAAO,OAAA,EAAS;AACrC,MAAA,MAAM,KAAA,GAAQ,SAAA;AACd,MAAA,IAAI,SAAS,KAAA,EAAO;AAClB,QAAA;AAAA,MACF;AACA,MAAA,SAAA,IAAa,CAAA;AACb,MAAA,MAAM,UAAU,KAAK,CAAA;AAAA,IACvB;AAAA,EACF,CAAA;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,OAAA,CAAQ,GAAA,CAAI,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,WAAA,EAAY,EAAG,MAAM,MAAA,EAAQ,CAAC,CAAA;AAAA,EACvE,CAAA,SAAE;AACA,IAAA,OAAA,EAAQ;AAAA,EACV;AAEA,EAAA,IAAI,cAAA,CAAe,OAAO,OAAA,EAAS;AACjC,IAAA,MAAM,UAAA,GAAa,YAAA,CAAa,cAAA,CAAe,MAAA,CAAO,QAAQ,yBAAyB,CAAA;AACvF,IAAA,KAAA,IAAS,KAAA,GAAQ,CAAA,EAAG,KAAA,GAAQ,KAAA,EAAO,SAAS,CAAA,EAAG;AAC7C,MAAA,IAAI,OAAA,CAAQ,KAAK,CAAA,EAAG;AAClB,QAAA;AAAA,MACF;AACA,MAAA,MAAM,OAAA,GAAsC;AAAA,QAC1C,MAAA,EAAQ,UAAA;AAAA,QACR,KAAA;AAAA,QACA,KAAA,EAAO,MAAM,KAAK,CAAA;AAAA,QAClB,MAAA,EAAQ,UAAA;AAAA,QACR,QAAA,EAAU,CAAA;AAAA,QACV,UAAA,EAAY;AAAA,OACd;AACA,MAAA,OAAA,CAAQ,KAAK,CAAA,GAAI,OAAA;AACjB,MAAA,MAAA,IAAU,CAAA;AACV,MAAA,SAAA,IAAa,CAAA;AACb,MAAA,YAAA,CAAa,OAAO,CAAA;AAAA,IACtB;AAAA,EACF;AAEA,EAAA,MAAM,cAAA,GAAiB,OAAA,CAAQ,GAAA,CAAI,CAAC,QAAQ,KAAA,KAAU;AACpD,IAAA,IAAI,MAAA,EAAQ;AACV,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO;AAAA,MACL,MAAA,EAAQ,UAAA;AAAA,MACR,KAAA;AAAA,MACA,KAAA,EAAO,MAAM,KAAK,CAAA;AAAA,MAClB,MAAA,EAAQ,IAAI,gBAAA,CAAiB,uBAAuB,CAAA;AAAA,MACpD,QAAA,EAAU,CAAA;AAAA,MACV,UAAA,EAAY;AAAA,KACd;AAAA,EACF,CAAC,CAAA;AAED,EAAA,MAAM,SAAA,GAAY,cAAA,CAAe,MAAA,CAAO,WAAW,CAAA;AACnD,EAAA,MAAM,QAAA,GAAW,cAAA,CAAe,MAAA,CAAO,UAAU,CAAA;AAEjD,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,WAAW,cAAA,CAAe,MAAA;AAAA,IAC1B,WAAW,SAAA,CAAU,MAAA;AAAA,IACrB,QAAQ,QAAA,CAAS,MAAA;AAAA,IACjB,SAAA,EAAW,SAAS,MAAA,GAAS,CAAA;AAAA,IAC7B,OAAA,EAAS,cAAA;AAAA,IACT,SAAA;AAAA,IACA;AAAA,GACF;AACF;AAEO,SAAS,kBAAmC,MAAA,EAA8C;AAC/F,EAAA,IAAI,CAAC,OAAO,SAAA,EAAW;AACrB,IAAA;AAAA,EACF;AACA,EAAA,MAAM,UAAU,MAAA,CAAO,QAAA,CAAS,IAAI,CAAC,KAAA,KAAU,MAAM,MAAM,CAAA;AAC3D,EAAA,MAAM,IAAI,cAAA,CAAe,OAAA,EAAS,CAAA,EAAG,MAAA,CAAO,MAAM,CAAA,2BAAA,CAA6B,CAAA;AACjF","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
|
+
}
|