@mrjacket/ahko 0.5.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +100 -9
- package/dist/ahko.d.ts +64 -0
- package/dist/events/event-emitter.d.ts +34 -0
- package/dist/events/index.d.ts +1 -0
- package/dist/index.cjs +325 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +325 -3
- package/dist/index.js.map +1 -1
- package/dist/models/events.model.d.ts +50 -0
- package/dist/models/index.d.ts +1 -0
- package/dist/models/stats.model.d.ts +4 -0
- package/dist/scheduler/debounce-coordinator.d.ts +2 -0
- package/dist/scheduler/task-queue.d.ts +30 -0
- package/dist/scheduler/task-runner.d.ts +2 -0
- package/dist/scheduler/throttle-coordinator.d.ts +2 -0
- package/dist/version.d.ts +1 -1
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -112,6 +112,8 @@ var AhkoCancellationError = class extends AhkoError {
|
|
|
112
112
|
// src/scheduler/debounce-coordinator.ts
|
|
113
113
|
var DebounceCoordinator = class {
|
|
114
114
|
entries = /* @__PURE__ */ new Map();
|
|
115
|
+
/** Optional callback invoked whenever entries are settled or removed from coordinator */
|
|
116
|
+
onSettled;
|
|
115
117
|
/**
|
|
116
118
|
* Schedules a task under the debounce strategy.
|
|
117
119
|
*
|
|
@@ -198,6 +200,7 @@ var DebounceCoordinator = class {
|
|
|
198
200
|
return;
|
|
199
201
|
}
|
|
200
202
|
this.entries.delete(key);
|
|
203
|
+
this.onSettled?.();
|
|
201
204
|
if (entry.options?.signal && entry.abortListener) {
|
|
202
205
|
entry.options.signal.removeEventListener("abort", entry.abortListener);
|
|
203
206
|
}
|
|
@@ -221,6 +224,7 @@ var DebounceCoordinator = class {
|
|
|
221
224
|
}
|
|
222
225
|
clearTimeout(entry.timerId);
|
|
223
226
|
this.entries.delete(key);
|
|
227
|
+
this.onSettled?.();
|
|
224
228
|
if (entry.options?.signal && entry.abortListener) {
|
|
225
229
|
entry.options.signal.removeEventListener("abort", entry.abortListener);
|
|
226
230
|
}
|
|
@@ -240,7 +244,7 @@ var DebounceCoordinator = class {
|
|
|
240
244
|
* Cancels all pending debounced entries and clears the map.
|
|
241
245
|
*/
|
|
242
246
|
clear() {
|
|
243
|
-
for (const
|
|
247
|
+
for (const entry of this.entries.values()) {
|
|
244
248
|
clearTimeout(entry.timerId);
|
|
245
249
|
if (entry.options?.signal && entry.abortListener) {
|
|
246
250
|
entry.options.signal.removeEventListener("abort", entry.abortListener);
|
|
@@ -248,6 +252,7 @@ var DebounceCoordinator = class {
|
|
|
248
252
|
entry.reject(new AhkoCancellationError("Debounced tasks cleared"));
|
|
249
253
|
}
|
|
250
254
|
this.entries.clear();
|
|
255
|
+
this.onSettled?.();
|
|
251
256
|
}
|
|
252
257
|
};
|
|
253
258
|
|
|
@@ -293,6 +298,8 @@ var IdleScheduler = class {
|
|
|
293
298
|
// src/scheduler/throttle-coordinator.ts
|
|
294
299
|
var ThrottleCoordinator = class {
|
|
295
300
|
entries = /* @__PURE__ */ new Map();
|
|
301
|
+
/** Optional callback invoked whenever entries are settled or removed from coordinator */
|
|
302
|
+
onSettled;
|
|
296
303
|
/**
|
|
297
304
|
* Schedules a task under the throttle strategy.
|
|
298
305
|
*
|
|
@@ -378,6 +385,7 @@ var ThrottleCoordinator = class {
|
|
|
378
385
|
return;
|
|
379
386
|
}
|
|
380
387
|
this.entries.delete(key);
|
|
388
|
+
this.onSettled?.();
|
|
381
389
|
}
|
|
382
390
|
/**
|
|
383
391
|
* Cancels any pending trailing throttled task for a given key.
|
|
@@ -394,6 +402,7 @@ var ThrottleCoordinator = class {
|
|
|
394
402
|
clearTimeout(entry.windowTimerId);
|
|
395
403
|
}
|
|
396
404
|
this.entries.delete(key);
|
|
405
|
+
this.onSettled?.();
|
|
397
406
|
if (entry.trailingReject) {
|
|
398
407
|
const cancelError = new AhkoCancellationError(
|
|
399
408
|
typeof reason === "string" ? reason : "Throttled task was cancelled",
|
|
@@ -412,7 +421,7 @@ var ThrottleCoordinator = class {
|
|
|
412
421
|
* Clears all throttled entries and timers.
|
|
413
422
|
*/
|
|
414
423
|
clear() {
|
|
415
|
-
for (const
|
|
424
|
+
for (const entry of this.entries.values()) {
|
|
416
425
|
if (entry.windowTimerId !== void 0) {
|
|
417
426
|
clearTimeout(entry.windowTimerId);
|
|
418
427
|
}
|
|
@@ -421,6 +430,75 @@ var ThrottleCoordinator = class {
|
|
|
421
430
|
}
|
|
422
431
|
}
|
|
423
432
|
this.entries.clear();
|
|
433
|
+
this.onSettled?.();
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/events/event-emitter.ts
|
|
438
|
+
var AhkoEventEmitter = class {
|
|
439
|
+
listeners = /* @__PURE__ */ new Map();
|
|
440
|
+
/**
|
|
441
|
+
* Subscribes a listener to a specific Ahko lifecycle event.
|
|
442
|
+
*
|
|
443
|
+
* @param event - The event name to subscribe to.
|
|
444
|
+
* @param handler - The callback function to invoke when the event is emitted.
|
|
445
|
+
* @returns An unsubscribe function to remove the listener.
|
|
446
|
+
*/
|
|
447
|
+
on(event, handler) {
|
|
448
|
+
let set = this.listeners.get(event);
|
|
449
|
+
if (!set) {
|
|
450
|
+
set = /* @__PURE__ */ new Set();
|
|
451
|
+
this.listeners.set(event, set);
|
|
452
|
+
}
|
|
453
|
+
set.add(handler);
|
|
454
|
+
return () => {
|
|
455
|
+
this.off(event, handler);
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Unsubscribes a listener from a specific Ahko lifecycle event.
|
|
460
|
+
*
|
|
461
|
+
* @param event - The event name.
|
|
462
|
+
* @param handler - The callback function to remove.
|
|
463
|
+
*/
|
|
464
|
+
off(event, handler) {
|
|
465
|
+
const set = this.listeners.get(event);
|
|
466
|
+
if (set) {
|
|
467
|
+
set.delete(handler);
|
|
468
|
+
if (set.size === 0) {
|
|
469
|
+
this.listeners.delete(event);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Emits an event with the corresponding typed payload to all subscribed listeners.
|
|
475
|
+
* Listener invocations are safely isolated in try/catch to protect scheduler integrity.
|
|
476
|
+
*
|
|
477
|
+
* @param event - The event name to emit.
|
|
478
|
+
* @param payload - The event-specific payload data.
|
|
479
|
+
*/
|
|
480
|
+
emit(event, payload) {
|
|
481
|
+
const set = this.listeners.get(event);
|
|
482
|
+
if (!set || set.size === 0) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const handlers = Array.from(set);
|
|
486
|
+
for (const handler of handlers) {
|
|
487
|
+
try {
|
|
488
|
+
const result = handler(payload);
|
|
489
|
+
if (result && typeof result.catch === "function") {
|
|
490
|
+
result.catch(() => {
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
} catch {
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Removes all registered event listeners.
|
|
499
|
+
*/
|
|
500
|
+
clear() {
|
|
501
|
+
this.listeners.clear();
|
|
424
502
|
}
|
|
425
503
|
};
|
|
426
504
|
|
|
@@ -448,6 +526,10 @@ var TaskQueue = class {
|
|
|
448
526
|
debounceCoordinator = new DebounceCoordinator();
|
|
449
527
|
/** Coordinator for throttled tasks with leading/trailing coalescing */
|
|
450
528
|
throttleCoordinator = new ThrottleCoordinator();
|
|
529
|
+
/** Lifecycle event emitter for task and scheduler events */
|
|
530
|
+
emitter = new AhkoEventEmitter();
|
|
531
|
+
/** Set of pending resolvers awaiting scheduler idle transition */
|
|
532
|
+
idleResolvers = /* @__PURE__ */ new Set();
|
|
451
533
|
/** WeakMap associating task runners with their scheduling options */
|
|
452
534
|
runnerOptions = /* @__PURE__ */ new WeakMap();
|
|
453
535
|
/** Cumulative completed tasks counter */
|
|
@@ -458,6 +540,10 @@ var TaskQueue = class {
|
|
|
458
540
|
cancelledTasks = 0;
|
|
459
541
|
/** Cumulative timed out tasks counter */
|
|
460
542
|
timedOutTasks = 0;
|
|
543
|
+
/** Cumulative count of retry attempts triggered */
|
|
544
|
+
retriedTasks = 0;
|
|
545
|
+
/** Cumulative count of tasks dispatched to concurrency slots */
|
|
546
|
+
totalDispatched = 0;
|
|
461
547
|
/**
|
|
462
548
|
* Creates a new TaskQueue.
|
|
463
549
|
*
|
|
@@ -478,6 +564,8 @@ var TaskQueue = class {
|
|
|
478
564
|
}
|
|
479
565
|
this.concurrency = concurrency;
|
|
480
566
|
this.minIntervalMs = minIntervalMs;
|
|
567
|
+
this.debounceCoordinator.onSettled = () => this.checkIdle();
|
|
568
|
+
this.throttleCoordinator.onSettled = () => this.checkIdle();
|
|
481
569
|
}
|
|
482
570
|
/**
|
|
483
571
|
* Enqueues a task runner according to the specified schedule options.
|
|
@@ -563,6 +651,11 @@ var TaskQueue = class {
|
|
|
563
651
|
if (index !== -1) {
|
|
564
652
|
this.queue.splice(index, 1);
|
|
565
653
|
this.cancelledTasks++;
|
|
654
|
+
this.emitter.emit("task:cancel", {
|
|
655
|
+
taskId: runner.taskId,
|
|
656
|
+
reason: "Task cancelled while queued"
|
|
657
|
+
});
|
|
658
|
+
this.checkIdle();
|
|
566
659
|
}
|
|
567
660
|
};
|
|
568
661
|
this.queue.push(runner);
|
|
@@ -586,6 +679,11 @@ var TaskQueue = class {
|
|
|
586
679
|
if (index !== -1) {
|
|
587
680
|
this.queue.splice(index, 1);
|
|
588
681
|
this.cancelledTasks++;
|
|
682
|
+
this.emitter.emit("task:cancel", {
|
|
683
|
+
taskId: runner.taskId,
|
|
684
|
+
reason: "Task cancelled while queued"
|
|
685
|
+
});
|
|
686
|
+
this.checkIdle();
|
|
589
687
|
}
|
|
590
688
|
};
|
|
591
689
|
this.queue.push(runner);
|
|
@@ -598,6 +696,11 @@ var TaskQueue = class {
|
|
|
598
696
|
clearTimeout(delayedEntry.timerId);
|
|
599
697
|
this.delayedEntries.delete(delayedEntry);
|
|
600
698
|
this.cancelledTasks++;
|
|
699
|
+
this.emitter.emit("task:cancel", {
|
|
700
|
+
taskId: runner.taskId,
|
|
701
|
+
reason: "Task cancelled while waiting in delay"
|
|
702
|
+
});
|
|
703
|
+
this.checkIdle();
|
|
601
704
|
}
|
|
602
705
|
};
|
|
603
706
|
}
|
|
@@ -617,6 +720,11 @@ var TaskQueue = class {
|
|
|
617
720
|
if (index !== -1) {
|
|
618
721
|
this.queue.splice(index, 1);
|
|
619
722
|
this.cancelledTasks++;
|
|
723
|
+
this.emitter.emit("task:cancel", {
|
|
724
|
+
taskId: runner.taskId,
|
|
725
|
+
reason: "Task cancelled while queued"
|
|
726
|
+
});
|
|
727
|
+
this.checkIdle();
|
|
620
728
|
}
|
|
621
729
|
};
|
|
622
730
|
this.queue.push(runner);
|
|
@@ -629,6 +737,11 @@ var TaskQueue = class {
|
|
|
629
737
|
handle.cancel();
|
|
630
738
|
this.idleEntries.delete(idleEntry);
|
|
631
739
|
this.cancelledTasks++;
|
|
740
|
+
this.emitter.emit("task:cancel", {
|
|
741
|
+
taskId: runner.taskId,
|
|
742
|
+
reason: "Task cancelled while waiting for idle"
|
|
743
|
+
});
|
|
744
|
+
this.checkIdle();
|
|
632
745
|
}
|
|
633
746
|
};
|
|
634
747
|
}
|
|
@@ -697,36 +810,69 @@ var TaskQueue = class {
|
|
|
697
810
|
*/
|
|
698
811
|
async executeRunner(runner) {
|
|
699
812
|
const options = this.runnerOptions.get(runner);
|
|
813
|
+
this.totalDispatched++;
|
|
814
|
+
this.emitter.emit("task:start", {
|
|
815
|
+
taskId: runner.taskId,
|
|
816
|
+
attempt: runner.attempt
|
|
817
|
+
});
|
|
700
818
|
try {
|
|
701
819
|
const result = await runner.run();
|
|
702
820
|
this.completedTasks++;
|
|
703
821
|
this.activeRunners.delete(runner);
|
|
704
822
|
this.runnerOptions.delete(runner);
|
|
823
|
+
this.emitter.emit("task:complete", {
|
|
824
|
+
taskId: runner.taskId,
|
|
825
|
+
attempt: runner.attempt,
|
|
826
|
+
durationMs: runner.lastDurationMs,
|
|
827
|
+
result
|
|
828
|
+
});
|
|
705
829
|
runner.resolve(result);
|
|
706
830
|
} catch (error) {
|
|
707
831
|
if (runner.state === "cancelled" /* CANCELLED */) {
|
|
708
832
|
this.cancelledTasks++;
|
|
709
833
|
this.activeRunners.delete(runner);
|
|
710
834
|
this.runnerOptions.delete(runner);
|
|
835
|
+
this.emitter.emit("task:cancel", {
|
|
836
|
+
taskId: runner.taskId,
|
|
837
|
+
reason: error
|
|
838
|
+
});
|
|
711
839
|
runner.reject(error);
|
|
712
840
|
return;
|
|
713
841
|
}
|
|
714
842
|
const shouldRetry = await runner.canRetry(error, options?.retry);
|
|
715
843
|
if (shouldRetry) {
|
|
844
|
+
this.retriedTasks++;
|
|
716
845
|
this.activeRunners.delete(runner);
|
|
846
|
+
this.emitter.emit("task:fail", {
|
|
847
|
+
taskId: runner.taskId,
|
|
848
|
+
attempt: runner.attempt - 1,
|
|
849
|
+
error,
|
|
850
|
+
willRetry: true
|
|
851
|
+
});
|
|
717
852
|
this.scheduleRetry(runner, options);
|
|
718
853
|
return;
|
|
719
854
|
}
|
|
720
855
|
if (runner.state === "timed_out" /* TIMED_OUT */ || error instanceof AhkoTimeoutError) {
|
|
721
856
|
this.timedOutTasks++;
|
|
857
|
+
this.emitter.emit("task:timeout", {
|
|
858
|
+
taskId: runner.taskId,
|
|
859
|
+
timeoutMs: runner.timeoutMs
|
|
860
|
+
});
|
|
722
861
|
} else {
|
|
723
862
|
this.failedTasks++;
|
|
724
863
|
}
|
|
725
864
|
this.activeRunners.delete(runner);
|
|
726
865
|
this.runnerOptions.delete(runner);
|
|
866
|
+
this.emitter.emit("task:fail", {
|
|
867
|
+
taskId: runner.taskId,
|
|
868
|
+
attempt: runner.attempt,
|
|
869
|
+
error,
|
|
870
|
+
willRetry: false
|
|
871
|
+
});
|
|
727
872
|
runner.reject(error);
|
|
728
873
|
} finally {
|
|
729
874
|
this.pump();
|
|
875
|
+
this.checkIdle();
|
|
730
876
|
}
|
|
731
877
|
}
|
|
732
878
|
/**
|
|
@@ -741,6 +887,11 @@ var TaskQueue = class {
|
|
|
741
887
|
if (index !== -1) {
|
|
742
888
|
this.queue.splice(index, 1);
|
|
743
889
|
this.cancelledTasks++;
|
|
890
|
+
this.emitter.emit("task:cancel", {
|
|
891
|
+
taskId: runner.taskId,
|
|
892
|
+
reason: "Task cancelled while queued"
|
|
893
|
+
});
|
|
894
|
+
this.checkIdle();
|
|
744
895
|
}
|
|
745
896
|
};
|
|
746
897
|
this.queue.push(runner);
|
|
@@ -759,6 +910,11 @@ var TaskQueue = class {
|
|
|
759
910
|
if (index !== -1) {
|
|
760
911
|
this.queue.splice(index, 1);
|
|
761
912
|
this.cancelledTasks++;
|
|
913
|
+
this.emitter.emit("task:cancel", {
|
|
914
|
+
taskId: runner.taskId,
|
|
915
|
+
reason: "Task cancelled while queued"
|
|
916
|
+
});
|
|
917
|
+
this.checkIdle();
|
|
762
918
|
}
|
|
763
919
|
};
|
|
764
920
|
this.queue.push(runner);
|
|
@@ -771,9 +927,91 @@ var TaskQueue = class {
|
|
|
771
927
|
clearTimeout(retryEntry.timerId);
|
|
772
928
|
this.retryEntries.delete(retryEntry);
|
|
773
929
|
this.cancelledTasks++;
|
|
930
|
+
this.emitter.emit("task:cancel", {
|
|
931
|
+
taskId: runner.taskId,
|
|
932
|
+
reason: "Task cancelled during retry backoff"
|
|
933
|
+
});
|
|
934
|
+
this.checkIdle();
|
|
774
935
|
}
|
|
775
936
|
};
|
|
776
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* Checks whether the scheduler has transitioned to idle and notifies listeners/resolvers.
|
|
940
|
+
*/
|
|
941
|
+
checkIdle() {
|
|
942
|
+
if (this.isIdle()) {
|
|
943
|
+
if (this.idleResolvers.size > 0) {
|
|
944
|
+
for (const resolve of this.idleResolvers) {
|
|
945
|
+
resolve();
|
|
946
|
+
}
|
|
947
|
+
this.idleResolvers.clear();
|
|
948
|
+
}
|
|
949
|
+
this.emitter.emit("idle", { timestamp: Date.now() });
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Checks whether the scheduler is currently idle (no active runners and no pending tasks).
|
|
954
|
+
*
|
|
955
|
+
* @returns True if completely idle, false otherwise.
|
|
956
|
+
*/
|
|
957
|
+
isIdle() {
|
|
958
|
+
return this.activeRunners.size === 0 && this.queue.length === 0 && this.delayedEntries.size === 0 && this.idleEntries.size === 0 && this.retryEntries.size === 0 && this.debounceCoordinator.size === 0 && this.throttleCoordinator.size === 0;
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Returns a promise that resolves once the scheduler has processed all tasks and is idle.
|
|
962
|
+
*
|
|
963
|
+
* @returns Promise resolving when idle.
|
|
964
|
+
*/
|
|
965
|
+
onIdle() {
|
|
966
|
+
if (this.isIdle()) {
|
|
967
|
+
return Promise.resolve();
|
|
968
|
+
}
|
|
969
|
+
return new Promise((resolve) => {
|
|
970
|
+
this.idleResolvers.add(resolve);
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Clears all pending and waiting tasks from the scheduler, cancelling their runners.
|
|
975
|
+
* Active tasks currently in flight will continue to run to completion or abort via signal.
|
|
976
|
+
*/
|
|
977
|
+
clear() {
|
|
978
|
+
while (this.queue.length > 0) {
|
|
979
|
+
const runner = this.queue.shift();
|
|
980
|
+
if (runner && runner.state !== "cancelled" /* CANCELLED */) {
|
|
981
|
+
runner.cancel("Scheduler cleared");
|
|
982
|
+
this.cancelledTasks++;
|
|
983
|
+
this.emitter.emit("task:cancel", { taskId: runner.taskId, reason: "Scheduler cleared" });
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
for (const entry of this.delayedEntries.values()) {
|
|
987
|
+
clearTimeout(entry.timerId);
|
|
988
|
+
entry.runner.cancel("Scheduler cleared");
|
|
989
|
+
this.cancelledTasks++;
|
|
990
|
+
this.emitter.emit("task:cancel", { taskId: entry.runner.taskId, reason: "Scheduler cleared" });
|
|
991
|
+
}
|
|
992
|
+
this.delayedEntries.clear();
|
|
993
|
+
for (const entry of this.idleEntries.values()) {
|
|
994
|
+
entry.handle.cancel();
|
|
995
|
+
entry.runner.cancel("Scheduler cleared");
|
|
996
|
+
this.cancelledTasks++;
|
|
997
|
+
this.emitter.emit("task:cancel", { taskId: entry.runner.taskId, reason: "Scheduler cleared" });
|
|
998
|
+
}
|
|
999
|
+
this.idleEntries.clear();
|
|
1000
|
+
for (const entry of this.retryEntries.values()) {
|
|
1001
|
+
clearTimeout(entry.timerId);
|
|
1002
|
+
entry.runner.cancel("Scheduler cleared");
|
|
1003
|
+
this.cancelledTasks++;
|
|
1004
|
+
this.emitter.emit("task:cancel", { taskId: entry.runner.taskId, reason: "Scheduler cleared" });
|
|
1005
|
+
}
|
|
1006
|
+
this.retryEntries.clear();
|
|
1007
|
+
this.debounceCoordinator.clear();
|
|
1008
|
+
this.throttleCoordinator.clear();
|
|
1009
|
+
if (this.rateLimitTimer !== void 0) {
|
|
1010
|
+
clearTimeout(this.rateLimitTimer);
|
|
1011
|
+
this.rateLimitTimer = void 0;
|
|
1012
|
+
}
|
|
1013
|
+
this.checkIdle();
|
|
1014
|
+
}
|
|
777
1015
|
/**
|
|
778
1016
|
* Returns telemetry snapshot for the scheduler.
|
|
779
1017
|
*
|
|
@@ -787,6 +1025,8 @@ var TaskQueue = class {
|
|
|
787
1025
|
failedTasks: this.failedTasks,
|
|
788
1026
|
cancelledTasks: this.cancelledTasks,
|
|
789
1027
|
timedOutTasks: this.timedOutTasks,
|
|
1028
|
+
retriedTasks: this.retriedTasks,
|
|
1029
|
+
totalDispatched: this.totalDispatched,
|
|
790
1030
|
capacity: this.concurrency
|
|
791
1031
|
});
|
|
792
1032
|
}
|
|
@@ -821,6 +1061,8 @@ var TaskRunner = class {
|
|
|
821
1061
|
onCancel;
|
|
822
1062
|
/** Current execution attempt count (1-indexed) */
|
|
823
1063
|
attempt = 1;
|
|
1064
|
+
/** Duration of the most recent execution attempt in milliseconds */
|
|
1065
|
+
lastDurationMs = 0;
|
|
824
1066
|
/**
|
|
825
1067
|
* Creates a new TaskRunner instance.
|
|
826
1068
|
*
|
|
@@ -979,9 +1221,11 @@ var TaskRunner = class {
|
|
|
979
1221
|
if (timeoutPromise) {
|
|
980
1222
|
racePromises.push(timeoutPromise);
|
|
981
1223
|
}
|
|
1224
|
+
const startTime = Date.now();
|
|
982
1225
|
try {
|
|
983
1226
|
const result = await Promise.race(racePromises);
|
|
984
1227
|
this.clearTimeoutTimer();
|
|
1228
|
+
this.lastDurationMs = Math.max(0, Date.now() - startTime);
|
|
985
1229
|
if (abortListener) {
|
|
986
1230
|
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
987
1231
|
}
|
|
@@ -998,6 +1242,7 @@ var TaskRunner = class {
|
|
|
998
1242
|
return result;
|
|
999
1243
|
} catch (error) {
|
|
1000
1244
|
this.clearTimeoutTimer();
|
|
1245
|
+
this.lastDurationMs = Math.max(0, Date.now() - startTime);
|
|
1001
1246
|
if (abortListener) {
|
|
1002
1247
|
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
1003
1248
|
}
|
|
@@ -1239,10 +1484,87 @@ var Ahko = class {
|
|
|
1239
1484
|
stats() {
|
|
1240
1485
|
return this.queue.getStats();
|
|
1241
1486
|
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Subscribes to a scheduler lifecycle event.
|
|
1489
|
+
*
|
|
1490
|
+
* @param event - Event name to listen for.
|
|
1491
|
+
* @param handler - Callback function invoked when the event is emitted.
|
|
1492
|
+
* @returns Unsubscribe function to remove the listener.
|
|
1493
|
+
*
|
|
1494
|
+
* @example
|
|
1495
|
+
* ```typescript
|
|
1496
|
+
* const unsubscribe = ahko.on("task:start", ({ taskId, attempt }) => {
|
|
1497
|
+
* console.log(`Task ${taskId} started attempt ${attempt}`);
|
|
1498
|
+
* });
|
|
1499
|
+
* ```
|
|
1500
|
+
*/
|
|
1501
|
+
on(event, handler) {
|
|
1502
|
+
return this.queue.emitter.on(event, handler);
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Unsubscribes an event listener from a scheduler lifecycle event.
|
|
1506
|
+
*
|
|
1507
|
+
* @param event - Event name.
|
|
1508
|
+
* @param handler - The exact listener callback to remove.
|
|
1509
|
+
*/
|
|
1510
|
+
off(event, handler) {
|
|
1511
|
+
this.queue.emitter.off(event, handler);
|
|
1512
|
+
}
|
|
1513
|
+
/**
|
|
1514
|
+
* Checks whether the scheduler is currently idle (no active runners and no pending tasks).
|
|
1515
|
+
*
|
|
1516
|
+
* @returns True if completely idle, false otherwise.
|
|
1517
|
+
*/
|
|
1518
|
+
isIdle() {
|
|
1519
|
+
return this.queue.isIdle();
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Returns a promise that resolves once the scheduler has completed all tasks and is idle.
|
|
1523
|
+
*
|
|
1524
|
+
* @returns Promise resolving when the scheduler is idle.
|
|
1525
|
+
*
|
|
1526
|
+
* @example
|
|
1527
|
+
* ```typescript
|
|
1528
|
+
* ahko.schedule(doWork);
|
|
1529
|
+
* await ahko.onIdle();
|
|
1530
|
+
* console.log("All work finished!");
|
|
1531
|
+
* ```
|
|
1532
|
+
*/
|
|
1533
|
+
onIdle() {
|
|
1534
|
+
return this.queue.onIdle();
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Clears all pending, delayed, and throttled/debounced tasks from the scheduler.
|
|
1538
|
+
* In-flight active tasks will continue executing to completion or abort via signal.
|
|
1539
|
+
*/
|
|
1540
|
+
clear() {
|
|
1541
|
+
this.queue.clear();
|
|
1542
|
+
}
|
|
1543
|
+
/**
|
|
1544
|
+
* Returns the delightful Ahko mascot battery telemetry status.
|
|
1545
|
+
*
|
|
1546
|
+
* Low energy, completely chill.
|
|
1547
|
+
*/
|
|
1548
|
+
battery() {
|
|
1549
|
+
return {
|
|
1550
|
+
level: 3,
|
|
1551
|
+
chill: true,
|
|
1552
|
+
status: "low-energy",
|
|
1553
|
+
quote: "Mwee... my battery is low, but all your tasks are handled completely chill."
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Delightful alias for `onIdle()`: wait for all tasks to settle chill and relaxed.
|
|
1558
|
+
*
|
|
1559
|
+
* @returns Promise resolving when all tasks have finished.
|
|
1560
|
+
*/
|
|
1561
|
+
chill() {
|
|
1562
|
+
return this.onIdle();
|
|
1563
|
+
}
|
|
1242
1564
|
};
|
|
1243
1565
|
|
|
1244
1566
|
// src/version.ts
|
|
1245
|
-
var VERSION = "0.
|
|
1567
|
+
var VERSION = "1.0.0";
|
|
1246
1568
|
|
|
1247
1569
|
// src/errors/queue.error.ts
|
|
1248
1570
|
var AhkoQueueError = class extends AhkoError {
|