@mrjacket/ahko 0.2.0 → 0.4.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/CHANGELOG.md +26 -0
- package/README.md +94 -8
- package/dist/errors/timeout.error.d.ts +15 -2
- package/dist/index.cjs +346 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +341 -25
- package/dist/index.js.map +1 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/options.model.d.ts +12 -0
- package/dist/models/retry.model.d.ts +52 -0
- package/dist/retry/backoff.d.ts +18 -0
- package/dist/retry/index.d.ts +1 -0
- package/dist/scheduler/signal.d.ts +20 -0
- package/dist/scheduler/task-queue.d.ts +9 -1
- package/dist/scheduler/task-runner.d.ts +25 -5
- package/dist/version.d.ts +1 -1
- package/package.json +21 -2
package/dist/index.js
CHANGED
|
@@ -36,6 +36,26 @@ var EScheduleStrategy = /* @__PURE__ */ ((EScheduleStrategy2) => {
|
|
|
36
36
|
return EScheduleStrategy2;
|
|
37
37
|
})(EScheduleStrategy || {});
|
|
38
38
|
|
|
39
|
+
// src/errors/timeout.error.ts
|
|
40
|
+
var AhkoTimeoutError = class extends AhkoError {
|
|
41
|
+
/**
|
|
42
|
+
* The timeout threshold in milliseconds that was exceeded, if configured.
|
|
43
|
+
*/
|
|
44
|
+
timeoutMs;
|
|
45
|
+
/**
|
|
46
|
+
* Creates a new AhkoTimeoutError.
|
|
47
|
+
*
|
|
48
|
+
* @param message - Explanation of timeout expiry.
|
|
49
|
+
* @param options - Standard Error options including optional timeoutMs and cause.
|
|
50
|
+
*/
|
|
51
|
+
constructor(message = "Task execution timed out", options) {
|
|
52
|
+
super(message, options);
|
|
53
|
+
this.name = "AhkoTimeoutError";
|
|
54
|
+
this.timeoutMs = options?.timeoutMs;
|
|
55
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
39
59
|
// src/models/state.model.ts
|
|
40
60
|
var ETaskState = /* @__PURE__ */ ((ETaskState2) => {
|
|
41
61
|
ETaskState2["PENDING"] = "pending";
|
|
@@ -47,6 +67,31 @@ var ETaskState = /* @__PURE__ */ ((ETaskState2) => {
|
|
|
47
67
|
return ETaskState2;
|
|
48
68
|
})(ETaskState || {});
|
|
49
69
|
|
|
70
|
+
// src/retry/backoff.ts
|
|
71
|
+
var DEFAULT_BASE_DELAY = 250;
|
|
72
|
+
var DEFAULT_MAX_DELAY = 1e4;
|
|
73
|
+
function calculateBackoff(attempt, options, randomFn = Math.random) {
|
|
74
|
+
const backoff = options?.backoff ?? "exponential";
|
|
75
|
+
if (backoff === "none") {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
const baseDelay = typeof options?.baseDelay === "number" && !Number.isNaN(options.baseDelay) && options.baseDelay >= 0 ? options.baseDelay : DEFAULT_BASE_DELAY;
|
|
79
|
+
const maxDelay = typeof options?.maxDelay === "number" && !Number.isNaN(options.maxDelay) && options.maxDelay >= baseDelay ? options.maxDelay : Math.max(DEFAULT_MAX_DELAY, baseDelay);
|
|
80
|
+
let calculatedDelay;
|
|
81
|
+
if (backoff === "linear") {
|
|
82
|
+
calculatedDelay = baseDelay * Math.max(1, attempt);
|
|
83
|
+
} else {
|
|
84
|
+
const exponent = Math.max(0, attempt - 1);
|
|
85
|
+
const factor = exponent > 30 ? 2 ** 30 : 2 ** exponent;
|
|
86
|
+
calculatedDelay = baseDelay * factor;
|
|
87
|
+
}
|
|
88
|
+
const cappedDelay = Math.min(calculatedDelay, maxDelay);
|
|
89
|
+
if (options?.jitter) {
|
|
90
|
+
return Math.floor(randomFn() * (cappedDelay + 1));
|
|
91
|
+
}
|
|
92
|
+
return Math.floor(cappedDelay);
|
|
93
|
+
}
|
|
94
|
+
|
|
50
95
|
// src/scheduler/idle-scheduler.ts
|
|
51
96
|
var IdleScheduler = class {
|
|
52
97
|
/**
|
|
@@ -98,6 +143,10 @@ var TaskQueue = class {
|
|
|
98
143
|
delayedEntries = /* @__PURE__ */ new Set();
|
|
99
144
|
/** Set of tasks currently awaiting an idle opportunity */
|
|
100
145
|
idleEntries = /* @__PURE__ */ new Set();
|
|
146
|
+
/** Set of tasks currently awaiting a retry backoff timer */
|
|
147
|
+
retryEntries = /* @__PURE__ */ new Set();
|
|
148
|
+
/** WeakMap associating task runners with their scheduling options */
|
|
149
|
+
runnerOptions = /* @__PURE__ */ new WeakMap();
|
|
101
150
|
/** Cumulative completed tasks counter */
|
|
102
151
|
completedTasks = 0;
|
|
103
152
|
/** Cumulative failed tasks counter */
|
|
@@ -136,6 +185,33 @@ var TaskQueue = class {
|
|
|
136
185
|
`Unsupported schedule strategy "${String(strategy)}". Supported strategies: "immediate", "delay", "idle".`
|
|
137
186
|
);
|
|
138
187
|
}
|
|
188
|
+
if (options?.retry) {
|
|
189
|
+
if (typeof options.retry.attempts !== "number" || Number.isNaN(options.retry.attempts) || options.retry.attempts < 1 || !Number.isInteger(options.retry.attempts)) {
|
|
190
|
+
throw new AhkoConfigurationError(
|
|
191
|
+
`Invalid retry attempts "${options.retry.attempts}". attempts must be an integer greater than or equal to 1.`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (options.retry.baseDelay !== void 0 && (typeof options.retry.baseDelay !== "number" || Number.isNaN(options.retry.baseDelay) || options.retry.baseDelay < 0)) {
|
|
195
|
+
throw new AhkoConfigurationError(
|
|
196
|
+
`Invalid retry baseDelay "${options.retry.baseDelay}". baseDelay must be a non-negative number in milliseconds.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (options.retry.maxDelay !== void 0 && (typeof options.retry.maxDelay !== "number" || Number.isNaN(options.retry.maxDelay) || options.retry.maxDelay < 0)) {
|
|
200
|
+
throw new AhkoConfigurationError(
|
|
201
|
+
`Invalid retry maxDelay "${options.retry.maxDelay}". maxDelay must be a non-negative number in milliseconds.`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (options?.timeoutMs !== void 0) {
|
|
206
|
+
if (typeof options.timeoutMs !== "number" || Number.isNaN(options.timeoutMs) || !Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0) {
|
|
207
|
+
throw new AhkoConfigurationError(
|
|
208
|
+
`Invalid timeoutMs "${options.timeoutMs}". timeoutMs must be a positive finite number greater than 0.`
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (options) {
|
|
213
|
+
this.runnerOptions.set(runner, options);
|
|
214
|
+
}
|
|
139
215
|
if (runner.state === "cancelled" /* CANCELLED */) {
|
|
140
216
|
this.cancelledTasks++;
|
|
141
217
|
return runner.promise;
|
|
@@ -252,28 +328,86 @@ var TaskQueue = class {
|
|
|
252
328
|
}
|
|
253
329
|
/**
|
|
254
330
|
* Internal execution of an active task runner.
|
|
255
|
-
* Settle caller promise strictly after stats and active status are updated.
|
|
256
331
|
*/
|
|
257
332
|
async executeRunner(runner) {
|
|
333
|
+
const options = this.runnerOptions.get(runner);
|
|
258
334
|
try {
|
|
259
335
|
const result = await runner.run();
|
|
260
336
|
this.completedTasks++;
|
|
261
337
|
this.activeRunners.delete(runner);
|
|
338
|
+
this.runnerOptions.delete(runner);
|
|
262
339
|
runner.resolve(result);
|
|
263
340
|
} catch (error) {
|
|
264
341
|
if (runner.state === "cancelled" /* CANCELLED */) {
|
|
265
342
|
this.cancelledTasks++;
|
|
266
|
-
|
|
343
|
+
this.activeRunners.delete(runner);
|
|
344
|
+
this.runnerOptions.delete(runner);
|
|
345
|
+
runner.reject(error);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const shouldRetry = await runner.canRetry(error, options?.retry);
|
|
349
|
+
if (shouldRetry) {
|
|
350
|
+
this.activeRunners.delete(runner);
|
|
351
|
+
this.scheduleRetry(runner, options);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (runner.state === "timed_out" /* TIMED_OUT */ || error instanceof AhkoTimeoutError) {
|
|
267
355
|
this.timedOutTasks++;
|
|
268
356
|
} else {
|
|
269
357
|
this.failedTasks++;
|
|
270
358
|
}
|
|
271
359
|
this.activeRunners.delete(runner);
|
|
360
|
+
this.runnerOptions.delete(runner);
|
|
272
361
|
runner.reject(error);
|
|
273
362
|
} finally {
|
|
274
363
|
this.pump();
|
|
275
364
|
}
|
|
276
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Schedules a retry attempt following backoff delay,
|
|
368
|
+
* without holding a concurrency slot.
|
|
369
|
+
*/
|
|
370
|
+
scheduleRetry(runner, options) {
|
|
371
|
+
const backoffDelay = calculateBackoff(runner.attempt - 1, options?.retry);
|
|
372
|
+
if (backoffDelay === 0) {
|
|
373
|
+
runner.onCancel = () => {
|
|
374
|
+
const index = this.queue.indexOf(runner);
|
|
375
|
+
if (index !== -1) {
|
|
376
|
+
this.queue.splice(index, 1);
|
|
377
|
+
this.cancelledTasks++;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
this.queue.push(runner);
|
|
381
|
+
this.pump();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const retryEntry = {
|
|
385
|
+
runner,
|
|
386
|
+
timerId: setTimeout(() => {
|
|
387
|
+
this.retryEntries.delete(retryEntry);
|
|
388
|
+
if (runner.state === "cancelled" /* CANCELLED */) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
runner.onCancel = () => {
|
|
392
|
+
const index = this.queue.indexOf(runner);
|
|
393
|
+
if (index !== -1) {
|
|
394
|
+
this.queue.splice(index, 1);
|
|
395
|
+
this.cancelledTasks++;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
this.queue.push(runner);
|
|
399
|
+
this.pump();
|
|
400
|
+
}, backoffDelay)
|
|
401
|
+
};
|
|
402
|
+
this.retryEntries.add(retryEntry);
|
|
403
|
+
runner.onCancel = () => {
|
|
404
|
+
if (this.retryEntries.has(retryEntry)) {
|
|
405
|
+
clearTimeout(retryEntry.timerId);
|
|
406
|
+
this.retryEntries.delete(retryEntry);
|
|
407
|
+
this.cancelledTasks++;
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
277
411
|
/**
|
|
278
412
|
* Returns telemetry snapshot for the scheduler.
|
|
279
413
|
*
|
|
@@ -282,7 +416,7 @@ var TaskQueue = class {
|
|
|
282
416
|
getStats() {
|
|
283
417
|
return Object.freeze({
|
|
284
418
|
activeTasks: this.activeRunners.size,
|
|
285
|
-
pendingTasks: this.queue.length + this.delayedEntries.size + this.idleEntries.size,
|
|
419
|
+
pendingTasks: this.queue.length + this.delayedEntries.size + this.idleEntries.size + this.retryEntries.size,
|
|
286
420
|
completedTasks: this.completedTasks,
|
|
287
421
|
failedTasks: this.failedTasks,
|
|
288
422
|
cancelledTasks: this.cancelledTasks,
|
|
@@ -320,6 +454,10 @@ var TaskRunner = class {
|
|
|
320
454
|
task;
|
|
321
455
|
/** User-supplied AbortSignal for external cancellation */
|
|
322
456
|
externalSignal;
|
|
457
|
+
/** Maximum execution duration allowed in milliseconds */
|
|
458
|
+
timeoutMs;
|
|
459
|
+
/** Active timeout timer identifier */
|
|
460
|
+
timeoutTimerId;
|
|
323
461
|
/** Abort event listener reference for clean detachment */
|
|
324
462
|
abortListener;
|
|
325
463
|
/** Promise resolve handler */
|
|
@@ -330,16 +468,20 @@ var TaskRunner = class {
|
|
|
330
468
|
promise;
|
|
331
469
|
/** Callback invoked when runner is cancelled while pending */
|
|
332
470
|
onCancel;
|
|
471
|
+
/** Current execution attempt count (1-indexed) */
|
|
472
|
+
attempt = 1;
|
|
333
473
|
/**
|
|
334
474
|
* Creates a new TaskRunner instance.
|
|
335
475
|
*
|
|
336
476
|
* @param task - The asynchronous work unit to run.
|
|
337
477
|
* @param externalSignal - Optional external AbortSignal to propagate.
|
|
478
|
+
* @param timeoutMs - Optional maximum execution time in milliseconds.
|
|
338
479
|
*/
|
|
339
|
-
constructor(task, externalSignal) {
|
|
480
|
+
constructor(task, externalSignal, timeoutMs) {
|
|
340
481
|
this.taskId = `task_${Date.now().toString(36)}_${(++taskIdCounter).toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
341
482
|
this.task = task;
|
|
342
483
|
this.externalSignal = externalSignal;
|
|
484
|
+
this.timeoutMs = timeoutMs;
|
|
343
485
|
this.abortController = new AbortController();
|
|
344
486
|
this.promise = new Promise((resolve, reject) => {
|
|
345
487
|
this.resolvePromise = resolve;
|
|
@@ -353,6 +495,7 @@ var TaskRunner = class {
|
|
|
353
495
|
typeof reason === "string" ? reason : "Task was cancelled prior to execution",
|
|
354
496
|
{ cause: reason instanceof Error ? reason : void 0 }
|
|
355
497
|
);
|
|
498
|
+
this.abortController.abort(cancelError);
|
|
356
499
|
this.rejectPromise(cancelError);
|
|
357
500
|
} else {
|
|
358
501
|
this.abortListener = () => {
|
|
@@ -374,6 +517,7 @@ var TaskRunner = class {
|
|
|
374
517
|
* @param value - Value to resolve with.
|
|
375
518
|
*/
|
|
376
519
|
resolve(value) {
|
|
520
|
+
this.cleanup();
|
|
377
521
|
this.resolvePromise(value);
|
|
378
522
|
}
|
|
379
523
|
/**
|
|
@@ -382,12 +526,45 @@ var TaskRunner = class {
|
|
|
382
526
|
* @param reason - Reason to reject with.
|
|
383
527
|
*/
|
|
384
528
|
reject(reason) {
|
|
529
|
+
this.cleanup();
|
|
385
530
|
this.rejectPromise(reason);
|
|
386
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Evaluates if the task should be retried following an execution failure or timeout.
|
|
534
|
+
*
|
|
535
|
+
* @param error - The error encountered during the attempt.
|
|
536
|
+
* @param retryOptions - Configured retry policy.
|
|
537
|
+
* @returns A promise resolving to true if retry should proceed, false otherwise.
|
|
538
|
+
*/
|
|
539
|
+
async canRetry(error, retryOptions) {
|
|
540
|
+
if (this._state === "cancelled" /* CANCELLED */ || (this.externalSignal?.aborted ?? false)) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
if (!retryOptions || typeof retryOptions.attempts !== "number") {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
if (this.attempt >= retryOptions.attempts) {
|
|
547
|
+
return false;
|
|
548
|
+
}
|
|
549
|
+
if (typeof retryOptions.shouldRetry === "function") {
|
|
550
|
+
try {
|
|
551
|
+
const allowed = await retryOptions.shouldRetry(error, this.attempt);
|
|
552
|
+
if (!allowed) {
|
|
553
|
+
return false;
|
|
554
|
+
}
|
|
555
|
+
} catch {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
this.attempt++;
|
|
560
|
+
this._state = "pending" /* PENDING */;
|
|
561
|
+
this.abortController = new AbortController();
|
|
562
|
+
return true;
|
|
563
|
+
}
|
|
387
564
|
/**
|
|
388
565
|
* Executes the task within an allocated concurrency slot.
|
|
389
566
|
*
|
|
390
|
-
* @returns A promise resolving to the task result or rejecting on failure/cancellation.
|
|
567
|
+
* @returns A promise resolving to the task result or rejecting on failure/cancellation/timeout.
|
|
391
568
|
*/
|
|
392
569
|
async run() {
|
|
393
570
|
if (this._state === "cancelled" /* CANCELLED */) {
|
|
@@ -398,16 +575,100 @@ var TaskRunner = class {
|
|
|
398
575
|
signal: this.abortController.signal,
|
|
399
576
|
taskId: this.taskId
|
|
400
577
|
};
|
|
578
|
+
let abortListener;
|
|
579
|
+
const abortPromise = new Promise((_, reject) => {
|
|
580
|
+
abortListener = () => {
|
|
581
|
+
if (this._state === "timed_out" /* TIMED_OUT */) {
|
|
582
|
+
reject(
|
|
583
|
+
new AhkoTimeoutError(
|
|
584
|
+
`Task execution timed out after ${this.timeoutMs}ms`,
|
|
585
|
+
{ timeoutMs: this.timeoutMs }
|
|
586
|
+
)
|
|
587
|
+
);
|
|
588
|
+
} else {
|
|
589
|
+
const reason = this.abortController.signal.reason;
|
|
590
|
+
reject(
|
|
591
|
+
new AhkoCancellationError("Task was cancelled during execution", {
|
|
592
|
+
cause: reason instanceof Error ? reason : void 0
|
|
593
|
+
})
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
this.abortController.signal.addEventListener("abort", abortListener, { once: true });
|
|
598
|
+
});
|
|
599
|
+
let timeoutPromise;
|
|
600
|
+
if (this.timeoutMs !== void 0) {
|
|
601
|
+
timeoutPromise = new Promise((_, reject) => {
|
|
602
|
+
this.timeoutTimerId = setTimeout(() => {
|
|
603
|
+
if (this._state !== "running" /* RUNNING */) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
this._state = "timed_out" /* TIMED_OUT */;
|
|
607
|
+
const timeoutError = new AhkoTimeoutError(
|
|
608
|
+
`Task execution timed out after ${this.timeoutMs}ms`,
|
|
609
|
+
{ timeoutMs: this.timeoutMs }
|
|
610
|
+
);
|
|
611
|
+
this.abortController.abort(timeoutError);
|
|
612
|
+
reject(timeoutError);
|
|
613
|
+
}, this.timeoutMs);
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
let taskExecutionPromise;
|
|
617
|
+
try {
|
|
618
|
+
taskExecutionPromise = Promise.resolve(this.task(context));
|
|
619
|
+
} catch (syncError) {
|
|
620
|
+
taskExecutionPromise = Promise.reject(syncError);
|
|
621
|
+
}
|
|
622
|
+
taskExecutionPromise.catch(() => {
|
|
623
|
+
});
|
|
624
|
+
const racePromises = [
|
|
625
|
+
taskExecutionPromise,
|
|
626
|
+
abortPromise
|
|
627
|
+
];
|
|
628
|
+
if (timeoutPromise) {
|
|
629
|
+
racePromises.push(timeoutPromise);
|
|
630
|
+
}
|
|
401
631
|
try {
|
|
402
|
-
const result = await
|
|
632
|
+
const result = await Promise.race(racePromises);
|
|
633
|
+
this.clearTimeoutTimer();
|
|
634
|
+
if (abortListener) {
|
|
635
|
+
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
636
|
+
}
|
|
637
|
+
if (this._state === "timed_out" /* TIMED_OUT */) {
|
|
638
|
+
throw new AhkoTimeoutError(
|
|
639
|
+
`Task execution timed out after ${this.timeoutMs}ms`,
|
|
640
|
+
{ timeoutMs: this.timeoutMs }
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (this._state === "cancelled" /* CANCELLED */) {
|
|
644
|
+
throw new AhkoCancellationError("Task was cancelled during execution");
|
|
645
|
+
}
|
|
403
646
|
this._state = "completed" /* COMPLETED */;
|
|
404
|
-
this.cleanup();
|
|
405
647
|
return result;
|
|
406
648
|
} catch (error) {
|
|
407
|
-
this.
|
|
408
|
-
|
|
649
|
+
this.clearTimeoutTimer();
|
|
650
|
+
if (abortListener) {
|
|
651
|
+
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
652
|
+
}
|
|
653
|
+
if (this._state === "timed_out" /* TIMED_OUT */ || error instanceof AhkoTimeoutError) {
|
|
654
|
+
this._state = "timed_out" /* TIMED_OUT */;
|
|
655
|
+
if (error instanceof AhkoTimeoutError) {
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
throw new AhkoTimeoutError(
|
|
659
|
+
`Task execution timed out after ${this.timeoutMs}ms`,
|
|
660
|
+
{
|
|
661
|
+
timeoutMs: this.timeoutMs,
|
|
662
|
+
cause: error instanceof Error ? error : void 0
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
const isCancelled = this._state === "cancelled" /* CANCELLED */ || this.abortController.signal.aborted || (this.externalSignal?.aborted ?? false);
|
|
409
667
|
if (isCancelled) {
|
|
410
668
|
this._state = "cancelled" /* CANCELLED */;
|
|
669
|
+
if (error instanceof AhkoCancellationError) {
|
|
670
|
+
throw error;
|
|
671
|
+
}
|
|
411
672
|
throw new AhkoCancellationError("Task was cancelled during execution", {
|
|
412
673
|
cause: error instanceof Error ? error : void 0
|
|
413
674
|
});
|
|
@@ -416,6 +677,15 @@ var TaskRunner = class {
|
|
|
416
677
|
throw error;
|
|
417
678
|
}
|
|
418
679
|
}
|
|
680
|
+
/**
|
|
681
|
+
* Clears the active timeout timer.
|
|
682
|
+
*/
|
|
683
|
+
clearTimeoutTimer() {
|
|
684
|
+
if (this.timeoutTimerId !== void 0) {
|
|
685
|
+
clearTimeout(this.timeoutTimerId);
|
|
686
|
+
this.timeoutTimerId = void 0;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
419
689
|
/**
|
|
420
690
|
* Cancels the task, aborting pending or running execution.
|
|
421
691
|
*
|
|
@@ -427,6 +697,7 @@ var TaskRunner = class {
|
|
|
427
697
|
}
|
|
428
698
|
const wasPending = this._state === "pending" /* PENDING */;
|
|
429
699
|
this._state = "cancelled" /* CANCELLED */;
|
|
700
|
+
this.clearTimeoutTimer();
|
|
430
701
|
this.abortController.abort(reason);
|
|
431
702
|
this.cleanup();
|
|
432
703
|
if (wasPending) {
|
|
@@ -448,6 +719,7 @@ var TaskRunner = class {
|
|
|
448
719
|
* Detaches event listeners from external signal to guarantee memory safety.
|
|
449
720
|
*/
|
|
450
721
|
cleanup() {
|
|
722
|
+
this.clearTimeoutTimer();
|
|
451
723
|
if (this.externalSignal && this.abortListener) {
|
|
452
724
|
this.externalSignal.removeEventListener("abort", this.abortListener);
|
|
453
725
|
}
|
|
@@ -499,7 +771,7 @@ var Ahko = class {
|
|
|
499
771
|
if (typeof task !== "function") {
|
|
500
772
|
throw new AhkoConfigurationError("Task must be a valid function.");
|
|
501
773
|
}
|
|
502
|
-
const runner = new TaskRunner(task, options?.signal);
|
|
774
|
+
const runner = new TaskRunner(task, options?.signal, options?.timeoutMs);
|
|
503
775
|
return this.queue.enqueue(runner, options);
|
|
504
776
|
}
|
|
505
777
|
/**
|
|
@@ -548,7 +820,7 @@ var Ahko = class {
|
|
|
548
820
|
};
|
|
549
821
|
|
|
550
822
|
// src/version.ts
|
|
551
|
-
var VERSION = "0.
|
|
823
|
+
var VERSION = "0.4.0";
|
|
552
824
|
|
|
553
825
|
// src/errors/queue.error.ts
|
|
554
826
|
var AhkoQueueError = class extends AhkoError {
|
|
@@ -565,20 +837,60 @@ var AhkoQueueError = class extends AhkoError {
|
|
|
565
837
|
}
|
|
566
838
|
};
|
|
567
839
|
|
|
568
|
-
// src/
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
840
|
+
// src/scheduler/signal.ts
|
|
841
|
+
function combineSignals(signals) {
|
|
842
|
+
const activeSignals = signals.filter(
|
|
843
|
+
(signal) => signal !== void 0
|
|
844
|
+
);
|
|
845
|
+
if (activeSignals.length === 0) {
|
|
846
|
+
const controller2 = new AbortController();
|
|
847
|
+
return {
|
|
848
|
+
signal: controller2.signal,
|
|
849
|
+
cleanup: () => {
|
|
850
|
+
}
|
|
851
|
+
};
|
|
580
852
|
}
|
|
581
|
-
|
|
853
|
+
const alreadyAborted = activeSignals.find((s) => s.aborted);
|
|
854
|
+
if (alreadyAborted) {
|
|
855
|
+
const controller2 = new AbortController();
|
|
856
|
+
controller2.abort(alreadyAborted.reason);
|
|
857
|
+
return {
|
|
858
|
+
signal: controller2.signal,
|
|
859
|
+
cleanup: () => {
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
if (activeSignals.length === 1) {
|
|
864
|
+
return {
|
|
865
|
+
signal: activeSignals[0],
|
|
866
|
+
cleanup: () => {
|
|
867
|
+
}
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const controller = new AbortController();
|
|
871
|
+
const cleanupFns = [];
|
|
872
|
+
const onAbort = (event) => {
|
|
873
|
+
const target = event.target;
|
|
874
|
+
cleanup();
|
|
875
|
+
controller.abort(target.reason);
|
|
876
|
+
};
|
|
877
|
+
for (const sig of activeSignals) {
|
|
878
|
+
sig.addEventListener("abort", onAbort, { once: true });
|
|
879
|
+
cleanupFns.push(() => {
|
|
880
|
+
sig.removeEventListener("abort", onAbort);
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
const cleanup = () => {
|
|
884
|
+
for (const fn of cleanupFns) {
|
|
885
|
+
fn();
|
|
886
|
+
}
|
|
887
|
+
cleanupFns.length = 0;
|
|
888
|
+
};
|
|
889
|
+
return {
|
|
890
|
+
signal: controller.signal,
|
|
891
|
+
cleanup
|
|
892
|
+
};
|
|
893
|
+
}
|
|
582
894
|
export {
|
|
583
895
|
Ahko,
|
|
584
896
|
AhkoCancellationError,
|
|
@@ -586,8 +898,12 @@ export {
|
|
|
586
898
|
AhkoError,
|
|
587
899
|
AhkoQueueError,
|
|
588
900
|
AhkoTimeoutError,
|
|
901
|
+
DEFAULT_BASE_DELAY,
|
|
902
|
+
DEFAULT_MAX_DELAY,
|
|
589
903
|
EScheduleStrategy,
|
|
590
904
|
ETaskState,
|
|
591
|
-
VERSION
|
|
905
|
+
VERSION,
|
|
906
|
+
calculateBackoff,
|
|
907
|
+
combineSignals
|
|
592
908
|
};
|
|
593
909
|
//# sourceMappingURL=index.js.map
|