@supergrowthai/tq 1.0.4 → 1.0.6

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.
Files changed (44) hide show
  1. package/README.md +138 -0
  2. package/dist/{PrismaAdapter-D-Bjaonm.cjs → PrismaAdapter-CT8dxOZX.cjs} +51 -3
  3. package/dist/PrismaAdapter-CT8dxOZX.cjs.map +1 -0
  4. package/dist/{PrismaAdapter-Kp4A4VHb.js → PrismaAdapter-Z2vLslDJ.js} +51 -3
  5. package/dist/PrismaAdapter-Z2vLslDJ.js.map +1 -0
  6. package/dist/adapters/index.cjs +1 -1
  7. package/dist/adapters/index.mjs +1 -1
  8. package/dist/core/Actions.cjs +13 -0
  9. package/dist/core/Actions.cjs.map +1 -1
  10. package/dist/core/Actions.mjs +13 -0
  11. package/dist/core/Actions.mjs.map +1 -1
  12. package/dist/index.cjs +603 -128
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.mjs +609 -134
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/src/adapters/ITaskStorageAdapter.d.cts +12 -0
  17. package/dist/src/adapters/ITaskStorageAdapter.d.ts +12 -0
  18. package/dist/src/adapters/InMemoryAdapter.d.cts +26 -1
  19. package/dist/src/adapters/InMemoryAdapter.d.ts +26 -1
  20. package/dist/src/adapters/MongoDbAdapter.d.cts +29 -2
  21. package/dist/src/adapters/MongoDbAdapter.d.ts +29 -2
  22. package/dist/src/adapters/PrismaAdapter.d.cts +36 -2
  23. package/dist/src/adapters/PrismaAdapter.d.ts +36 -2
  24. package/dist/src/adapters/index.d.cts +1 -1
  25. package/dist/src/adapters/index.d.ts +1 -1
  26. package/dist/src/core/Actions.d.cts +5 -0
  27. package/dist/src/core/Actions.d.ts +5 -0
  28. package/dist/src/core/TaskHandler.d.cts +22 -2
  29. package/dist/src/core/TaskHandler.d.ts +22 -2
  30. package/dist/src/core/TaskRunner.d.cts +10 -1
  31. package/dist/src/core/TaskRunner.d.ts +10 -1
  32. package/dist/src/core/lifecycle.d.cts +133 -0
  33. package/dist/src/core/lifecycle.d.ts +133 -0
  34. package/dist/src/index.d.cts +2 -0
  35. package/dist/src/index.d.ts +2 -0
  36. package/dist/src/providers/ConsoleHealthProvider.d.cts +47 -0
  37. package/dist/src/providers/ConsoleHealthProvider.d.ts +47 -0
  38. package/dist/src/providers/index.d.cts +1 -0
  39. package/dist/src/providers/index.d.ts +1 -0
  40. package/dist/src/utils/disposable-lock.d.cts +39 -0
  41. package/dist/src/utils/disposable-lock.d.ts +39 -0
  42. package/package.json +7 -7
  43. package/dist/PrismaAdapter-D-Bjaonm.cjs.map +0 -1
  44. package/dist/PrismaAdapter-Kp4A4VHb.js.map +0 -1
package/dist/index.mjs CHANGED
@@ -1,4 +1,49 @@
1
- import { I, M, P } from "./PrismaAdapter-Kp4A4VHb.js";
1
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name);
2
+ var __typeError = (msg) => {
3
+ throw TypeError(msg);
4
+ };
5
+ var __using = (stack, value, async) => {
6
+ if (value != null) {
7
+ if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected");
8
+ var dispose, inner;
9
+ if (async) dispose = value[__knownSymbol("asyncDispose")];
10
+ if (dispose === void 0) {
11
+ dispose = value[__knownSymbol("dispose")];
12
+ if (async) inner = dispose;
13
+ }
14
+ if (typeof dispose !== "function") __typeError("Object not disposable");
15
+ if (inner) dispose = function() {
16
+ try {
17
+ inner.call(this);
18
+ } catch (e) {
19
+ return Promise.reject(e);
20
+ }
21
+ };
22
+ stack.push([async, dispose, value]);
23
+ } else if (async) {
24
+ stack.push([async]);
25
+ }
26
+ return value;
27
+ };
28
+ var __callDispose = (stack, error, hasError) => {
29
+ var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) {
30
+ return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _;
31
+ };
32
+ var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e);
33
+ var next = (it) => {
34
+ while (it = stack.pop()) {
35
+ try {
36
+ var result = it[1] && it[1].call(it[2]);
37
+ if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next()));
38
+ } catch (e) {
39
+ fail(e);
40
+ }
41
+ }
42
+ if (hasError) throw error;
43
+ };
44
+ return next();
45
+ };
46
+ import { I, M, P } from "./PrismaAdapter-Z2vLslDJ.js";
2
47
  import { getEnvironmentQueueName } from "@supergrowthai/mq";
3
48
  import { L as Logger, a as LogLevel } from "./client-BAiCkZv7.js";
4
49
  import { tId } from "./utils/task-id-gen.mjs";
@@ -256,8 +301,8 @@ function require_Symbol() {
256
301
  if (hasRequired_Symbol) return _Symbol;
257
302
  hasRequired_Symbol = 1;
258
303
  var root = require_root();
259
- var Symbol = root.Symbol;
260
- _Symbol = Symbol;
304
+ var Symbol2 = root.Symbol;
305
+ _Symbol = Symbol2;
261
306
  return _Symbol;
262
307
  }
263
308
  var _getRawTag;
@@ -265,11 +310,11 @@ var hasRequired_getRawTag;
265
310
  function require_getRawTag() {
266
311
  if (hasRequired_getRawTag) return _getRawTag;
267
312
  hasRequired_getRawTag = 1;
268
- var Symbol = require_Symbol();
313
+ var Symbol2 = require_Symbol();
269
314
  var objectProto = Object.prototype;
270
315
  var hasOwnProperty = objectProto.hasOwnProperty;
271
316
  var nativeObjectToString = objectProto.toString;
272
- var symToStringTag = Symbol ? Symbol.toStringTag : void 0;
317
+ var symToStringTag = Symbol2 ? Symbol2.toStringTag : void 0;
273
318
  function getRawTag(value) {
274
319
  var isOwn = hasOwnProperty.call(value, symToStringTag), tag = value[symToStringTag];
275
320
  try {
@@ -308,9 +353,9 @@ var hasRequired_baseGetTag;
308
353
  function require_baseGetTag() {
309
354
  if (hasRequired_baseGetTag) return _baseGetTag;
310
355
  hasRequired_baseGetTag = 1;
311
- var Symbol = require_Symbol(), getRawTag = require_getRawTag(), objectToString = require_objectToString();
356
+ var Symbol2 = require_Symbol(), getRawTag = require_getRawTag(), objectToString = require_objectToString();
312
357
  var nullTag = "[object Null]", undefinedTag = "[object Undefined]";
313
- var symToStringTag = Symbol ? Symbol.toStringTag : void 0;
358
+ var symToStringTag = Symbol2 ? Symbol2.toStringTag : void 0;
314
359
  function baseGetTag(value) {
315
360
  if (value == null) {
316
361
  return value === void 0 ? undefinedTag : nullTag;
@@ -553,144 +598,326 @@ function requireChunk() {
553
598
  }
554
599
  var chunkExports = requireChunk();
555
600
  const chunk = /* @__PURE__ */ getDefaultExportFromCjs(chunkExports);
601
+ class DisposableLockBatch {
602
+ constructor(lockManager, onError) {
603
+ this.lockManager = lockManager;
604
+ this.onError = onError;
605
+ this.lockIds = [];
606
+ this.disposed = false;
607
+ }
608
+ /**
609
+ * Get the list of currently held lock IDs
610
+ */
611
+ get heldLocks() {
612
+ return this.lockIds;
613
+ }
614
+ /**
615
+ * Number of locks currently held
616
+ */
617
+ get size() {
618
+ return this.lockIds.length;
619
+ }
620
+ /**
621
+ * Acquire a lock and track it for automatic release
622
+ */
623
+ async acquire(lockId, timeout) {
624
+ if (this.disposed) {
625
+ throw new Error("Cannot acquire lock on disposed DisposableLockBatch");
626
+ }
627
+ const acquired = await this.lockManager.acquire(lockId, timeout);
628
+ if (acquired) {
629
+ this.lockIds.push(lockId);
630
+ }
631
+ return acquired;
632
+ }
633
+ /**
634
+ * AsyncDisposable implementation - releases all locks
635
+ */
636
+ async [Symbol.asyncDispose]() {
637
+ if (this.disposed) return;
638
+ this.disposed = true;
639
+ await Promise.all(
640
+ this.lockIds.map(
641
+ (lockId) => this.lockManager.release(lockId).catch((err) => {
642
+ if (this.onError) {
643
+ this.onError(lockId, err);
644
+ }
645
+ })
646
+ )
647
+ );
648
+ }
649
+ }
556
650
  class TaskRunner {
557
- constructor(messageQueue, taskQueue, taskStore, cacheProvider, generateId) {
651
+ constructor(messageQueue, taskQueue, taskStore, cacheProvider, generateId, lifecycleProvider, lifecycleConfig) {
558
652
  this.messageQueue = messageQueue;
559
653
  this.taskQueue = taskQueue;
560
654
  this.taskStore = taskStore;
561
655
  this.generateId = generateId;
656
+ this.lifecycleProvider = lifecycleProvider;
657
+ this.lifecycleConfig = lifecycleConfig;
658
+ this.taskStartTimes = /* @__PURE__ */ new Map();
562
659
  this.logger = new Logger("TaskRunner", LogLevel.INFO);
563
660
  this.lockManager = new LockManager(cacheProvider, {
564
661
  prefix: "task_lock_",
565
662
  defaultTimeout: 30 * 60
566
663
  });
567
664
  }
665
+ // ============ Lifecycle Helpers ============
568
666
  async run(taskRunnerId, tasksRaw, asyncTaskManager, abortSignal) {
569
- this.logger.info(`[${taskRunnerId}] Starting task runner`);
570
- this.logger.info(`[${taskRunnerId}] Processing ${tasksRaw.length} provided tasks`);
571
- if (abortSignal?.aborted) {
572
- this.logger.info(`[${taskRunnerId}] AbortSignal already aborted, returning empty results`);
573
- return { successTasks: [], failedTasks: [], newTasks: [], ignoredTasks: [], asyncTasks: [] };
574
- }
575
- const tasks = await this.lockManager.filterLocked(tasksRaw, tId);
576
- this.logger.info(`[${taskRunnerId}] Found ${tasks.length} not locked tasks to process`);
577
- await Promise.all(tasks.map((t) => this.lockManager.acquire(tId(t))));
578
- const groupedTasksObject = tasks.reduce((acc, task) => {
579
- acc[task.type] = acc[task.type] || [];
580
- acc[task.type].push(task);
581
- return acc;
582
- }, {});
583
- const groupedTasksArray = Object.keys(groupedTasksObject).reduce((acc, type) => {
584
- acc.push({ type, tasks: groupedTasksObject[type] });
585
- return acc;
586
- }, []);
587
- this.logger.info(`[${taskRunnerId}] Task groups: ${groupedTasksArray.map((g) => `${g.type}: ${g.tasks.length}`).join(", ")}`);
588
- const actions = new Actions(taskRunnerId);
589
- const asyncTasks = [];
590
- const processedTaskIds = /* @__PURE__ */ new Set();
591
- for (let i = 0; i < groupedTasksArray.length; i++) {
667
+ var _stack = [];
668
+ try {
669
+ this.logger.info(`[${taskRunnerId}] Starting task runner`);
670
+ this.logger.info(`[${taskRunnerId}] Processing ${tasksRaw.length} provided tasks`);
592
671
  if (abortSignal?.aborted) {
593
- this.logger.info(`[${taskRunnerId}] AbortSignal detected, stopping task group processing`);
594
- break;
672
+ this.logger.info(`[${taskRunnerId}] AbortSignal already aborted, returning empty results`);
673
+ return { successTasks: [], failedTasks: [], newTasks: [], ignoredTasks: [], asyncTasks: [] };
595
674
  }
596
- const taskGroup = groupedTasksArray[i];
597
- const firstTask = taskGroup.tasks[0];
598
- if (!firstTask) {
599
- this.logger.warn(`[${taskRunnerId}] No tasks found for type: ${taskGroup.type}`);
600
- continue;
675
+ const tasks = await this.lockManager.filterLocked(tasksRaw, tId);
676
+ this.logger.info(`[${taskRunnerId}] Found ${tasks.length} not locked tasks to process`);
677
+ const locks = __using(_stack, new DisposableLockBatch(
678
+ this.lockManager,
679
+ (lockId, err) => this.logger.error(`[${taskRunnerId}] Failed to release lock ${lockId}:`, err)
680
+ ), true);
681
+ for (const task of tasks) {
682
+ await locks.acquire(tId(task));
601
683
  }
602
- const executor = this.taskQueue.getExecutor(firstTask.queue_id, taskGroup.type);
603
- if (!executor) {
604
- this.logger.warn(`[${taskRunnerId}] No executor found for type: ${taskGroup.type} in queue ${firstTask.queue_id}`);
605
- for (const task of taskGroup.tasks) {
606
- const taskWithId = task.id ? task : { ...task, id: this.generateId() };
607
- actions.addIgnoredTask(taskWithId);
684
+ const groupedTasksObject = tasks.reduce((acc, task) => {
685
+ acc[task.type] = acc[task.type] || [];
686
+ acc[task.type].push(task);
687
+ return acc;
688
+ }, {});
689
+ const groupedTasksArray = Object.keys(groupedTasksObject).reduce((acc, type) => {
690
+ acc.push({ type, tasks: groupedTasksObject[type] });
691
+ return acc;
692
+ }, []);
693
+ this.logger.info(`[${taskRunnerId}] Task groups: ${groupedTasksArray.map((g) => `${g.type}: ${g.tasks.length}`).join(", ")}`);
694
+ const actions = new Actions(taskRunnerId);
695
+ const asyncTasks = [];
696
+ const processedTaskIds = /* @__PURE__ */ new Set();
697
+ for (let i = 0; i < groupedTasksArray.length; i++) {
698
+ if (abortSignal?.aborted) {
699
+ this.logger.info(`[${taskRunnerId}] AbortSignal detected, stopping task group processing`);
700
+ break;
608
701
  }
609
- continue;
610
- }
611
- if (executor.asyncConfig?.handoffTimeout && asyncTaskManager && !asyncTaskManager.canAcceptTask()) {
612
- this.logger.warn(`[${taskRunnerId}] Async queue full, rescheduling ${taskGroup.tasks.length} ${taskGroup.type} tasks for 3 min later`);
613
- const rescheduledTasks = taskGroup.tasks.map((task) => ({
614
- ...task,
615
- execute_at: new Date(Date.now() + 18e4),
616
- status: "scheduled"
617
- }));
618
- await this.taskStore.updateTasksForRetry(rescheduledTasks);
619
- continue;
620
- }
621
- taskGroup.tasks.forEach((task) => processedTaskIds.add(tId(task)));
622
- this.logger.info(`[${taskRunnerId}] Processing ${taskGroup.tasks.length} tasks of type: ${taskGroup.type}`);
623
- if (executor.multiple) {
624
- await executor.onTasks(taskGroup.tasks, actions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTasks failed: ${err}`));
625
- } else {
626
- if (executor.parallel) {
627
- const chunks = chunk(taskGroup.tasks, executor.chunkSize);
628
- this.logger.info(`[${taskRunnerId}] Processing in parallel chunks of ${executor.chunkSize}`);
629
- for (const chunk2 of chunks) {
630
- const chunkTasks = [];
631
- for (let j = 0; j < chunk2.length; j++) {
632
- const taskActions = actions.forkForTask(chunk2[j]);
633
- chunkTasks.push(executor.onTask(chunk2[j], taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`)));
634
- }
635
- await Promise.all(chunkTasks);
702
+ const taskGroup = groupedTasksArray[i];
703
+ const firstTask = taskGroup.tasks[0];
704
+ if (!firstTask) {
705
+ this.logger.warn(`[${taskRunnerId}] No tasks found for type: ${taskGroup.type}`);
706
+ continue;
707
+ }
708
+ const executor = this.taskQueue.getExecutor(firstTask.queue_id, taskGroup.type);
709
+ if (!executor) {
710
+ this.logger.warn(`[${taskRunnerId}] No executor found for type: ${taskGroup.type} in queue ${firstTask.queue_id}`);
711
+ for (const task of taskGroup.tasks) {
712
+ const taskWithId = task.id ? task : { ...task, id: this.generateId() };
713
+ actions.addIgnoredTask(taskWithId);
636
714
  }
715
+ continue;
716
+ }
717
+ if (executor.asyncConfig?.handoffTimeout && asyncTaskManager && !asyncTaskManager.canAcceptTask()) {
718
+ this.logger.warn(`[${taskRunnerId}] Async queue full, rescheduling ${taskGroup.tasks.length} ${taskGroup.type} tasks for 3 min later`);
719
+ const rescheduledTasks = taskGroup.tasks.map((task) => ({
720
+ ...task,
721
+ execute_at: new Date(Date.now() + 18e4),
722
+ status: "scheduled"
723
+ }));
724
+ await this.taskStore.updateTasksForRetry(rescheduledTasks);
725
+ continue;
726
+ }
727
+ taskGroup.tasks.forEach((task) => processedTaskIds.add(tId(task)));
728
+ this.logger.info(`[${taskRunnerId}] Processing ${taskGroup.tasks.length} tasks of type: ${taskGroup.type}`);
729
+ if (executor.multiple) {
730
+ await executor.onTasks(taskGroup.tasks, actions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTasks failed: ${err}`));
637
731
  } else {
638
- const timeoutMs = executor.asyncConfig?.handoffTimeout;
639
- for (let j = 0; j < taskGroup.tasks.length; j++) {
640
- const task = taskGroup.tasks[j];
641
- if (!timeoutMs) {
642
- const taskActions = actions.forkForTask(task);
643
- await executor.onTask(task, taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`));
644
- } else {
645
- const startTime = Date.now();
646
- const taskActions = actions.forkForTask(task);
647
- const taskPromise = executor.onTask(task, taskActions).catch((err) => {
648
- this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`);
649
- });
650
- let timeoutId;
651
- const timeoutPromise = new Promise((resolve) => {
652
- timeoutId = setTimeout(() => {
653
- resolve("~~~timeout");
654
- }, timeoutMs);
655
- });
656
- const result = await Promise.race([taskPromise, timeoutPromise]);
657
- if (timeoutId) {
658
- clearTimeout(timeoutId);
732
+ if (executor.parallel) {
733
+ const chunks = chunk(taskGroup.tasks, executor.chunkSize);
734
+ this.logger.info(`[${taskRunnerId}] Processing in parallel chunks of ${executor.chunkSize}`);
735
+ for (const taskChunk of chunks) {
736
+ for (const task of taskChunk) {
737
+ this.emitTaskStarted(task, taskRunnerId);
738
+ }
739
+ const chunkPromises = [];
740
+ for (let j = 0; j < taskChunk.length; j++) {
741
+ const taskActions = actions.forkForTask(taskChunk[j]);
742
+ chunkPromises.push(executor.onTask(taskChunk[j], taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`)));
743
+ }
744
+ await Promise.all(chunkPromises);
745
+ for (const task of taskChunk) {
746
+ const resultStatus = actions.getTaskResultStatus(tId(task));
747
+ if (resultStatus === "success") {
748
+ this.emitTaskCompleted(task, taskRunnerId);
749
+ } else if (resultStatus === "fail") {
750
+ const retryCount = task.execution_stats?.retry_count || 0;
751
+ const maxRetries = task.retries ?? executor.default_retries ?? 0;
752
+ const willRetry = retryCount < maxRetries;
753
+ this.emitTaskFailed(task, taskRunnerId, new Error("Task failed"), willRetry);
754
+ }
659
755
  }
660
- if (result === "~~~timeout") {
661
- this.logger.info(`[${taskRunnerId}] Task ${tId(task)} (${task.type}) exceeded ${timeoutMs}ms, marking for async handoff`);
662
- if (!asyncTaskManager) {
663
- throw new Error(`Task ${task.type} exceeded timeout but AsyncTaskManager not initialized!`);
756
+ }
757
+ } else {
758
+ const timeoutMs = executor.asyncConfig?.handoffTimeout;
759
+ for (let j = 0; j < taskGroup.tasks.length; j++) {
760
+ const task = taskGroup.tasks[j];
761
+ if (!timeoutMs) {
762
+ this.emitTaskStarted(task, taskRunnerId);
763
+ const taskActions = actions.forkForTask(task);
764
+ await executor.onTask(task, taskActions).catch((err) => this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`));
765
+ const resultStatus = actions.getTaskResultStatus(tId(task));
766
+ if (resultStatus === "success") {
767
+ this.emitTaskCompleted(task, taskRunnerId);
768
+ } else if (resultStatus === "fail") {
769
+ const retryCount = task.execution_stats?.retry_count || 0;
770
+ const maxRetries = task.retries ?? executor.default_retries ?? 0;
771
+ const willRetry = retryCount < maxRetries;
772
+ this.emitTaskFailed(task, taskRunnerId, new Error("Task failed"), willRetry);
773
+ }
774
+ } else {
775
+ this.emitTaskStarted(task, taskRunnerId);
776
+ const startTime = Date.now();
777
+ const taskActions = actions.forkForTask(task);
778
+ const taskPromise = executor.onTask(task, taskActions).catch((err) => {
779
+ this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`);
780
+ });
781
+ let timeoutId;
782
+ const timeoutPromise = new Promise((resolve) => {
783
+ timeoutId = setTimeout(() => {
784
+ resolve("~~~timeout");
785
+ }, timeoutMs);
786
+ });
787
+ const result = await Promise.race([taskPromise, timeoutPromise]);
788
+ if (timeoutId) {
789
+ clearTimeout(timeoutId);
664
790
  }
665
- if (!task.id) {
666
- this.logger.error(`[${taskRunnerId}] Cannot hand off task without id (type: ${task.type}). Task will continue but won't be tracked.`);
667
- } else {
668
- const asyncActions = new AsyncActions(this.messageQueue, this.taskStore, this.taskQueue, actions, task, this.generateId);
669
- const asyncPromise = taskPromise.finally(async () => {
670
- try {
671
- await asyncActions.onPromiseFulfilled();
672
- } catch (err) {
673
- this.logger.error(`[${taskRunnerId}] Failed to execute async actions for task ${tId(task)}:`, err);
674
- }
675
- });
676
- asyncTasks.push({
677
- task,
678
- promise: asyncPromise,
679
- startTime,
680
- actions: asyncActions
681
- });
791
+ if (result !== "~~~timeout") {
792
+ const resultStatus = actions.getTaskResultStatus(tId(task));
793
+ if (resultStatus === "success") {
794
+ this.emitTaskCompleted(task, taskRunnerId);
795
+ } else if (resultStatus === "fail") {
796
+ const retryCount = task.execution_stats?.retry_count || 0;
797
+ const maxRetries = task.retries ?? executor.default_retries ?? 0;
798
+ const willRetry = retryCount < maxRetries;
799
+ this.emitTaskFailed(task, taskRunnerId, new Error("Task failed"), willRetry);
800
+ }
801
+ }
802
+ if (result === "~~~timeout") {
803
+ this.logger.info(`[${taskRunnerId}] Task ${tId(task)} (${task.type}) exceeded ${timeoutMs}ms, marking for async handoff`);
804
+ if (!asyncTaskManager) {
805
+ throw new Error(`Task ${task.type} exceeded timeout but AsyncTaskManager not initialized!`);
806
+ }
807
+ if (!task.id) {
808
+ this.logger.error(`[${taskRunnerId}] Cannot hand off task without id (type: ${task.type}). Task will continue but won't be tracked.`);
809
+ } else {
810
+ const asyncActions = new AsyncActions(this.messageQueue, this.taskStore, this.taskQueue, actions, task, this.generateId);
811
+ const asyncPromise = taskPromise.finally(async () => {
812
+ try {
813
+ await asyncActions.onPromiseFulfilled();
814
+ } catch (err) {
815
+ this.logger.error(`[${taskRunnerId}] Failed to execute async actions for task ${tId(task)}:`, err);
816
+ }
817
+ });
818
+ asyncTasks.push({
819
+ task,
820
+ promise: asyncPromise,
821
+ startTime,
822
+ actions: asyncActions
823
+ });
824
+ }
682
825
  }
683
826
  }
684
827
  }
685
828
  }
686
829
  }
687
830
  }
831
+ const asyncTaskIds = asyncTasks.map((at) => tId(at.task));
832
+ const results = actions.extractSyncResults(asyncTaskIds);
833
+ this.logger.info(`[${taskRunnerId}] Completing run - Success: ${results.successTasks.length}, Failed: ${results.failedTasks.length}, New: ${results.newTasks.length}, Async: ${asyncTasks.length}, Ignored: ${results.ignoredTasks.length}`);
834
+ return { ...results, asyncTasks };
835
+ } catch (_) {
836
+ var _error = _, _hasError = true;
837
+ } finally {
838
+ var _promise = __callDispose(_stack, _error, _hasError);
839
+ _promise && await _promise;
840
+ }
841
+ }
842
+ emitLifecycleEvent(callback, ctx) {
843
+ if (!callback) return;
844
+ try {
845
+ const result = callback(ctx);
846
+ if (result instanceof Promise) {
847
+ result.catch((err) => {
848
+ this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
849
+ });
850
+ }
851
+ } catch (err) {
852
+ this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
853
+ }
854
+ }
855
+ buildTaskContext(task, workerId) {
856
+ const retryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
857
+ const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
858
+ const maxRetries = task.retries ?? executor?.default_retries ?? 0;
859
+ const payload = task.payload;
860
+ return {
861
+ task_id: task.id?.toString() || tId(task),
862
+ task_hash: payload?.task_hash,
863
+ task_type: task.type,
864
+ queue_id: task.queue_id,
865
+ payload: this.lifecycleConfig?.include_payload ? payload : {},
866
+ attempt: retryCount + 1,
867
+ max_retries: maxRetries,
868
+ scheduled_at: task.created_at || /* @__PURE__ */ new Date(),
869
+ worker_id: workerId
870
+ };
871
+ }
872
+ emitTaskStarted(task, workerId) {
873
+ const startedAt = Date.now();
874
+ this.taskStartTimes.set(tId(task), startedAt);
875
+ if (this.lifecycleProvider?.onTaskStarted) {
876
+ const ctx = this.buildTaskContext(task, workerId);
877
+ const queuedDuration = startedAt - (task.created_at?.getTime() || startedAt);
878
+ this.emitLifecycleEvent(
879
+ this.lifecycleProvider.onTaskStarted,
880
+ {
881
+ ...ctx,
882
+ started_at: new Date(startedAt),
883
+ queued_duration_ms: queuedDuration
884
+ }
885
+ );
886
+ }
887
+ }
888
+ emitTaskCompleted(task, workerId, result) {
889
+ const completedAt = Date.now();
890
+ const startedAt = this.taskStartTimes.get(tId(task)) || completedAt;
891
+ this.taskStartTimes.delete(tId(task));
892
+ if (this.lifecycleProvider?.onTaskCompleted) {
893
+ const ctx = this.buildTaskContext(task, workerId);
894
+ const timing = {
895
+ queued_duration_ms: startedAt - (task.created_at?.getTime() || startedAt),
896
+ processing_duration_ms: completedAt - startedAt,
897
+ total_duration_ms: completedAt - (task.created_at?.getTime() || completedAt)
898
+ };
899
+ this.emitLifecycleEvent(
900
+ this.lifecycleProvider.onTaskCompleted,
901
+ { ...ctx, timing, result }
902
+ );
903
+ }
904
+ }
905
+ emitTaskFailed(task, workerId, error, willRetry, nextAttemptAt) {
906
+ const completedAt = Date.now();
907
+ const startedAt = this.taskStartTimes.get(tId(task)) || completedAt;
908
+ this.taskStartTimes.delete(tId(task));
909
+ if (this.lifecycleProvider?.onTaskFailed) {
910
+ const ctx = this.buildTaskContext(task, workerId);
911
+ const timing = {
912
+ queued_duration_ms: startedAt - (task.created_at?.getTime() || startedAt),
913
+ processing_duration_ms: completedAt - startedAt,
914
+ total_duration_ms: completedAt - (task.created_at?.getTime() || completedAt)
915
+ };
916
+ this.emitLifecycleEvent(
917
+ this.lifecycleProvider.onTaskFailed,
918
+ { ...ctx, timing, error, will_retry: willRetry, next_attempt_at: nextAttemptAt }
919
+ );
688
920
  }
689
- const asyncTaskIds = asyncTasks.map((at) => tId(at.task));
690
- const results = actions.extractSyncResults(asyncTaskIds);
691
- this.logger.info(`[${taskRunnerId}] Completing run - Success: ${results.successTasks.length}, Failed: ${results.failedTasks.length}, New: ${results.newTasks.length}, Async: ${asyncTasks.length}, Ignored: ${results.ignoredTasks.length}`);
692
- await Promise.all(tasks.filter((t) => processedTaskIds.has(tId(t))).map((t) => this.lockManager.release(tId(t))));
693
- return { ...results, asyncTasks };
694
921
  }
695
922
  }
696
923
  function getEnabledQueues() {
@@ -704,7 +931,7 @@ const STATS_THRESHOLD = parseInt(process.env.TQ_STATS_THRESHOLD || "1000");
704
931
  const FAILURE_THRESHOLD = parseInt(process.env.TQ_STATS_FAILURE_THRESHOLD || "100");
705
932
  const INSTANCE_ID = process.env.INSTANCE_ID || "unknown";
706
933
  class TaskHandler {
707
- constructor(messageQueue, taskQueuesManager, databaseAdapter, cacheAdapter, asyncTaskManager, notificationProvider) {
934
+ constructor(messageQueue, taskQueuesManager, databaseAdapter, cacheAdapter, asyncTaskManager, notificationProvider, config) {
708
935
  this.messageQueue = messageQueue;
709
936
  this.taskQueuesManager = taskQueuesManager;
710
937
  this.databaseAdapter = databaseAdapter;
@@ -712,10 +939,39 @@ class TaskHandler {
712
939
  this.asyncTaskManager = asyncTaskManager;
713
940
  this.notificationProvider = notificationProvider;
714
941
  this.matureTaskTimer = null;
942
+ this.heartbeatTimer = null;
943
+ this.enabledQueues = [];
944
+ this.workerStarted = false;
945
+ this.workerStats = {
946
+ tasks_processed: 0,
947
+ tasks_succeeded: 0,
948
+ tasks_failed: 0,
949
+ avg_processing_ms: 0,
950
+ current_task: void 0
951
+ };
952
+ this.totalProcessingMs = 0;
715
953
  this.queueStats = /* @__PURE__ */ new Map();
716
954
  this.logger = new Logger("TaskHandler", LogLevel.INFO);
955
+ this.config = config || {};
956
+ this.workerId = `${(void 0)()}-${process.pid}-${Date.now()}`;
957
+ this.workerStartedAt = /* @__PURE__ */ new Date();
717
958
  this.taskStore = new TaskStore(databaseAdapter);
718
- this.taskRunner = new TaskRunner(messageQueue, taskQueuesManager, this.taskStore, this.cacheAdapter, databaseAdapter.generateId.bind(databaseAdapter));
959
+ this.taskRunner = new TaskRunner(
960
+ messageQueue,
961
+ taskQueuesManager,
962
+ this.taskStore,
963
+ this.cacheAdapter,
964
+ databaseAdapter.generateId.bind(databaseAdapter),
965
+ this.config.lifecycleProvider,
966
+ this.config.lifecycle
967
+ );
968
+ }
969
+ // ============ Lifecycle Event Helpers ============
970
+ get lifecycleProvider() {
971
+ return this.config.lifecycleProvider;
972
+ }
973
+ get workerProvider() {
974
+ return this.config.workerProvider;
719
975
  }
720
976
  async addTasks(tasks) {
721
977
  const diffedItems = tasks.reduce(
@@ -741,24 +997,40 @@ class TaskHandler {
741
997
  const iQueues = Object.keys(diffedItems.immediate);
742
998
  for (let i = 0; i < iQueues.length; i++) {
743
999
  const queue = iQueues[i];
744
- const tasks2 = diffedItems.immediate[queue].map((task) => {
1000
+ const queueTasks = diffedItems.immediate[queue].map((task) => {
745
1001
  const executor = this.taskQueuesManager.getExecutor(task.queue_id, task.type);
746
1002
  const shouldStoreOnFailure = executor?.store_on_failure ?? false;
747
1003
  const id = shouldStoreOnFailure ? { id: this.databaseAdapter.generateId() } : {};
748
1004
  return { ...id, ...task };
749
1005
  });
750
- await this.messageQueue.addMessages(queue, tasks2);
1006
+ await this.messageQueue.addMessages(queue, queueTasks);
1007
+ if (this.lifecycleProvider?.onTaskScheduled) {
1008
+ for (const task of queueTasks) {
1009
+ this.emitLifecycleEvent(
1010
+ this.lifecycleProvider.onTaskScheduled,
1011
+ this.buildTaskContext(task)
1012
+ );
1013
+ }
1014
+ }
751
1015
  }
752
1016
  const fQueues = Object.keys(diffedItems.future);
753
1017
  for (let i = 0; i < fQueues.length; i++) {
754
1018
  const queue = fQueues[i];
755
- const tasks2 = diffedItems.future[queue].map((task) => {
1019
+ const queueTasks = diffedItems.future[queue].map((task) => {
756
1020
  const executor = this.taskQueuesManager.getExecutor(task.queue_id, task.type);
757
1021
  const shouldStoreOnFailure = executor?.store_on_failure ?? false;
758
1022
  const id = shouldStoreOnFailure ? { id: this.databaseAdapter.generateId() } : {};
759
1023
  return { ...id, ...task };
760
1024
  });
761
- await this.taskStore.addTasksToScheduled(tasks2);
1025
+ await this.taskStore.addTasksToScheduled(queueTasks);
1026
+ if (this.lifecycleProvider?.onTaskScheduled) {
1027
+ for (const task of queueTasks) {
1028
+ this.emitLifecycleEvent(
1029
+ this.lifecycleProvider.onTaskScheduled,
1030
+ this.buildTaskContext(task)
1031
+ );
1032
+ }
1033
+ }
762
1034
  }
763
1035
  }
764
1036
  async postProcessTasks({
@@ -769,10 +1041,12 @@ class TaskHandler {
769
1041
  const tasksToRetry = [];
770
1042
  const finalFailedTasks = [];
771
1043
  let discardedTasksCount = 0;
1044
+ const MAX_RETRY_DELAY_MS = 5 * 60 * 1e3;
772
1045
  for (const task of failedTasksRaw) {
773
1046
  const taskRetryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
774
1047
  const taskRetryAfter = task.retry_after || 2e3;
775
- const retryAfter = taskRetryAfter * Math.pow(taskRetryCount + 1, 2);
1048
+ const calculatedDelay = taskRetryAfter * Math.pow(taskRetryCount + 1, 2);
1049
+ const retryAfter = Math.min(calculatedDelay, MAX_RETRY_DELAY_MS);
776
1050
  const executeAt = Date.now() + retryAfter;
777
1051
  const maxRetries = this.getRetryCount(task);
778
1052
  if (task.id && taskRetryCount < maxRetries) {
@@ -814,6 +1088,23 @@ class TaskHandler {
814
1088
  } else {
815
1089
  discardedTasksCount++;
816
1090
  this.logger.info(`Discarding task of type ${task.type} after ${taskRetryCount} retries`);
1091
+ if (this.lifecycleProvider?.onTaskExhausted) {
1092
+ const ctx = this.buildTaskContext(task);
1093
+ const errorMessage = task.execution_stats?.last_error || "Task exhausted all retries";
1094
+ this.emitLifecycleEvent(
1095
+ this.lifecycleProvider.onTaskExhausted,
1096
+ {
1097
+ ...ctx,
1098
+ timing: {
1099
+ queued_duration_ms: 0,
1100
+ processing_duration_ms: 0,
1101
+ total_duration_ms: Date.now() - (task.created_at?.getTime() || Date.now())
1102
+ },
1103
+ error: new Error(errorMessage),
1104
+ total_attempts: taskRetryCount + 1
1105
+ }
1106
+ );
1107
+ }
817
1108
  }
818
1109
  }
819
1110
  if (discardedTasksCount > 0) {
@@ -838,6 +1129,18 @@ class TaskHandler {
838
1129
  this.logger.info(`AbortSignal detected, skipping processing of ${tasks.length} tasks for stream ${streamName}`);
839
1130
  return { failedTasks: [], newTasks: [], successTasks: [], asyncTasks: [], ignoredTasks: [] };
840
1131
  }
1132
+ const batchStartTime = Date.now();
1133
+ const taskTypes = [...new Set(tasks.map((t) => t.type))];
1134
+ if (this.workerProvider?.onBatchStarted) {
1135
+ this.emitLifecycleEvent(
1136
+ this.workerProvider.onBatchStarted,
1137
+ {
1138
+ ...this.buildWorkerInfo(),
1139
+ batch_size: tasks.length,
1140
+ task_types: taskTypes
1141
+ }
1142
+ );
1143
+ }
841
1144
  this.logger.debug(`Processing ${tasks.length} tasks for stream ${streamName}`);
842
1145
  const {
843
1146
  failedTasks,
@@ -888,10 +1191,118 @@ class TaskHandler {
888
1191
  stats.async += asyncTasks.length;
889
1192
  stats.ignored += ignoredTasks.length;
890
1193
  await this.reportQueueStats(streamName);
1194
+ const batchDuration = Date.now() - batchStartTime;
1195
+ this.updateWorkerStats(successTasks.length, failedTasks.length, batchDuration);
1196
+ if (this.workerProvider?.onBatchCompleted) {
1197
+ this.emitLifecycleEvent(
1198
+ this.workerProvider.onBatchCompleted,
1199
+ {
1200
+ ...this.buildWorkerInfo(),
1201
+ batch_size: tasks.length,
1202
+ succeeded: successTasks.length,
1203
+ failed: failedTasks.length,
1204
+ duration_ms: batchDuration
1205
+ }
1206
+ );
1207
+ }
891
1208
  this.logger.debug(`Completed processing for stream ${streamName}: ${successTasks.length} succeeded, ${failedTasks.length} failed, ${newTasks.length} new tasks, ${ignoredTasks.length} ignored`);
892
1209
  return { failedTasks, newTasks, successTasks, asyncTasks, ignoredTasks };
893
1210
  }, abortSignal);
894
1211
  }
1212
+ taskProcessServer(abortSignal) {
1213
+ const queues = getEnabledQueues();
1214
+ this.enabledQueues = queues;
1215
+ if (!this.workerStarted) {
1216
+ this.workerStarted = true;
1217
+ this.emitWorkerStarted();
1218
+ this.startHeartbeat();
1219
+ }
1220
+ for (let i = 0; i < queues.length; i++) {
1221
+ this.logger.info(`Starting consumer for queue: ${queues[i]}`);
1222
+ this.startConsumingTasks(queues[i], abortSignal);
1223
+ }
1224
+ this.logger.info("Starting mature tasks processor");
1225
+ this.processMatureTasks(abortSignal);
1226
+ abortSignal?.addEventListener("abort", () => {
1227
+ this.stopHeartbeat();
1228
+ this.emitWorkerStopped("shutdown");
1229
+ });
1230
+ }
1231
+ buildWorkerInfo() {
1232
+ return {
1233
+ worker_id: this.workerId,
1234
+ hostname: (void 0)(),
1235
+ pid: process.pid,
1236
+ started_at: this.workerStartedAt,
1237
+ enabled_queues: this.enabledQueues
1238
+ };
1239
+ }
1240
+ emitWorkerStarted() {
1241
+ if (!this.workerProvider?.onWorkerStarted) return;
1242
+ this.emitLifecycleEvent(this.workerProvider.onWorkerStarted, this.buildWorkerInfo());
1243
+ }
1244
+ emitWorkerStopped(reason) {
1245
+ if (!this.workerProvider?.onWorkerStopped) return;
1246
+ this.emitLifecycleEvent(
1247
+ this.workerProvider.onWorkerStopped,
1248
+ {
1249
+ ...this.buildWorkerInfo(),
1250
+ reason,
1251
+ final_stats: { ...this.workerStats }
1252
+ }
1253
+ );
1254
+ }
1255
+ emitWorkerHeartbeat() {
1256
+ if (!this.workerProvider?.onWorkerHeartbeat) return;
1257
+ const memUsage = process.memoryUsage();
1258
+ this.emitLifecycleEvent(
1259
+ this.workerProvider.onWorkerHeartbeat,
1260
+ {
1261
+ ...this.buildWorkerInfo(),
1262
+ stats: { ...this.workerStats },
1263
+ memory_usage_mb: memUsage.heapUsed / 1024 / 1024
1264
+ }
1265
+ );
1266
+ }
1267
+ startHeartbeat() {
1268
+ if (this.heartbeatTimer) return;
1269
+ const intervalMs = this.config.lifecycle?.heartbeat_interval_ms || 5e3;
1270
+ this.heartbeatTimer = setInterval(() => {
1271
+ this.emitWorkerHeartbeat();
1272
+ }, intervalMs);
1273
+ }
1274
+ stopHeartbeat() {
1275
+ if (this.heartbeatTimer) {
1276
+ clearInterval(this.heartbeatTimer);
1277
+ this.heartbeatTimer = null;
1278
+ }
1279
+ }
1280
+ updateWorkerStats(succeeded, failed, processingMs) {
1281
+ this.workerStats.tasks_processed += succeeded + failed;
1282
+ this.workerStats.tasks_succeeded += succeeded;
1283
+ this.workerStats.tasks_failed += failed;
1284
+ this.totalProcessingMs += processingMs;
1285
+ if (this.workerStats.tasks_processed > 0) {
1286
+ this.workerStats.avg_processing_ms = this.totalProcessingMs / this.workerStats.tasks_processed;
1287
+ }
1288
+ }
1289
+ emitLifecycleEvent(callback, ctx) {
1290
+ if (!callback) return;
1291
+ try {
1292
+ const result = callback(ctx);
1293
+ if (this.config.lifecycle?.mode === "sync" && result instanceof Promise) {
1294
+ result.catch((err) => {
1295
+ this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
1296
+ });
1297
+ } else if (result instanceof Promise) {
1298
+ result.catch((err) => {
1299
+ this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
1300
+ });
1301
+ }
1302
+ } catch (err) {
1303
+ this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
1304
+ }
1305
+ }
895
1306
  processMatureTasks(abortSignal) {
896
1307
  if (this.matureTaskTimer) clearInterval(this.matureTaskTimer);
897
1308
  if (abortSignal?.aborted) {
@@ -935,14 +1346,20 @@ class TaskHandler {
935
1346
  });
936
1347
  });
937
1348
  }
938
- taskProcessServer(abortSignal) {
939
- const queues = getEnabledQueues();
940
- for (let i = 0; i < queues.length; i++) {
941
- this.logger.info(`Starting consumer for queue: ${queues[i]}`);
942
- this.startConsumingTasks(queues[i], abortSignal);
943
- }
944
- this.logger.info("Starting mature tasks processor");
945
- this.processMatureTasks(abortSignal);
1349
+ buildTaskContext(task) {
1350
+ const maxRetries = this.getRetryCount(task);
1351
+ const retryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
1352
+ const payload = task.payload;
1353
+ return {
1354
+ task_id: task.id?.toString() || "",
1355
+ task_hash: payload?.task_hash,
1356
+ task_type: task.type,
1357
+ queue_id: task.queue_id,
1358
+ payload: this.config.lifecycle?.include_payload ? payload : {},
1359
+ attempt: retryCount + 1,
1360
+ max_retries: maxRetries,
1361
+ scheduled_at: task.created_at || /* @__PURE__ */ new Date()
1362
+ };
946
1363
  }
947
1364
  processBatch(queueId, processor, limit, abortSignal) {
948
1365
  if (abortSignal?.aborted) {
@@ -1054,6 +1471,7 @@ class TaskHandler {
1054
1471
  }
1055
1472
  }
1056
1473
  }
1474
+ const logger = new Logger("TaskQueuesManager", LogLevel.INFO);
1057
1475
  class TaskQueuesManager {
1058
1476
  constructor(messageQueue) {
1059
1477
  this.messageQueue = messageQueue;
@@ -1073,7 +1491,7 @@ class TaskQueuesManager {
1073
1491
  }
1074
1492
  const queueMap = this.queueTaskExecutorMap.get(queueName);
1075
1493
  queueMap.set(taskType, executor);
1076
- console.log(`Registered task executor for ${taskType} on queue ${queueName}`);
1494
+ logger.info(`Registered task executor for ${taskType} on queue ${queueName}`);
1077
1495
  }
1078
1496
  /**
1079
1497
  * Gets the task executor for a specific queue and task type
@@ -1110,10 +1528,67 @@ class TaskQueuesManager {
1110
1528
  return Array.from(queueMap.keys());
1111
1529
  }
1112
1530
  }
1531
+ class ConsoleHealthProvider {
1532
+ constructor(prefix = "[Health]") {
1533
+ this.prefix = prefix;
1534
+ }
1535
+ // ============ Task Lifecycle ============
1536
+ onTaskScheduled(ctx) {
1537
+ console.log(`${this.prefix} Task scheduled: ${ctx.task_type} (${ctx.task_id}) in queue ${ctx.queue_id}`);
1538
+ }
1539
+ onTaskStarted(ctx) {
1540
+ console.log(`${this.prefix} Task started: ${ctx.task_type} (${ctx.task_id}) - queued for ${ctx.queued_duration_ms}ms`);
1541
+ }
1542
+ onTaskCompleted(ctx) {
1543
+ console.log(
1544
+ `${this.prefix} Task completed: ${ctx.task_type} (${ctx.task_id}) - processing: ${ctx.timing.processing_duration_ms}ms, total: ${ctx.timing.total_duration_ms}ms`
1545
+ );
1546
+ }
1547
+ onTaskFailed(ctx) {
1548
+ console.log(
1549
+ `${this.prefix} Task failed: ${ctx.task_type} (${ctx.task_id}) - error: ${ctx.error.message}, will_retry: ${ctx.will_retry}`
1550
+ );
1551
+ }
1552
+ onTaskExhausted(ctx) {
1553
+ console.log(
1554
+ `${this.prefix} Task exhausted: ${ctx.task_type} (${ctx.task_id}) - total attempts: ${ctx.total_attempts}, error: ${ctx.error.message}`
1555
+ );
1556
+ }
1557
+ onTaskCancelled(ctx) {
1558
+ console.log(`${this.prefix} Task cancelled: ${ctx.task_type} (${ctx.task_id}) - reason: ${ctx.reason}`);
1559
+ }
1560
+ // ============ Worker Lifecycle ============
1561
+ onWorkerStarted(info) {
1562
+ console.log(
1563
+ `${this.prefix} Worker started: ${info.worker_id} on ${info.hostname} (PID: ${info.pid}) - queues: [${info.enabled_queues.join(", ")}]`
1564
+ );
1565
+ }
1566
+ onWorkerHeartbeat(info) {
1567
+ console.log(
1568
+ `${this.prefix} Worker heartbeat: ${info.worker_id} - processed: ${info.stats.tasks_processed}, success: ${info.stats.tasks_succeeded}, failed: ${info.stats.tasks_failed}, avg: ${info.stats.avg_processing_ms.toFixed(2)}ms, memory: ${info.memory_usage_mb.toFixed(2)}MB`
1569
+ );
1570
+ }
1571
+ onWorkerStopped(info) {
1572
+ console.log(
1573
+ `${this.prefix} Worker stopped: ${info.worker_id} - reason: ${info.reason} - final stats: ${info.final_stats.tasks_processed} processed, ${info.final_stats.tasks_succeeded} succeeded, ${info.final_stats.tasks_failed} failed`
1574
+ );
1575
+ }
1576
+ onBatchStarted(info) {
1577
+ console.log(
1578
+ `${this.prefix} Batch started: ${info.batch_size} tasks - types: [${info.task_types.join(", ")}]`
1579
+ );
1580
+ }
1581
+ onBatchCompleted(info) {
1582
+ console.log(
1583
+ `${this.prefix} Batch completed: ${info.batch_size} tasks in ${info.duration_ms}ms - succeeded: ${info.succeeded}, failed: ${info.failed}`
1584
+ );
1585
+ }
1586
+ }
1113
1587
  export {
1114
1588
  Actions,
1115
1589
  AsyncActions,
1116
1590
  AsyncTaskManager,
1591
+ ConsoleHealthProvider,
1117
1592
  I as InMemoryAdapter,
1118
1593
  M as MongoDbAdapter,
1119
1594
  P as PrismaAdapter,