@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.cjs
CHANGED
|
@@ -150,6 +150,8 @@ var AhkoCancellationError = class extends AhkoError {
|
|
|
150
150
|
// src/scheduler/debounce-coordinator.ts
|
|
151
151
|
var DebounceCoordinator = class {
|
|
152
152
|
entries = /* @__PURE__ */ new Map();
|
|
153
|
+
/** Optional callback invoked whenever entries are settled or removed from coordinator */
|
|
154
|
+
onSettled;
|
|
153
155
|
/**
|
|
154
156
|
* Schedules a task under the debounce strategy.
|
|
155
157
|
*
|
|
@@ -236,6 +238,7 @@ var DebounceCoordinator = class {
|
|
|
236
238
|
return;
|
|
237
239
|
}
|
|
238
240
|
this.entries.delete(key);
|
|
241
|
+
this.onSettled?.();
|
|
239
242
|
if (entry.options?.signal && entry.abortListener) {
|
|
240
243
|
entry.options.signal.removeEventListener("abort", entry.abortListener);
|
|
241
244
|
}
|
|
@@ -259,6 +262,7 @@ var DebounceCoordinator = class {
|
|
|
259
262
|
}
|
|
260
263
|
clearTimeout(entry.timerId);
|
|
261
264
|
this.entries.delete(key);
|
|
265
|
+
this.onSettled?.();
|
|
262
266
|
if (entry.options?.signal && entry.abortListener) {
|
|
263
267
|
entry.options.signal.removeEventListener("abort", entry.abortListener);
|
|
264
268
|
}
|
|
@@ -278,7 +282,7 @@ var DebounceCoordinator = class {
|
|
|
278
282
|
* Cancels all pending debounced entries and clears the map.
|
|
279
283
|
*/
|
|
280
284
|
clear() {
|
|
281
|
-
for (const
|
|
285
|
+
for (const entry of this.entries.values()) {
|
|
282
286
|
clearTimeout(entry.timerId);
|
|
283
287
|
if (entry.options?.signal && entry.abortListener) {
|
|
284
288
|
entry.options.signal.removeEventListener("abort", entry.abortListener);
|
|
@@ -286,6 +290,7 @@ var DebounceCoordinator = class {
|
|
|
286
290
|
entry.reject(new AhkoCancellationError("Debounced tasks cleared"));
|
|
287
291
|
}
|
|
288
292
|
this.entries.clear();
|
|
293
|
+
this.onSettled?.();
|
|
289
294
|
}
|
|
290
295
|
};
|
|
291
296
|
|
|
@@ -331,6 +336,8 @@ var IdleScheduler = class {
|
|
|
331
336
|
// src/scheduler/throttle-coordinator.ts
|
|
332
337
|
var ThrottleCoordinator = class {
|
|
333
338
|
entries = /* @__PURE__ */ new Map();
|
|
339
|
+
/** Optional callback invoked whenever entries are settled or removed from coordinator */
|
|
340
|
+
onSettled;
|
|
334
341
|
/**
|
|
335
342
|
* Schedules a task under the throttle strategy.
|
|
336
343
|
*
|
|
@@ -416,6 +423,7 @@ var ThrottleCoordinator = class {
|
|
|
416
423
|
return;
|
|
417
424
|
}
|
|
418
425
|
this.entries.delete(key);
|
|
426
|
+
this.onSettled?.();
|
|
419
427
|
}
|
|
420
428
|
/**
|
|
421
429
|
* Cancels any pending trailing throttled task for a given key.
|
|
@@ -432,6 +440,7 @@ var ThrottleCoordinator = class {
|
|
|
432
440
|
clearTimeout(entry.windowTimerId);
|
|
433
441
|
}
|
|
434
442
|
this.entries.delete(key);
|
|
443
|
+
this.onSettled?.();
|
|
435
444
|
if (entry.trailingReject) {
|
|
436
445
|
const cancelError = new AhkoCancellationError(
|
|
437
446
|
typeof reason === "string" ? reason : "Throttled task was cancelled",
|
|
@@ -450,7 +459,7 @@ var ThrottleCoordinator = class {
|
|
|
450
459
|
* Clears all throttled entries and timers.
|
|
451
460
|
*/
|
|
452
461
|
clear() {
|
|
453
|
-
for (const
|
|
462
|
+
for (const entry of this.entries.values()) {
|
|
454
463
|
if (entry.windowTimerId !== void 0) {
|
|
455
464
|
clearTimeout(entry.windowTimerId);
|
|
456
465
|
}
|
|
@@ -459,6 +468,75 @@ var ThrottleCoordinator = class {
|
|
|
459
468
|
}
|
|
460
469
|
}
|
|
461
470
|
this.entries.clear();
|
|
471
|
+
this.onSettled?.();
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// src/events/event-emitter.ts
|
|
476
|
+
var AhkoEventEmitter = class {
|
|
477
|
+
listeners = /* @__PURE__ */ new Map();
|
|
478
|
+
/**
|
|
479
|
+
* Subscribes a listener to a specific Ahko lifecycle event.
|
|
480
|
+
*
|
|
481
|
+
* @param event - The event name to subscribe to.
|
|
482
|
+
* @param handler - The callback function to invoke when the event is emitted.
|
|
483
|
+
* @returns An unsubscribe function to remove the listener.
|
|
484
|
+
*/
|
|
485
|
+
on(event, handler) {
|
|
486
|
+
let set = this.listeners.get(event);
|
|
487
|
+
if (!set) {
|
|
488
|
+
set = /* @__PURE__ */ new Set();
|
|
489
|
+
this.listeners.set(event, set);
|
|
490
|
+
}
|
|
491
|
+
set.add(handler);
|
|
492
|
+
return () => {
|
|
493
|
+
this.off(event, handler);
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Unsubscribes a listener from a specific Ahko lifecycle event.
|
|
498
|
+
*
|
|
499
|
+
* @param event - The event name.
|
|
500
|
+
* @param handler - The callback function to remove.
|
|
501
|
+
*/
|
|
502
|
+
off(event, handler) {
|
|
503
|
+
const set = this.listeners.get(event);
|
|
504
|
+
if (set) {
|
|
505
|
+
set.delete(handler);
|
|
506
|
+
if (set.size === 0) {
|
|
507
|
+
this.listeners.delete(event);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Emits an event with the corresponding typed payload to all subscribed listeners.
|
|
513
|
+
* Listener invocations are safely isolated in try/catch to protect scheduler integrity.
|
|
514
|
+
*
|
|
515
|
+
* @param event - The event name to emit.
|
|
516
|
+
* @param payload - The event-specific payload data.
|
|
517
|
+
*/
|
|
518
|
+
emit(event, payload) {
|
|
519
|
+
const set = this.listeners.get(event);
|
|
520
|
+
if (!set || set.size === 0) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const handlers = Array.from(set);
|
|
524
|
+
for (const handler of handlers) {
|
|
525
|
+
try {
|
|
526
|
+
const result = handler(payload);
|
|
527
|
+
if (result && typeof result.catch === "function") {
|
|
528
|
+
result.catch(() => {
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Removes all registered event listeners.
|
|
537
|
+
*/
|
|
538
|
+
clear() {
|
|
539
|
+
this.listeners.clear();
|
|
462
540
|
}
|
|
463
541
|
};
|
|
464
542
|
|
|
@@ -486,6 +564,10 @@ var TaskQueue = class {
|
|
|
486
564
|
debounceCoordinator = new DebounceCoordinator();
|
|
487
565
|
/** Coordinator for throttled tasks with leading/trailing coalescing */
|
|
488
566
|
throttleCoordinator = new ThrottleCoordinator();
|
|
567
|
+
/** Lifecycle event emitter for task and scheduler events */
|
|
568
|
+
emitter = new AhkoEventEmitter();
|
|
569
|
+
/** Set of pending resolvers awaiting scheduler idle transition */
|
|
570
|
+
idleResolvers = /* @__PURE__ */ new Set();
|
|
489
571
|
/** WeakMap associating task runners with their scheduling options */
|
|
490
572
|
runnerOptions = /* @__PURE__ */ new WeakMap();
|
|
491
573
|
/** Cumulative completed tasks counter */
|
|
@@ -496,6 +578,10 @@ var TaskQueue = class {
|
|
|
496
578
|
cancelledTasks = 0;
|
|
497
579
|
/** Cumulative timed out tasks counter */
|
|
498
580
|
timedOutTasks = 0;
|
|
581
|
+
/** Cumulative count of retry attempts triggered */
|
|
582
|
+
retriedTasks = 0;
|
|
583
|
+
/** Cumulative count of tasks dispatched to concurrency slots */
|
|
584
|
+
totalDispatched = 0;
|
|
499
585
|
/**
|
|
500
586
|
* Creates a new TaskQueue.
|
|
501
587
|
*
|
|
@@ -516,6 +602,8 @@ var TaskQueue = class {
|
|
|
516
602
|
}
|
|
517
603
|
this.concurrency = concurrency;
|
|
518
604
|
this.minIntervalMs = minIntervalMs;
|
|
605
|
+
this.debounceCoordinator.onSettled = () => this.checkIdle();
|
|
606
|
+
this.throttleCoordinator.onSettled = () => this.checkIdle();
|
|
519
607
|
}
|
|
520
608
|
/**
|
|
521
609
|
* Enqueues a task runner according to the specified schedule options.
|
|
@@ -601,6 +689,11 @@ var TaskQueue = class {
|
|
|
601
689
|
if (index !== -1) {
|
|
602
690
|
this.queue.splice(index, 1);
|
|
603
691
|
this.cancelledTasks++;
|
|
692
|
+
this.emitter.emit("task:cancel", {
|
|
693
|
+
taskId: runner.taskId,
|
|
694
|
+
reason: "Task cancelled while queued"
|
|
695
|
+
});
|
|
696
|
+
this.checkIdle();
|
|
604
697
|
}
|
|
605
698
|
};
|
|
606
699
|
this.queue.push(runner);
|
|
@@ -624,6 +717,11 @@ var TaskQueue = class {
|
|
|
624
717
|
if (index !== -1) {
|
|
625
718
|
this.queue.splice(index, 1);
|
|
626
719
|
this.cancelledTasks++;
|
|
720
|
+
this.emitter.emit("task:cancel", {
|
|
721
|
+
taskId: runner.taskId,
|
|
722
|
+
reason: "Task cancelled while queued"
|
|
723
|
+
});
|
|
724
|
+
this.checkIdle();
|
|
627
725
|
}
|
|
628
726
|
};
|
|
629
727
|
this.queue.push(runner);
|
|
@@ -636,6 +734,11 @@ var TaskQueue = class {
|
|
|
636
734
|
clearTimeout(delayedEntry.timerId);
|
|
637
735
|
this.delayedEntries.delete(delayedEntry);
|
|
638
736
|
this.cancelledTasks++;
|
|
737
|
+
this.emitter.emit("task:cancel", {
|
|
738
|
+
taskId: runner.taskId,
|
|
739
|
+
reason: "Task cancelled while waiting in delay"
|
|
740
|
+
});
|
|
741
|
+
this.checkIdle();
|
|
639
742
|
}
|
|
640
743
|
};
|
|
641
744
|
}
|
|
@@ -655,6 +758,11 @@ var TaskQueue = class {
|
|
|
655
758
|
if (index !== -1) {
|
|
656
759
|
this.queue.splice(index, 1);
|
|
657
760
|
this.cancelledTasks++;
|
|
761
|
+
this.emitter.emit("task:cancel", {
|
|
762
|
+
taskId: runner.taskId,
|
|
763
|
+
reason: "Task cancelled while queued"
|
|
764
|
+
});
|
|
765
|
+
this.checkIdle();
|
|
658
766
|
}
|
|
659
767
|
};
|
|
660
768
|
this.queue.push(runner);
|
|
@@ -667,6 +775,11 @@ var TaskQueue = class {
|
|
|
667
775
|
handle.cancel();
|
|
668
776
|
this.idleEntries.delete(idleEntry);
|
|
669
777
|
this.cancelledTasks++;
|
|
778
|
+
this.emitter.emit("task:cancel", {
|
|
779
|
+
taskId: runner.taskId,
|
|
780
|
+
reason: "Task cancelled while waiting for idle"
|
|
781
|
+
});
|
|
782
|
+
this.checkIdle();
|
|
670
783
|
}
|
|
671
784
|
};
|
|
672
785
|
}
|
|
@@ -735,36 +848,69 @@ var TaskQueue = class {
|
|
|
735
848
|
*/
|
|
736
849
|
async executeRunner(runner) {
|
|
737
850
|
const options = this.runnerOptions.get(runner);
|
|
851
|
+
this.totalDispatched++;
|
|
852
|
+
this.emitter.emit("task:start", {
|
|
853
|
+
taskId: runner.taskId,
|
|
854
|
+
attempt: runner.attempt
|
|
855
|
+
});
|
|
738
856
|
try {
|
|
739
857
|
const result = await runner.run();
|
|
740
858
|
this.completedTasks++;
|
|
741
859
|
this.activeRunners.delete(runner);
|
|
742
860
|
this.runnerOptions.delete(runner);
|
|
861
|
+
this.emitter.emit("task:complete", {
|
|
862
|
+
taskId: runner.taskId,
|
|
863
|
+
attempt: runner.attempt,
|
|
864
|
+
durationMs: runner.lastDurationMs,
|
|
865
|
+
result
|
|
866
|
+
});
|
|
743
867
|
runner.resolve(result);
|
|
744
868
|
} catch (error) {
|
|
745
869
|
if (runner.state === "cancelled" /* CANCELLED */) {
|
|
746
870
|
this.cancelledTasks++;
|
|
747
871
|
this.activeRunners.delete(runner);
|
|
748
872
|
this.runnerOptions.delete(runner);
|
|
873
|
+
this.emitter.emit("task:cancel", {
|
|
874
|
+
taskId: runner.taskId,
|
|
875
|
+
reason: error
|
|
876
|
+
});
|
|
749
877
|
runner.reject(error);
|
|
750
878
|
return;
|
|
751
879
|
}
|
|
752
880
|
const shouldRetry = await runner.canRetry(error, options?.retry);
|
|
753
881
|
if (shouldRetry) {
|
|
882
|
+
this.retriedTasks++;
|
|
754
883
|
this.activeRunners.delete(runner);
|
|
884
|
+
this.emitter.emit("task:fail", {
|
|
885
|
+
taskId: runner.taskId,
|
|
886
|
+
attempt: runner.attempt - 1,
|
|
887
|
+
error,
|
|
888
|
+
willRetry: true
|
|
889
|
+
});
|
|
755
890
|
this.scheduleRetry(runner, options);
|
|
756
891
|
return;
|
|
757
892
|
}
|
|
758
893
|
if (runner.state === "timed_out" /* TIMED_OUT */ || error instanceof AhkoTimeoutError) {
|
|
759
894
|
this.timedOutTasks++;
|
|
895
|
+
this.emitter.emit("task:timeout", {
|
|
896
|
+
taskId: runner.taskId,
|
|
897
|
+
timeoutMs: runner.timeoutMs
|
|
898
|
+
});
|
|
760
899
|
} else {
|
|
761
900
|
this.failedTasks++;
|
|
762
901
|
}
|
|
763
902
|
this.activeRunners.delete(runner);
|
|
764
903
|
this.runnerOptions.delete(runner);
|
|
904
|
+
this.emitter.emit("task:fail", {
|
|
905
|
+
taskId: runner.taskId,
|
|
906
|
+
attempt: runner.attempt,
|
|
907
|
+
error,
|
|
908
|
+
willRetry: false
|
|
909
|
+
});
|
|
765
910
|
runner.reject(error);
|
|
766
911
|
} finally {
|
|
767
912
|
this.pump();
|
|
913
|
+
this.checkIdle();
|
|
768
914
|
}
|
|
769
915
|
}
|
|
770
916
|
/**
|
|
@@ -779,6 +925,11 @@ var TaskQueue = class {
|
|
|
779
925
|
if (index !== -1) {
|
|
780
926
|
this.queue.splice(index, 1);
|
|
781
927
|
this.cancelledTasks++;
|
|
928
|
+
this.emitter.emit("task:cancel", {
|
|
929
|
+
taskId: runner.taskId,
|
|
930
|
+
reason: "Task cancelled while queued"
|
|
931
|
+
});
|
|
932
|
+
this.checkIdle();
|
|
782
933
|
}
|
|
783
934
|
};
|
|
784
935
|
this.queue.push(runner);
|
|
@@ -797,6 +948,11 @@ var TaskQueue = class {
|
|
|
797
948
|
if (index !== -1) {
|
|
798
949
|
this.queue.splice(index, 1);
|
|
799
950
|
this.cancelledTasks++;
|
|
951
|
+
this.emitter.emit("task:cancel", {
|
|
952
|
+
taskId: runner.taskId,
|
|
953
|
+
reason: "Task cancelled while queued"
|
|
954
|
+
});
|
|
955
|
+
this.checkIdle();
|
|
800
956
|
}
|
|
801
957
|
};
|
|
802
958
|
this.queue.push(runner);
|
|
@@ -809,9 +965,91 @@ var TaskQueue = class {
|
|
|
809
965
|
clearTimeout(retryEntry.timerId);
|
|
810
966
|
this.retryEntries.delete(retryEntry);
|
|
811
967
|
this.cancelledTasks++;
|
|
968
|
+
this.emitter.emit("task:cancel", {
|
|
969
|
+
taskId: runner.taskId,
|
|
970
|
+
reason: "Task cancelled during retry backoff"
|
|
971
|
+
});
|
|
972
|
+
this.checkIdle();
|
|
812
973
|
}
|
|
813
974
|
};
|
|
814
975
|
}
|
|
976
|
+
/**
|
|
977
|
+
* Checks whether the scheduler has transitioned to idle and notifies listeners/resolvers.
|
|
978
|
+
*/
|
|
979
|
+
checkIdle() {
|
|
980
|
+
if (this.isIdle()) {
|
|
981
|
+
if (this.idleResolvers.size > 0) {
|
|
982
|
+
for (const resolve of this.idleResolvers) {
|
|
983
|
+
resolve();
|
|
984
|
+
}
|
|
985
|
+
this.idleResolvers.clear();
|
|
986
|
+
}
|
|
987
|
+
this.emitter.emit("idle", { timestamp: Date.now() });
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Checks whether the scheduler is currently idle (no active runners and no pending tasks).
|
|
992
|
+
*
|
|
993
|
+
* @returns True if completely idle, false otherwise.
|
|
994
|
+
*/
|
|
995
|
+
isIdle() {
|
|
996
|
+
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;
|
|
997
|
+
}
|
|
998
|
+
/**
|
|
999
|
+
* Returns a promise that resolves once the scheduler has processed all tasks and is idle.
|
|
1000
|
+
*
|
|
1001
|
+
* @returns Promise resolving when idle.
|
|
1002
|
+
*/
|
|
1003
|
+
onIdle() {
|
|
1004
|
+
if (this.isIdle()) {
|
|
1005
|
+
return Promise.resolve();
|
|
1006
|
+
}
|
|
1007
|
+
return new Promise((resolve) => {
|
|
1008
|
+
this.idleResolvers.add(resolve);
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Clears all pending and waiting tasks from the scheduler, cancelling their runners.
|
|
1013
|
+
* Active tasks currently in flight will continue to run to completion or abort via signal.
|
|
1014
|
+
*/
|
|
1015
|
+
clear() {
|
|
1016
|
+
while (this.queue.length > 0) {
|
|
1017
|
+
const runner = this.queue.shift();
|
|
1018
|
+
if (runner && runner.state !== "cancelled" /* CANCELLED */) {
|
|
1019
|
+
runner.cancel("Scheduler cleared");
|
|
1020
|
+
this.cancelledTasks++;
|
|
1021
|
+
this.emitter.emit("task:cancel", { taskId: runner.taskId, reason: "Scheduler cleared" });
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
for (const entry of this.delayedEntries.values()) {
|
|
1025
|
+
clearTimeout(entry.timerId);
|
|
1026
|
+
entry.runner.cancel("Scheduler cleared");
|
|
1027
|
+
this.cancelledTasks++;
|
|
1028
|
+
this.emitter.emit("task:cancel", { taskId: entry.runner.taskId, reason: "Scheduler cleared" });
|
|
1029
|
+
}
|
|
1030
|
+
this.delayedEntries.clear();
|
|
1031
|
+
for (const entry of this.idleEntries.values()) {
|
|
1032
|
+
entry.handle.cancel();
|
|
1033
|
+
entry.runner.cancel("Scheduler cleared");
|
|
1034
|
+
this.cancelledTasks++;
|
|
1035
|
+
this.emitter.emit("task:cancel", { taskId: entry.runner.taskId, reason: "Scheduler cleared" });
|
|
1036
|
+
}
|
|
1037
|
+
this.idleEntries.clear();
|
|
1038
|
+
for (const entry of this.retryEntries.values()) {
|
|
1039
|
+
clearTimeout(entry.timerId);
|
|
1040
|
+
entry.runner.cancel("Scheduler cleared");
|
|
1041
|
+
this.cancelledTasks++;
|
|
1042
|
+
this.emitter.emit("task:cancel", { taskId: entry.runner.taskId, reason: "Scheduler cleared" });
|
|
1043
|
+
}
|
|
1044
|
+
this.retryEntries.clear();
|
|
1045
|
+
this.debounceCoordinator.clear();
|
|
1046
|
+
this.throttleCoordinator.clear();
|
|
1047
|
+
if (this.rateLimitTimer !== void 0) {
|
|
1048
|
+
clearTimeout(this.rateLimitTimer);
|
|
1049
|
+
this.rateLimitTimer = void 0;
|
|
1050
|
+
}
|
|
1051
|
+
this.checkIdle();
|
|
1052
|
+
}
|
|
815
1053
|
/**
|
|
816
1054
|
* Returns telemetry snapshot for the scheduler.
|
|
817
1055
|
*
|
|
@@ -825,6 +1063,8 @@ var TaskQueue = class {
|
|
|
825
1063
|
failedTasks: this.failedTasks,
|
|
826
1064
|
cancelledTasks: this.cancelledTasks,
|
|
827
1065
|
timedOutTasks: this.timedOutTasks,
|
|
1066
|
+
retriedTasks: this.retriedTasks,
|
|
1067
|
+
totalDispatched: this.totalDispatched,
|
|
828
1068
|
capacity: this.concurrency
|
|
829
1069
|
});
|
|
830
1070
|
}
|
|
@@ -859,6 +1099,8 @@ var TaskRunner = class {
|
|
|
859
1099
|
onCancel;
|
|
860
1100
|
/** Current execution attempt count (1-indexed) */
|
|
861
1101
|
attempt = 1;
|
|
1102
|
+
/** Duration of the most recent execution attempt in milliseconds */
|
|
1103
|
+
lastDurationMs = 0;
|
|
862
1104
|
/**
|
|
863
1105
|
* Creates a new TaskRunner instance.
|
|
864
1106
|
*
|
|
@@ -1017,9 +1259,11 @@ var TaskRunner = class {
|
|
|
1017
1259
|
if (timeoutPromise) {
|
|
1018
1260
|
racePromises.push(timeoutPromise);
|
|
1019
1261
|
}
|
|
1262
|
+
const startTime = Date.now();
|
|
1020
1263
|
try {
|
|
1021
1264
|
const result = await Promise.race(racePromises);
|
|
1022
1265
|
this.clearTimeoutTimer();
|
|
1266
|
+
this.lastDurationMs = Math.max(0, Date.now() - startTime);
|
|
1023
1267
|
if (abortListener) {
|
|
1024
1268
|
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
1025
1269
|
}
|
|
@@ -1036,6 +1280,7 @@ var TaskRunner = class {
|
|
|
1036
1280
|
return result;
|
|
1037
1281
|
} catch (error) {
|
|
1038
1282
|
this.clearTimeoutTimer();
|
|
1283
|
+
this.lastDurationMs = Math.max(0, Date.now() - startTime);
|
|
1039
1284
|
if (abortListener) {
|
|
1040
1285
|
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
1041
1286
|
}
|
|
@@ -1277,10 +1522,87 @@ var Ahko = class {
|
|
|
1277
1522
|
stats() {
|
|
1278
1523
|
return this.queue.getStats();
|
|
1279
1524
|
}
|
|
1525
|
+
/**
|
|
1526
|
+
* Subscribes to a scheduler lifecycle event.
|
|
1527
|
+
*
|
|
1528
|
+
* @param event - Event name to listen for.
|
|
1529
|
+
* @param handler - Callback function invoked when the event is emitted.
|
|
1530
|
+
* @returns Unsubscribe function to remove the listener.
|
|
1531
|
+
*
|
|
1532
|
+
* @example
|
|
1533
|
+
* ```typescript
|
|
1534
|
+
* const unsubscribe = ahko.on("task:start", ({ taskId, attempt }) => {
|
|
1535
|
+
* console.log(`Task ${taskId} started attempt ${attempt}`);
|
|
1536
|
+
* });
|
|
1537
|
+
* ```
|
|
1538
|
+
*/
|
|
1539
|
+
on(event, handler) {
|
|
1540
|
+
return this.queue.emitter.on(event, handler);
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Unsubscribes an event listener from a scheduler lifecycle event.
|
|
1544
|
+
*
|
|
1545
|
+
* @param event - Event name.
|
|
1546
|
+
* @param handler - The exact listener callback to remove.
|
|
1547
|
+
*/
|
|
1548
|
+
off(event, handler) {
|
|
1549
|
+
this.queue.emitter.off(event, handler);
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Checks whether the scheduler is currently idle (no active runners and no pending tasks).
|
|
1553
|
+
*
|
|
1554
|
+
* @returns True if completely idle, false otherwise.
|
|
1555
|
+
*/
|
|
1556
|
+
isIdle() {
|
|
1557
|
+
return this.queue.isIdle();
|
|
1558
|
+
}
|
|
1559
|
+
/**
|
|
1560
|
+
* Returns a promise that resolves once the scheduler has completed all tasks and is idle.
|
|
1561
|
+
*
|
|
1562
|
+
* @returns Promise resolving when the scheduler is idle.
|
|
1563
|
+
*
|
|
1564
|
+
* @example
|
|
1565
|
+
* ```typescript
|
|
1566
|
+
* ahko.schedule(doWork);
|
|
1567
|
+
* await ahko.onIdle();
|
|
1568
|
+
* console.log("All work finished!");
|
|
1569
|
+
* ```
|
|
1570
|
+
*/
|
|
1571
|
+
onIdle() {
|
|
1572
|
+
return this.queue.onIdle();
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* Clears all pending, delayed, and throttled/debounced tasks from the scheduler.
|
|
1576
|
+
* In-flight active tasks will continue executing to completion or abort via signal.
|
|
1577
|
+
*/
|
|
1578
|
+
clear() {
|
|
1579
|
+
this.queue.clear();
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Returns the delightful Ahko mascot battery telemetry status.
|
|
1583
|
+
*
|
|
1584
|
+
* Low energy, completely chill.
|
|
1585
|
+
*/
|
|
1586
|
+
battery() {
|
|
1587
|
+
return {
|
|
1588
|
+
level: 3,
|
|
1589
|
+
chill: true,
|
|
1590
|
+
status: "low-energy",
|
|
1591
|
+
quote: "Mwee... my battery is low, but all your tasks are handled completely chill."
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Delightful alias for `onIdle()`: wait for all tasks to settle chill and relaxed.
|
|
1596
|
+
*
|
|
1597
|
+
* @returns Promise resolving when all tasks have finished.
|
|
1598
|
+
*/
|
|
1599
|
+
chill() {
|
|
1600
|
+
return this.onIdle();
|
|
1601
|
+
}
|
|
1280
1602
|
};
|
|
1281
1603
|
|
|
1282
1604
|
// src/version.ts
|
|
1283
|
-
var VERSION = "0.
|
|
1605
|
+
var VERSION = "1.0.0";
|
|
1284
1606
|
|
|
1285
1607
|
// src/errors/queue.error.ts
|
|
1286
1608
|
var AhkoQueueError = class extends AhkoError {
|