@supergrowthai/tq 1.0.13 → 1.1.1
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/README.md +149 -8
- package/dist/{AsyncActions-CZYO8ShR.js → AsyncActions-B8ImDgTo.js} +39 -3
- package/dist/AsyncActions-B8ImDgTo.js.map +1 -0
- package/dist/{AsyncActions-BOO1ikWz.cjs → AsyncActions-BsxMX_Ib.cjs} +39 -3
- package/dist/AsyncActions-BsxMX_Ib.cjs.map +1 -0
- package/dist/core/Actions.cjs +23 -1
- package/dist/core/Actions.cjs.map +1 -1
- package/dist/core/Actions.mjs +23 -1
- package/dist/core/Actions.mjs.map +1 -1
- package/dist/core/async/AsyncActions.cjs +1 -1
- package/dist/core/async/AsyncActions.mjs +1 -1
- package/dist/index.cjs +459 -226
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +459 -226
- package/dist/index.mjs.map +1 -1
- package/dist/src/core/Actions.d.cts +5 -1
- package/dist/src/core/Actions.d.ts +5 -1
- package/dist/src/core/TaskHandler.d.cts +6 -0
- package/dist/src/core/TaskHandler.d.ts +6 -0
- package/dist/src/core/TaskRunner.d.cts +22 -5
- package/dist/src/core/TaskRunner.d.ts +22 -5
- package/dist/src/core/async/AsyncActions.d.cts +1 -0
- package/dist/src/core/async/AsyncActions.d.ts +1 -0
- package/dist/src/core/flow/FlowMiddleware.d.cts +6 -1
- package/dist/src/core/flow/FlowMiddleware.d.ts +6 -1
- package/dist/src/core/flow/IFlowBarrierProvider.d.cts +4 -0
- package/dist/src/core/flow/IFlowBarrierProvider.d.ts +4 -0
- package/dist/src/core/flow/InMemoryFlowBarrierProvider.d.cts +1 -0
- package/dist/src/core/flow/InMemoryFlowBarrierProvider.d.ts +1 -0
- package/dist/src/core/lifecycle.d.cts +98 -3
- package/dist/src/core/lifecycle.d.ts +98 -3
- package/dist/src/providers/ConsoleHealthProvider.d.cts +42 -2
- package/dist/src/providers/ConsoleHealthProvider.d.ts +42 -2
- package/dist/src/test/lifecycle-events.test.d.cts +31 -0
- package/dist/src/test/lifecycle-events.test.d.ts +31 -0
- package/package.json +2 -2
- package/dist/AsyncActions-BOO1ikWz.cjs.map +0 -1
- package/dist/AsyncActions-CZYO8ShR.js.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -48,7 +48,7 @@ import { getEnvironmentQueueName } from "@supergrowthai/mq";
|
|
|
48
48
|
import { L as Logger, a as LogLevel } from "./client-dvHNt8qU.js";
|
|
49
49
|
import { tId } from "./utils/task-id-gen.mjs";
|
|
50
50
|
import { Actions } from "./core/Actions.mjs";
|
|
51
|
-
import { b as buildProjection, s as syncProjections, A as AsyncActions } from "./AsyncActions-
|
|
51
|
+
import { b as buildProjection, s as syncProjections, A as AsyncActions } from "./AsyncActions-B8ImDgTo.js";
|
|
52
52
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
53
53
|
import moment from "moment";
|
|
54
54
|
import * as os from "os";
|
|
@@ -663,7 +663,22 @@ function getLogContext() {
|
|
|
663
663
|
return als.getStore();
|
|
664
664
|
}
|
|
665
665
|
class TaskRunner {
|
|
666
|
-
constructor(
|
|
666
|
+
constructor(opts) {
|
|
667
|
+
this.taskStartTimes = /* @__PURE__ */ new Map();
|
|
668
|
+
const {
|
|
669
|
+
messageQueue,
|
|
670
|
+
taskQueue,
|
|
671
|
+
taskStore,
|
|
672
|
+
cacheProvider,
|
|
673
|
+
generateId,
|
|
674
|
+
lifecycleProvider,
|
|
675
|
+
lifecycleConfig,
|
|
676
|
+
entityProjection,
|
|
677
|
+
entityProjectionConfig,
|
|
678
|
+
flowMiddleware,
|
|
679
|
+
flowLifecycleProvider,
|
|
680
|
+
workerId = ""
|
|
681
|
+
} = opts;
|
|
667
682
|
this.messageQueue = messageQueue;
|
|
668
683
|
this.taskQueue = taskQueue;
|
|
669
684
|
this.taskStore = taskStore;
|
|
@@ -673,7 +688,8 @@ class TaskRunner {
|
|
|
673
688
|
this.entityProjection = entityProjection;
|
|
674
689
|
this.entityProjectionConfig = entityProjectionConfig;
|
|
675
690
|
this.flowMiddleware = flowMiddleware;
|
|
676
|
-
this.
|
|
691
|
+
this.flowLifecycleProvider = flowLifecycleProvider;
|
|
692
|
+
this.workerId = workerId;
|
|
677
693
|
this.logger = new Logger("TaskRunner", LogLevel.INFO);
|
|
678
694
|
this.lockManager = new LockManager(cacheProvider, {
|
|
679
695
|
prefix: "task_lock_",
|
|
@@ -740,7 +756,7 @@ class TaskRunner {
|
|
|
740
756
|
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
741
757
|
}
|
|
742
758
|
}
|
|
743
|
-
const actions = new Actions(taskRunnerId);
|
|
759
|
+
const actions = new Actions(taskRunnerId, this.flowLifecycleProvider, this.workerId);
|
|
744
760
|
const asyncTasks = [];
|
|
745
761
|
const processedTaskIds = /* @__PURE__ */ new Set();
|
|
746
762
|
for (let i = 0; i < groupedTasksArray.length; i++) {
|
|
@@ -776,7 +792,20 @@ class TaskRunner {
|
|
|
776
792
|
taskGroup.tasks.forEach((task) => processedTaskIds.add(tId(task)));
|
|
777
793
|
this.logger.info(`[${taskRunnerId}] Processing ${taskGroup.tasks.length} tasks of type: ${taskGroup.type}`);
|
|
778
794
|
if (executor.multiple) {
|
|
779
|
-
const batchStore = this.buildBatchLogStore(taskGroup.tasks,
|
|
795
|
+
const batchStore = this.buildBatchLogStore(taskGroup.tasks, this.workerId);
|
|
796
|
+
const batchTaskContexts = taskGroup.tasks.map((t) => this.buildTaskContext(t, this.workerId, taskRunnerId));
|
|
797
|
+
const batchStartedAt = Date.now();
|
|
798
|
+
this.emitLifecycleEvent(
|
|
799
|
+
this.lifecycleProvider?.onTaskBatchStarted,
|
|
800
|
+
{
|
|
801
|
+
task_type: taskGroup.type,
|
|
802
|
+
queue_id: firstTask.queue_id,
|
|
803
|
+
tasks: batchTaskContexts,
|
|
804
|
+
worker_id: this.workerId,
|
|
805
|
+
consumer_id: taskRunnerId,
|
|
806
|
+
started_at: new Date(batchStartedAt)
|
|
807
|
+
}
|
|
808
|
+
);
|
|
780
809
|
await runWithLogContext(
|
|
781
810
|
batchStore,
|
|
782
811
|
() => executor.onTasks(taskGroup.tasks, actions).catch((err) => {
|
|
@@ -788,19 +817,40 @@ class TaskRunner {
|
|
|
788
817
|
}
|
|
789
818
|
})
|
|
790
819
|
);
|
|
820
|
+
const succeeded = [];
|
|
821
|
+
const failed = [];
|
|
822
|
+
for (const task of taskGroup.tasks) {
|
|
823
|
+
const status = actions.getTaskResultStatus(tId(task));
|
|
824
|
+
if (status === "success") succeeded.push(tId(task));
|
|
825
|
+
else if (status === "fail") failed.push(tId(task));
|
|
826
|
+
else failed.push(tId(task));
|
|
827
|
+
}
|
|
828
|
+
this.emitLifecycleEvent(
|
|
829
|
+
this.lifecycleProvider?.onTaskBatchCompleted,
|
|
830
|
+
{
|
|
831
|
+
task_type: taskGroup.type,
|
|
832
|
+
queue_id: firstTask.queue_id,
|
|
833
|
+
tasks: batchTaskContexts,
|
|
834
|
+
worker_id: this.workerId,
|
|
835
|
+
consumer_id: taskRunnerId,
|
|
836
|
+
succeeded,
|
|
837
|
+
failed,
|
|
838
|
+
duration_ms: Date.now() - batchStartedAt
|
|
839
|
+
}
|
|
840
|
+
);
|
|
791
841
|
} else {
|
|
792
842
|
if (executor.parallel) {
|
|
793
843
|
const chunks = chunk(taskGroup.tasks, executor.chunkSize);
|
|
794
844
|
this.logger.info(`[${taskRunnerId}] Processing in parallel chunks of ${executor.chunkSize}`);
|
|
795
845
|
for (const taskChunk of chunks) {
|
|
796
846
|
for (const task of taskChunk) {
|
|
797
|
-
this.emitTaskStarted(task, taskRunnerId);
|
|
847
|
+
this.emitTaskStarted(task, this.workerId, taskRunnerId);
|
|
798
848
|
}
|
|
799
849
|
const chunkPromises = [];
|
|
800
850
|
for (let j = 0; j < taskChunk.length; j++) {
|
|
801
851
|
const task = taskChunk[j];
|
|
802
852
|
const taskActions = actions.forkForTask(task);
|
|
803
|
-
const logStore = this.buildLogStore(task,
|
|
853
|
+
const logStore = this.buildLogStore(task, this.workerId);
|
|
804
854
|
chunkPromises.push(runWithLogContext(
|
|
805
855
|
logStore,
|
|
806
856
|
() => executor.onTask(task, taskActions).catch((err) => {
|
|
@@ -815,12 +865,12 @@ class TaskRunner {
|
|
|
815
865
|
for (const task of taskChunk) {
|
|
816
866
|
const resultStatus = actions.getTaskResultStatus(tId(task));
|
|
817
867
|
if (resultStatus === "success") {
|
|
818
|
-
this.emitTaskCompleted(task,
|
|
868
|
+
this.emitTaskCompleted(task, this.workerId, actions.getTaskResult(tId(task)), taskRunnerId);
|
|
819
869
|
} else if (resultStatus === "fail") {
|
|
820
870
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
821
871
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
822
872
|
const willRetry = retryCount < maxRetries;
|
|
823
|
-
this.emitTaskFailed(task,
|
|
873
|
+
this.emitTaskFailed(task, this.workerId, new Error("Task failed"), willRetry, void 0, taskRunnerId);
|
|
824
874
|
}
|
|
825
875
|
}
|
|
826
876
|
}
|
|
@@ -829,9 +879,9 @@ class TaskRunner {
|
|
|
829
879
|
for (let j = 0; j < taskGroup.tasks.length; j++) {
|
|
830
880
|
const task = taskGroup.tasks[j];
|
|
831
881
|
if (!timeoutMs) {
|
|
832
|
-
this.emitTaskStarted(task, taskRunnerId);
|
|
882
|
+
this.emitTaskStarted(task, this.workerId, taskRunnerId);
|
|
833
883
|
const taskActions = actions.forkForTask(task);
|
|
834
|
-
const logStore = this.buildLogStore(task,
|
|
884
|
+
const logStore = this.buildLogStore(task, this.workerId);
|
|
835
885
|
await runWithLogContext(
|
|
836
886
|
logStore,
|
|
837
887
|
() => executor.onTask(task, taskActions).catch((err) => {
|
|
@@ -843,18 +893,18 @@ class TaskRunner {
|
|
|
843
893
|
);
|
|
844
894
|
const resultStatus = actions.getTaskResultStatus(tId(task));
|
|
845
895
|
if (resultStatus === "success") {
|
|
846
|
-
this.emitTaskCompleted(task,
|
|
896
|
+
this.emitTaskCompleted(task, this.workerId, actions.getTaskResult(tId(task)), taskRunnerId);
|
|
847
897
|
} else if (resultStatus === "fail") {
|
|
848
898
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
849
899
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
850
900
|
const willRetry = retryCount < maxRetries;
|
|
851
|
-
this.emitTaskFailed(task,
|
|
901
|
+
this.emitTaskFailed(task, this.workerId, new Error("Task failed"), willRetry, void 0, taskRunnerId);
|
|
852
902
|
}
|
|
853
903
|
} else {
|
|
854
|
-
this.emitTaskStarted(task, taskRunnerId);
|
|
904
|
+
this.emitTaskStarted(task, this.workerId, taskRunnerId);
|
|
855
905
|
const startTime = Date.now();
|
|
856
906
|
const taskActions = actions.forkForTask(task);
|
|
857
|
-
const logStore = this.buildLogStore(task,
|
|
907
|
+
const logStore = this.buildLogStore(task, this.workerId);
|
|
858
908
|
const taskPromise = runWithLogContext(
|
|
859
909
|
logStore,
|
|
860
910
|
() => executor.onTask(task, taskActions).catch((err) => {
|
|
@@ -877,12 +927,12 @@ class TaskRunner {
|
|
|
877
927
|
if (result !== "~~~timeout") {
|
|
878
928
|
const resultStatus = actions.getTaskResultStatus(tId(task));
|
|
879
929
|
if (resultStatus === "success") {
|
|
880
|
-
this.emitTaskCompleted(task,
|
|
930
|
+
this.emitTaskCompleted(task, this.workerId, actions.getTaskResult(tId(task)), taskRunnerId);
|
|
881
931
|
} else if (resultStatus === "fail") {
|
|
882
932
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
883
933
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
884
934
|
const willRetry = retryCount < maxRetries;
|
|
885
|
-
this.emitTaskFailed(task,
|
|
935
|
+
this.emitTaskFailed(task, this.workerId, new Error("Task failed"), willRetry, void 0, taskRunnerId);
|
|
886
936
|
}
|
|
887
937
|
}
|
|
888
938
|
if (result === "~~~timeout") {
|
|
@@ -895,10 +945,18 @@ class TaskRunner {
|
|
|
895
945
|
} else {
|
|
896
946
|
const asyncLifecycleEmitter = this.lifecycleProvider ? {
|
|
897
947
|
onCompleted: (t, result2) => {
|
|
898
|
-
this.emitTaskCompleted(t,
|
|
948
|
+
this.emitTaskCompleted(t, this.workerId, result2, taskRunnerId);
|
|
899
949
|
},
|
|
900
950
|
onFailed: (t, error, willRetry) => {
|
|
901
|
-
this.emitTaskFailed(t,
|
|
951
|
+
this.emitTaskFailed(t, this.workerId, error, willRetry, void 0, taskRunnerId);
|
|
952
|
+
},
|
|
953
|
+
onScheduled: (t) => {
|
|
954
|
+
if (this.lifecycleProvider?.onTaskScheduled) {
|
|
955
|
+
this.emitLifecycleEvent(
|
|
956
|
+
this.lifecycleProvider.onTaskScheduled,
|
|
957
|
+
this.buildTaskContext(t, this.workerId, taskRunnerId)
|
|
958
|
+
);
|
|
959
|
+
}
|
|
902
960
|
}
|
|
903
961
|
} : void 0;
|
|
904
962
|
const asyncActions = new AsyncActions(this.messageQueue, this.taskStore, this.taskQueue, actions, task, this.generateId, asyncLifecycleEmitter, this.entityProjection, this.entityProjectionConfig, this.flowMiddleware);
|
|
@@ -946,7 +1004,7 @@ class TaskRunner {
|
|
|
946
1004
|
this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
|
|
947
1005
|
}
|
|
948
1006
|
}
|
|
949
|
-
buildTaskContext(task, workerId) {
|
|
1007
|
+
buildTaskContext(task, workerId, consumerId) {
|
|
950
1008
|
const retryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
|
|
951
1009
|
const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
|
|
952
1010
|
const maxRetries = task.retries ?? executor?.default_retries ?? 0;
|
|
@@ -961,14 +1019,15 @@ class TaskRunner {
|
|
|
961
1019
|
max_retries: maxRetries,
|
|
962
1020
|
scheduled_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
963
1021
|
worker_id: workerId,
|
|
1022
|
+
consumer_id: consumerId,
|
|
964
1023
|
log_context: task.metadata?.log_context
|
|
965
1024
|
};
|
|
966
1025
|
}
|
|
967
|
-
emitTaskStarted(task, workerId) {
|
|
1026
|
+
emitTaskStarted(task, workerId, consumerId) {
|
|
968
1027
|
const startedAt = Date.now();
|
|
969
1028
|
this.taskStartTimes.set(tId(task), startedAt);
|
|
970
1029
|
if (this.lifecycleProvider?.onTaskStarted) {
|
|
971
|
-
const ctx = this.buildTaskContext(task, workerId);
|
|
1030
|
+
const ctx = this.buildTaskContext(task, workerId, consumerId);
|
|
972
1031
|
const queuedDuration = startedAt - (task.created_at?.getTime() || startedAt);
|
|
973
1032
|
this.emitLifecycleEvent(
|
|
974
1033
|
this.lifecycleProvider.onTaskStarted,
|
|
@@ -980,12 +1039,12 @@ class TaskRunner {
|
|
|
980
1039
|
);
|
|
981
1040
|
}
|
|
982
1041
|
}
|
|
983
|
-
emitTaskCompleted(task, workerId, result) {
|
|
1042
|
+
emitTaskCompleted(task, workerId, result, consumerId) {
|
|
984
1043
|
const completedAt = Date.now();
|
|
985
1044
|
const startedAt = this.taskStartTimes.get(tId(task)) || completedAt;
|
|
986
1045
|
this.taskStartTimes.delete(tId(task));
|
|
987
1046
|
if (this.lifecycleProvider?.onTaskCompleted) {
|
|
988
|
-
const ctx = this.buildTaskContext(task, workerId);
|
|
1047
|
+
const ctx = this.buildTaskContext(task, workerId, consumerId);
|
|
989
1048
|
const timing = {
|
|
990
1049
|
queued_duration_ms: startedAt - (task.created_at?.getTime() || startedAt),
|
|
991
1050
|
processing_duration_ms: completedAt - startedAt,
|
|
@@ -997,12 +1056,12 @@ class TaskRunner {
|
|
|
997
1056
|
);
|
|
998
1057
|
}
|
|
999
1058
|
}
|
|
1000
|
-
emitTaskFailed(task, workerId, error, willRetry, nextAttemptAt) {
|
|
1059
|
+
emitTaskFailed(task, workerId, error, willRetry, nextAttemptAt, consumerId) {
|
|
1001
1060
|
const completedAt = Date.now();
|
|
1002
1061
|
const startedAt = this.taskStartTimes.get(tId(task)) || completedAt;
|
|
1003
1062
|
this.taskStartTimes.delete(tId(task));
|
|
1004
1063
|
if (this.lifecycleProvider?.onTaskFailed) {
|
|
1005
|
-
const ctx = this.buildTaskContext(task, workerId);
|
|
1064
|
+
const ctx = this.buildTaskContext(task, workerId, consumerId);
|
|
1006
1065
|
const timing = {
|
|
1007
1066
|
queued_duration_ms: startedAt - (task.created_at?.getTime() || startedAt),
|
|
1008
1067
|
processing_duration_ms: completedAt - startedAt,
|
|
@@ -1020,6 +1079,247 @@ function getEnabledQueues() {
|
|
|
1020
1079
|
if (enabledQueues.length === 0) throw new Error("No queues enabled");
|
|
1021
1080
|
return enabledQueues.map(getEnvironmentQueueName);
|
|
1022
1081
|
}
|
|
1082
|
+
function getFlowMeta(task) {
|
|
1083
|
+
return task.metadata?.flow_meta;
|
|
1084
|
+
}
|
|
1085
|
+
function getFlowMetaRequired(task) {
|
|
1086
|
+
return task.metadata.flow_meta;
|
|
1087
|
+
}
|
|
1088
|
+
class FlowMiddleware {
|
|
1089
|
+
constructor(barrierProvider, generateId, flowLifecycleProvider, workerId = "") {
|
|
1090
|
+
this.barrierProvider = barrierProvider;
|
|
1091
|
+
this.generateId = generateId;
|
|
1092
|
+
this.flowLifecycleProvider = flowLifecycleProvider;
|
|
1093
|
+
this.workerId = workerId;
|
|
1094
|
+
}
|
|
1095
|
+
emitFlowEvent(callback, ctx) {
|
|
1096
|
+
if (!callback) return;
|
|
1097
|
+
try {
|
|
1098
|
+
const result = callback(ctx);
|
|
1099
|
+
if (result instanceof Promise) {
|
|
1100
|
+
result.catch(() => {
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
} catch {
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
buildFlowContext(flowMeta) {
|
|
1107
|
+
return {
|
|
1108
|
+
flow_id: flowMeta.flow_id,
|
|
1109
|
+
total_steps: flowMeta.total_steps,
|
|
1110
|
+
join: flowMeta.join,
|
|
1111
|
+
failure_policy: flowMeta.failure_policy,
|
|
1112
|
+
entity: flowMeta.entity,
|
|
1113
|
+
worker_id: this.workerId
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Process completed tasks for flow orchestration.
|
|
1118
|
+
* Called from TaskHandler.postProcessTasks after markFailed/markSuccess.
|
|
1119
|
+
*
|
|
1120
|
+
* @param input Categorized terminal tasks — success and final-failed (no retries left)
|
|
1121
|
+
* @returns Join tasks to dispatch and entity projections to sync
|
|
1122
|
+
*/
|
|
1123
|
+
async onPostProcess(input) {
|
|
1124
|
+
const joinTasks = [];
|
|
1125
|
+
const projections = [];
|
|
1126
|
+
const successTaskSet = new Set(input.successTasks);
|
|
1127
|
+
const allTasks = [...input.successTasks, ...input.failedTasks];
|
|
1128
|
+
const joinCompletions = [];
|
|
1129
|
+
const timeoutTasks = [];
|
|
1130
|
+
const stepTasks = [];
|
|
1131
|
+
for (const task of allTasks) {
|
|
1132
|
+
const flowMeta = getFlowMeta(task);
|
|
1133
|
+
if (!flowMeta) continue;
|
|
1134
|
+
const isSuccess = successTaskSet.has(task);
|
|
1135
|
+
if (flowMeta.is_join) {
|
|
1136
|
+
joinCompletions.push(task);
|
|
1137
|
+
} else if (flowMeta.is_timeout) {
|
|
1138
|
+
timeoutTasks.push(task);
|
|
1139
|
+
} else {
|
|
1140
|
+
stepTasks.push({ task, isSuccess });
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
for (const task of joinCompletions) {
|
|
1144
|
+
const flowMeta = getFlowMetaRequired(task);
|
|
1145
|
+
const isSuccess = successTaskSet.has(task);
|
|
1146
|
+
if (flowMeta.entity) {
|
|
1147
|
+
try {
|
|
1148
|
+
const status = isSuccess ? "executed" : "failed";
|
|
1149
|
+
const error = !isSuccess ? task.execution_stats?.last_error || "Join task failed" : void 0;
|
|
1150
|
+
const p = buildProjection(
|
|
1151
|
+
{
|
|
1152
|
+
...task,
|
|
1153
|
+
id: flowMeta.flow_id,
|
|
1154
|
+
entity: flowMeta.entity
|
|
1155
|
+
},
|
|
1156
|
+
status,
|
|
1157
|
+
{ result: task.execution_result, error }
|
|
1158
|
+
);
|
|
1159
|
+
if (p) projections.push(p);
|
|
1160
|
+
} catch {
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
for (const task of timeoutTasks) {
|
|
1165
|
+
const flowMeta = getFlowMetaRequired(task);
|
|
1166
|
+
const flowId = flowMeta.flow_id;
|
|
1167
|
+
await this.barrierProvider.initBarrier(flowId, flowMeta.total_steps);
|
|
1168
|
+
const isComplete = await this.barrierProvider.isComplete(flowId);
|
|
1169
|
+
if (isComplete) continue;
|
|
1170
|
+
const isFirstAbort = await this.barrierProvider.markAborted(flowId);
|
|
1171
|
+
if (!isFirstAbort) continue;
|
|
1172
|
+
const partialResults = await this.barrierProvider.getStepResults(flowId);
|
|
1173
|
+
const flowResults = {
|
|
1174
|
+
flow_id: flowId,
|
|
1175
|
+
steps: partialResults,
|
|
1176
|
+
timed_out: true
|
|
1177
|
+
};
|
|
1178
|
+
const joinTask = this.buildJoinTask(flowMeta, flowResults);
|
|
1179
|
+
joinTasks.push(joinTask);
|
|
1180
|
+
if (this.flowLifecycleProvider?.onFlowTimedOut) {
|
|
1181
|
+
const startedAt = await this.barrierProvider.getStartedAt(flowId);
|
|
1182
|
+
const durationMs = startedAt ? Date.now() - startedAt.getTime() : 0;
|
|
1183
|
+
this.emitFlowEvent(this.flowLifecycleProvider.onFlowTimedOut, {
|
|
1184
|
+
...this.buildFlowContext(flowMeta),
|
|
1185
|
+
duration_ms: durationMs,
|
|
1186
|
+
steps_completed: partialResults.length
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
if (flowMeta.entity) {
|
|
1190
|
+
try {
|
|
1191
|
+
const p = buildProjection(
|
|
1192
|
+
{
|
|
1193
|
+
...joinTask,
|
|
1194
|
+
id: flowId,
|
|
1195
|
+
entity: flowMeta.entity
|
|
1196
|
+
},
|
|
1197
|
+
"failed",
|
|
1198
|
+
{ error: "flow_timeout" }
|
|
1199
|
+
);
|
|
1200
|
+
if (p) projections.push(p);
|
|
1201
|
+
} catch {
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const stepsByFlow = /* @__PURE__ */ new Map();
|
|
1206
|
+
for (const entry of stepTasks) {
|
|
1207
|
+
const flowMeta = getFlowMetaRequired(entry.task);
|
|
1208
|
+
const group = stepsByFlow.get(flowMeta.flow_id) || [];
|
|
1209
|
+
group.push(entry);
|
|
1210
|
+
stepsByFlow.set(flowMeta.flow_id, group);
|
|
1211
|
+
}
|
|
1212
|
+
for (const [flowId, entries] of stepsByFlow) {
|
|
1213
|
+
const firstFlowMeta = getFlowMetaRequired(entries[0].task);
|
|
1214
|
+
await this.barrierProvider.initBarrier(flowId, firstFlowMeta.total_steps);
|
|
1215
|
+
if (firstFlowMeta.failure_policy === "abort") {
|
|
1216
|
+
const hasFailure = entries.some((e) => !e.isSuccess);
|
|
1217
|
+
if (hasFailure) {
|
|
1218
|
+
const isFirstAbort = await this.barrierProvider.markAborted(flowId);
|
|
1219
|
+
if (isFirstAbort) {
|
|
1220
|
+
const stepResults2 = this.buildStepResults(entries);
|
|
1221
|
+
await this.barrierProvider.batchDecrementAndCheck(flowId, stepResults2);
|
|
1222
|
+
const allResults = await this.barrierProvider.getStepResults(flowId);
|
|
1223
|
+
const flowResults = {
|
|
1224
|
+
flow_id: flowId,
|
|
1225
|
+
steps: allResults,
|
|
1226
|
+
aborted: true
|
|
1227
|
+
};
|
|
1228
|
+
const joinTask = this.buildJoinTask(firstFlowMeta, flowResults);
|
|
1229
|
+
joinTasks.push(joinTask);
|
|
1230
|
+
if (this.flowLifecycleProvider?.onFlowAborted) {
|
|
1231
|
+
const startedAt = await this.barrierProvider.getStartedAt(flowId);
|
|
1232
|
+
const durationMs = startedAt ? Date.now() - startedAt.getTime() : 0;
|
|
1233
|
+
const failedEntry = entries.find((e) => !e.isSuccess);
|
|
1234
|
+
const triggerIndex = failedEntry ? failedEntry.task.metadata?.flow_meta?.step_index ?? -1 : -1;
|
|
1235
|
+
this.emitFlowEvent(this.flowLifecycleProvider.onFlowAborted, {
|
|
1236
|
+
...this.buildFlowContext(firstFlowMeta),
|
|
1237
|
+
duration_ms: durationMs,
|
|
1238
|
+
steps_completed: allResults.length,
|
|
1239
|
+
trigger_step_index: triggerIndex
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
if (firstFlowMeta.entity) {
|
|
1243
|
+
try {
|
|
1244
|
+
const p = buildProjection(
|
|
1245
|
+
{
|
|
1246
|
+
...joinTask,
|
|
1247
|
+
id: flowId,
|
|
1248
|
+
entity: firstFlowMeta.entity
|
|
1249
|
+
},
|
|
1250
|
+
"failed",
|
|
1251
|
+
{ error: "flow_aborted" }
|
|
1252
|
+
);
|
|
1253
|
+
if (p) projections.push(p);
|
|
1254
|
+
} catch {
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
continue;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
const stepResults = this.buildStepResults(entries);
|
|
1262
|
+
const { remaining } = await this.barrierProvider.batchDecrementAndCheck(flowId, stepResults);
|
|
1263
|
+
if (remaining === 0) {
|
|
1264
|
+
const allResults = await this.barrierProvider.getStepResults(flowId);
|
|
1265
|
+
const flowResults = {
|
|
1266
|
+
flow_id: flowId,
|
|
1267
|
+
steps: allResults
|
|
1268
|
+
};
|
|
1269
|
+
const joinTask = this.buildJoinTask(firstFlowMeta, flowResults);
|
|
1270
|
+
joinTasks.push(joinTask);
|
|
1271
|
+
if (this.flowLifecycleProvider?.onFlowCompleted) {
|
|
1272
|
+
const startedAt = await this.barrierProvider.getStartedAt(flowId);
|
|
1273
|
+
const durationMs = startedAt ? Date.now() - startedAt.getTime() : 0;
|
|
1274
|
+
const stepsSucceeded = allResults.filter((r) => r.status === "success").length;
|
|
1275
|
+
const stepsFailed = allResults.filter((r) => r.status === "fail").length;
|
|
1276
|
+
this.emitFlowEvent(this.flowLifecycleProvider.onFlowCompleted, {
|
|
1277
|
+
...this.buildFlowContext(firstFlowMeta),
|
|
1278
|
+
duration_ms: durationMs,
|
|
1279
|
+
steps_succeeded: stepsSucceeded,
|
|
1280
|
+
steps_failed: stepsFailed
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return { joinTasks, projections };
|
|
1286
|
+
}
|
|
1287
|
+
buildStepResults(entries) {
|
|
1288
|
+
return entries.map(({ task, isSuccess }) => {
|
|
1289
|
+
const flowMeta = getFlowMetaRequired(task);
|
|
1290
|
+
return {
|
|
1291
|
+
step_index: flowMeta.step_index,
|
|
1292
|
+
status: isSuccess ? "success" : "fail",
|
|
1293
|
+
result: task.execution_result,
|
|
1294
|
+
error: !isSuccess ? task.execution_stats?.last_error || "Step failed" : void 0
|
|
1295
|
+
};
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
buildJoinTask(flowMeta, flowResults) {
|
|
1299
|
+
const now = /* @__PURE__ */ new Date();
|
|
1300
|
+
const joinFlowMeta = {
|
|
1301
|
+
...flowMeta,
|
|
1302
|
+
is_join: true,
|
|
1303
|
+
is_timeout: void 0,
|
|
1304
|
+
step_index: -1
|
|
1305
|
+
};
|
|
1306
|
+
return {
|
|
1307
|
+
id: this.generateId(),
|
|
1308
|
+
type: flowMeta.join.type,
|
|
1309
|
+
queue_id: flowMeta.join.queue_id,
|
|
1310
|
+
payload: { flow_results: flowResults },
|
|
1311
|
+
execute_at: now,
|
|
1312
|
+
status: "scheduled",
|
|
1313
|
+
created_at: now,
|
|
1314
|
+
updated_at: now,
|
|
1315
|
+
force_store: true,
|
|
1316
|
+
metadata: {
|
|
1317
|
+
flow_meta: joinFlowMeta
|
|
1318
|
+
},
|
|
1319
|
+
...flowMeta.entity ? { entity: flowMeta.entity } : {}
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1023
1323
|
const METRICS_KEY_PREFIX = "task_metrics:";
|
|
1024
1324
|
const DISCARDED_TASKS_KEY = `${METRICS_KEY_PREFIX}discarded_tasks`;
|
|
1025
1325
|
const STATS_THRESHOLD = parseInt(process.env.TQ_STATS_THRESHOLD || "1000");
|
|
@@ -1046,23 +1346,35 @@ class TaskHandler {
|
|
|
1046
1346
|
};
|
|
1047
1347
|
this.totalProcessingMs = 0;
|
|
1048
1348
|
this.queueStats = /* @__PURE__ */ new Map();
|
|
1349
|
+
this.consumerStatsMap = /* @__PURE__ */ new Map();
|
|
1049
1350
|
this.logger = new Logger("TaskHandler", LogLevel.INFO);
|
|
1050
1351
|
this.config = config || {};
|
|
1051
1352
|
this.workerId = `${os.hostname()}-${process.pid}-${Date.now()}`;
|
|
1052
1353
|
this.workerStartedAt = /* @__PURE__ */ new Date();
|
|
1053
1354
|
this.taskStore = new TaskStore(databaseAdapter);
|
|
1054
|
-
this.
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
this.
|
|
1355
|
+
if (this.config.flowLifecycleProvider && !this.config.flowBarrierProvider) {
|
|
1356
|
+
throw new Error("[TQ] flowLifecycleProvider requires flowBarrierProvider — flow lifecycle events need flow orchestration enabled");
|
|
1357
|
+
}
|
|
1358
|
+
this.flowMiddleware = this.config.flowBarrierProvider ? new FlowMiddleware(
|
|
1359
|
+
this.config.flowBarrierProvider,
|
|
1059
1360
|
databaseAdapter.generateId.bind(databaseAdapter),
|
|
1060
|
-
this.config.
|
|
1061
|
-
this.
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1361
|
+
this.config.flowLifecycleProvider,
|
|
1362
|
+
this.workerId
|
|
1363
|
+
) : void 0;
|
|
1364
|
+
this.taskRunner = new TaskRunner({
|
|
1365
|
+
messageQueue,
|
|
1366
|
+
taskQueue: taskQueuesManager,
|
|
1367
|
+
taskStore: this.taskStore,
|
|
1368
|
+
cacheProvider: this.cacheAdapter,
|
|
1369
|
+
generateId: databaseAdapter.generateId.bind(databaseAdapter),
|
|
1370
|
+
lifecycleProvider: this.config.lifecycleProvider,
|
|
1371
|
+
lifecycleConfig: this.config.lifecycle,
|
|
1372
|
+
entityProjection: this.config.entityProjection,
|
|
1373
|
+
entityProjectionConfig: this.config.entityProjectionConfig,
|
|
1374
|
+
flowMiddleware: this.flowMiddleware,
|
|
1375
|
+
flowLifecycleProvider: this.config.flowLifecycleProvider,
|
|
1376
|
+
workerId: this.workerId
|
|
1377
|
+
});
|
|
1066
1378
|
}
|
|
1067
1379
|
// ============ Lifecycle Event Helpers ============
|
|
1068
1380
|
get lifecycleProvider() {
|
|
@@ -1367,9 +1679,9 @@ class TaskHandler {
|
|
|
1367
1679
|
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
1368
1680
|
}
|
|
1369
1681
|
}
|
|
1370
|
-
if (this.
|
|
1682
|
+
if (this.flowMiddleware) {
|
|
1371
1683
|
try {
|
|
1372
|
-
const flowResult = await this.
|
|
1684
|
+
const flowResult = await this.flowMiddleware.onPostProcess({ successTasks, failedTasks: finalFailedTasks });
|
|
1373
1685
|
if (flowResult.projections.length > 0 && this.entityProjectionProvider) {
|
|
1374
1686
|
await syncProjections(flowResult.projections, this.entityProjectionProvider, this.logger);
|
|
1375
1687
|
}
|
|
@@ -1392,6 +1704,7 @@ class TaskHandler {
|
|
|
1392
1704
|
}
|
|
1393
1705
|
const batchStartTime = Date.now();
|
|
1394
1706
|
const taskTypes = [...new Set(tasks.map((t) => t.type))];
|
|
1707
|
+
this.registerConsumerIfNew(id, streamName);
|
|
1395
1708
|
if (this.workerProvider?.onBatchStarted) {
|
|
1396
1709
|
this.emitLifecycleEvent(
|
|
1397
1710
|
this.workerProvider.onBatchStarted,
|
|
@@ -1468,6 +1781,7 @@ class TaskHandler {
|
|
|
1468
1781
|
await this.reportQueueStats(streamName);
|
|
1469
1782
|
const batchDuration = Date.now() - batchStartTime;
|
|
1470
1783
|
this.updateWorkerStats(successTasks.length, failedTasks.length, batchDuration);
|
|
1784
|
+
this.updateConsumerStats(id, successTasks.length, failedTasks.length);
|
|
1471
1785
|
if (this.workerProvider?.onBatchCompleted) {
|
|
1472
1786
|
this.emitLifecycleEvent(
|
|
1473
1787
|
this.workerProvider.onBatchCompleted,
|
|
@@ -1500,6 +1814,7 @@ class TaskHandler {
|
|
|
1500
1814
|
this.processMatureTasks(abortSignal);
|
|
1501
1815
|
abortSignal?.addEventListener("abort", () => {
|
|
1502
1816
|
this.stopHeartbeat();
|
|
1817
|
+
this.emitAllConsumersStopped("shutdown");
|
|
1503
1818
|
this.emitWorkerStopped("shutdown");
|
|
1504
1819
|
});
|
|
1505
1820
|
}
|
|
@@ -1535,7 +1850,8 @@ class TaskHandler {
|
|
|
1535
1850
|
{
|
|
1536
1851
|
...this.buildWorkerInfo(),
|
|
1537
1852
|
stats: { ...this.workerStats },
|
|
1538
|
-
memory_usage_mb: memUsage.heapUsed / 1024 / 1024
|
|
1853
|
+
memory_usage_mb: memUsage.heapUsed / 1024 / 1024,
|
|
1854
|
+
active_consumers: this.getActiveConsumerStats()
|
|
1539
1855
|
}
|
|
1540
1856
|
);
|
|
1541
1857
|
}
|
|
@@ -1561,6 +1877,58 @@ class TaskHandler {
|
|
|
1561
1877
|
this.workerStats.avg_processing_ms = this.totalProcessingMs / this.workerStats.tasks_processed;
|
|
1562
1878
|
}
|
|
1563
1879
|
}
|
|
1880
|
+
// ============ Consumer Tracking ============
|
|
1881
|
+
registerConsumerIfNew(consumerId, queueId) {
|
|
1882
|
+
if (this.consumerStatsMap.has(consumerId)) return;
|
|
1883
|
+
const now = /* @__PURE__ */ new Date();
|
|
1884
|
+
this.consumerStatsMap.set(consumerId, {
|
|
1885
|
+
consumer_id: consumerId,
|
|
1886
|
+
queue_id: queueId,
|
|
1887
|
+
tasks_processed: 0,
|
|
1888
|
+
tasks_succeeded: 0,
|
|
1889
|
+
tasks_failed: 0,
|
|
1890
|
+
started_at: now
|
|
1891
|
+
});
|
|
1892
|
+
if (this.workerProvider?.onConsumerStarted) {
|
|
1893
|
+
this.emitLifecycleEvent(
|
|
1894
|
+
this.workerProvider.onConsumerStarted,
|
|
1895
|
+
{
|
|
1896
|
+
consumer_id: consumerId,
|
|
1897
|
+
queue_id: queueId,
|
|
1898
|
+
worker_id: this.workerId,
|
|
1899
|
+
started_at: now
|
|
1900
|
+
}
|
|
1901
|
+
);
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
updateConsumerStats(consumerId, succeeded, failed) {
|
|
1905
|
+
const stats = this.consumerStatsMap.get(consumerId);
|
|
1906
|
+
if (!stats) return;
|
|
1907
|
+
stats.tasks_processed += succeeded + failed;
|
|
1908
|
+
stats.tasks_succeeded += succeeded;
|
|
1909
|
+
stats.tasks_failed += failed;
|
|
1910
|
+
stats.last_task_at = /* @__PURE__ */ new Date();
|
|
1911
|
+
}
|
|
1912
|
+
getActiveConsumerStats() {
|
|
1913
|
+
return Array.from(this.consumerStatsMap.values()).map(({ started_at, ...stats }) => stats);
|
|
1914
|
+
}
|
|
1915
|
+
emitAllConsumersStopped(reason) {
|
|
1916
|
+
if (!this.workerProvider?.onConsumerStopped) return;
|
|
1917
|
+
for (const entry of this.consumerStatsMap.values()) {
|
|
1918
|
+
const { started_at, ...stats } = entry;
|
|
1919
|
+
this.emitLifecycleEvent(
|
|
1920
|
+
this.workerProvider.onConsumerStopped,
|
|
1921
|
+
{
|
|
1922
|
+
consumer_id: entry.consumer_id,
|
|
1923
|
+
queue_id: entry.queue_id,
|
|
1924
|
+
worker_id: this.workerId,
|
|
1925
|
+
started_at,
|
|
1926
|
+
reason,
|
|
1927
|
+
stats
|
|
1928
|
+
}
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1564
1932
|
emitLifecycleEvent(callback, ctx) {
|
|
1565
1933
|
if (!callback) return;
|
|
1566
1934
|
try {
|
|
@@ -1656,6 +2024,7 @@ class TaskHandler {
|
|
|
1656
2024
|
attempt: retryCount + 1,
|
|
1657
2025
|
max_retries: maxRetries,
|
|
1658
2026
|
scheduled_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
2027
|
+
worker_id: this.workerId,
|
|
1659
2028
|
log_context: task.metadata?.log_context
|
|
1660
2029
|
};
|
|
1661
2030
|
}
|
|
@@ -1835,7 +2204,8 @@ class InMemoryFlowBarrierProvider {
|
|
|
1835
2204
|
this.barriers.set(flowId, {
|
|
1836
2205
|
remaining: totalSteps,
|
|
1837
2206
|
status: "active",
|
|
1838
|
-
results: /* @__PURE__ */ new Map()
|
|
2207
|
+
results: /* @__PURE__ */ new Map(),
|
|
2208
|
+
started_at: /* @__PURE__ */ new Date()
|
|
1839
2209
|
});
|
|
1840
2210
|
}
|
|
1841
2211
|
async batchDecrementAndCheck(flowId, results) {
|
|
@@ -1879,190 +2249,10 @@ class InMemoryFlowBarrierProvider {
|
|
|
1879
2249
|
if (!state) return false;
|
|
1880
2250
|
return state.status === "complete";
|
|
1881
2251
|
}
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
function getFlowMetaRequired(task) {
|
|
1887
|
-
return task.metadata.flow_meta;
|
|
1888
|
-
}
|
|
1889
|
-
class FlowMiddleware {
|
|
1890
|
-
constructor(barrierProvider, generateId) {
|
|
1891
|
-
this.barrierProvider = barrierProvider;
|
|
1892
|
-
this.generateId = generateId;
|
|
1893
|
-
}
|
|
1894
|
-
/**
|
|
1895
|
-
* Process completed tasks for flow orchestration.
|
|
1896
|
-
* Called from TaskHandler.postProcessTasks after markFailed/markSuccess.
|
|
1897
|
-
*
|
|
1898
|
-
* @param input Categorized terminal tasks — success and final-failed (no retries left)
|
|
1899
|
-
* @returns Join tasks to dispatch and entity projections to sync
|
|
1900
|
-
*/
|
|
1901
|
-
async onPostProcess(input) {
|
|
1902
|
-
const joinTasks = [];
|
|
1903
|
-
const projections = [];
|
|
1904
|
-
const successTaskSet = new Set(input.successTasks);
|
|
1905
|
-
const allTasks = [...input.successTasks, ...input.failedTasks];
|
|
1906
|
-
const joinCompletions = [];
|
|
1907
|
-
const timeoutTasks = [];
|
|
1908
|
-
const stepTasks = [];
|
|
1909
|
-
for (const task of allTasks) {
|
|
1910
|
-
const flowMeta = getFlowMeta(task);
|
|
1911
|
-
if (!flowMeta) continue;
|
|
1912
|
-
const isSuccess = successTaskSet.has(task);
|
|
1913
|
-
if (flowMeta.is_join) {
|
|
1914
|
-
joinCompletions.push(task);
|
|
1915
|
-
} else if (flowMeta.is_timeout) {
|
|
1916
|
-
timeoutTasks.push(task);
|
|
1917
|
-
} else {
|
|
1918
|
-
stepTasks.push({ task, isSuccess });
|
|
1919
|
-
}
|
|
1920
|
-
}
|
|
1921
|
-
for (const task of joinCompletions) {
|
|
1922
|
-
const flowMeta = getFlowMetaRequired(task);
|
|
1923
|
-
const isSuccess = successTaskSet.has(task);
|
|
1924
|
-
if (flowMeta.entity) {
|
|
1925
|
-
try {
|
|
1926
|
-
const status = isSuccess ? "executed" : "failed";
|
|
1927
|
-
const error = !isSuccess ? task.execution_stats?.last_error || "Join task failed" : void 0;
|
|
1928
|
-
const p = buildProjection(
|
|
1929
|
-
{
|
|
1930
|
-
...task,
|
|
1931
|
-
id: flowMeta.flow_id,
|
|
1932
|
-
entity: flowMeta.entity
|
|
1933
|
-
},
|
|
1934
|
-
status,
|
|
1935
|
-
{ result: task.execution_result, error }
|
|
1936
|
-
);
|
|
1937
|
-
if (p) projections.push(p);
|
|
1938
|
-
} catch {
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
for (const task of timeoutTasks) {
|
|
1943
|
-
const flowMeta = getFlowMetaRequired(task);
|
|
1944
|
-
const flowId = flowMeta.flow_id;
|
|
1945
|
-
await this.barrierProvider.initBarrier(flowId, flowMeta.total_steps);
|
|
1946
|
-
const isComplete = await this.barrierProvider.isComplete(flowId);
|
|
1947
|
-
if (isComplete) continue;
|
|
1948
|
-
const isFirstAbort = await this.barrierProvider.markAborted(flowId);
|
|
1949
|
-
if (!isFirstAbort) continue;
|
|
1950
|
-
const partialResults = await this.barrierProvider.getStepResults(flowId);
|
|
1951
|
-
const flowResults = {
|
|
1952
|
-
flow_id: flowId,
|
|
1953
|
-
steps: partialResults,
|
|
1954
|
-
timed_out: true
|
|
1955
|
-
};
|
|
1956
|
-
const joinTask = this.buildJoinTask(flowMeta, flowResults);
|
|
1957
|
-
joinTasks.push(joinTask);
|
|
1958
|
-
if (flowMeta.entity) {
|
|
1959
|
-
try {
|
|
1960
|
-
const p = buildProjection(
|
|
1961
|
-
{
|
|
1962
|
-
...joinTask,
|
|
1963
|
-
id: flowId,
|
|
1964
|
-
entity: flowMeta.entity
|
|
1965
|
-
},
|
|
1966
|
-
"failed",
|
|
1967
|
-
{ error: "flow_timeout" }
|
|
1968
|
-
);
|
|
1969
|
-
if (p) projections.push(p);
|
|
1970
|
-
} catch {
|
|
1971
|
-
}
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
const stepsByFlow = /* @__PURE__ */ new Map();
|
|
1975
|
-
for (const entry of stepTasks) {
|
|
1976
|
-
const flowMeta = getFlowMetaRequired(entry.task);
|
|
1977
|
-
const group = stepsByFlow.get(flowMeta.flow_id) || [];
|
|
1978
|
-
group.push(entry);
|
|
1979
|
-
stepsByFlow.set(flowMeta.flow_id, group);
|
|
1980
|
-
}
|
|
1981
|
-
for (const [flowId, entries] of stepsByFlow) {
|
|
1982
|
-
const firstFlowMeta = getFlowMetaRequired(entries[0].task);
|
|
1983
|
-
await this.barrierProvider.initBarrier(flowId, firstFlowMeta.total_steps);
|
|
1984
|
-
if (firstFlowMeta.failure_policy === "abort") {
|
|
1985
|
-
const hasFailure = entries.some((e) => !e.isSuccess);
|
|
1986
|
-
if (hasFailure) {
|
|
1987
|
-
const isFirstAbort = await this.barrierProvider.markAborted(flowId);
|
|
1988
|
-
if (isFirstAbort) {
|
|
1989
|
-
const stepResults2 = this.buildStepResults(entries);
|
|
1990
|
-
await this.barrierProvider.batchDecrementAndCheck(flowId, stepResults2);
|
|
1991
|
-
const allResults = await this.barrierProvider.getStepResults(flowId);
|
|
1992
|
-
const flowResults = {
|
|
1993
|
-
flow_id: flowId,
|
|
1994
|
-
steps: allResults,
|
|
1995
|
-
aborted: true
|
|
1996
|
-
};
|
|
1997
|
-
const joinTask = this.buildJoinTask(firstFlowMeta, flowResults);
|
|
1998
|
-
joinTasks.push(joinTask);
|
|
1999
|
-
if (firstFlowMeta.entity) {
|
|
2000
|
-
try {
|
|
2001
|
-
const p = buildProjection(
|
|
2002
|
-
{
|
|
2003
|
-
...joinTask,
|
|
2004
|
-
id: flowId,
|
|
2005
|
-
entity: firstFlowMeta.entity
|
|
2006
|
-
},
|
|
2007
|
-
"failed",
|
|
2008
|
-
{ error: "flow_aborted" }
|
|
2009
|
-
);
|
|
2010
|
-
if (p) projections.push(p);
|
|
2011
|
-
} catch {
|
|
2012
|
-
}
|
|
2013
|
-
}
|
|
2014
|
-
}
|
|
2015
|
-
continue;
|
|
2016
|
-
}
|
|
2017
|
-
}
|
|
2018
|
-
const stepResults = this.buildStepResults(entries);
|
|
2019
|
-
const { remaining } = await this.barrierProvider.batchDecrementAndCheck(flowId, stepResults);
|
|
2020
|
-
if (remaining === 0) {
|
|
2021
|
-
const allResults = await this.barrierProvider.getStepResults(flowId);
|
|
2022
|
-
const flowResults = {
|
|
2023
|
-
flow_id: flowId,
|
|
2024
|
-
steps: allResults
|
|
2025
|
-
};
|
|
2026
|
-
const joinTask = this.buildJoinTask(firstFlowMeta, flowResults);
|
|
2027
|
-
joinTasks.push(joinTask);
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
return { joinTasks, projections };
|
|
2031
|
-
}
|
|
2032
|
-
buildStepResults(entries) {
|
|
2033
|
-
return entries.map(({ task, isSuccess }) => {
|
|
2034
|
-
const flowMeta = getFlowMetaRequired(task);
|
|
2035
|
-
return {
|
|
2036
|
-
step_index: flowMeta.step_index,
|
|
2037
|
-
status: isSuccess ? "success" : "fail",
|
|
2038
|
-
result: task.execution_result,
|
|
2039
|
-
error: !isSuccess ? task.execution_stats?.last_error || "Step failed" : void 0
|
|
2040
|
-
};
|
|
2041
|
-
});
|
|
2042
|
-
}
|
|
2043
|
-
buildJoinTask(flowMeta, flowResults) {
|
|
2044
|
-
const now = /* @__PURE__ */ new Date();
|
|
2045
|
-
const joinFlowMeta = {
|
|
2046
|
-
...flowMeta,
|
|
2047
|
-
is_join: true,
|
|
2048
|
-
is_timeout: void 0,
|
|
2049
|
-
step_index: -1
|
|
2050
|
-
};
|
|
2051
|
-
return {
|
|
2052
|
-
id: this.generateId(),
|
|
2053
|
-
type: flowMeta.join.type,
|
|
2054
|
-
queue_id: flowMeta.join.queue_id,
|
|
2055
|
-
payload: { flow_results: flowResults },
|
|
2056
|
-
execute_at: now,
|
|
2057
|
-
status: "scheduled",
|
|
2058
|
-
created_at: now,
|
|
2059
|
-
updated_at: now,
|
|
2060
|
-
force_store: true,
|
|
2061
|
-
metadata: {
|
|
2062
|
-
flow_meta: joinFlowMeta
|
|
2063
|
-
},
|
|
2064
|
-
...flowMeta.entity ? { entity: flowMeta.entity } : {}
|
|
2065
|
-
};
|
|
2252
|
+
async getStartedAt(flowId) {
|
|
2253
|
+
const state = this.barriers.get(flowId);
|
|
2254
|
+
if (!state) return null;
|
|
2255
|
+
return state.started_at;
|
|
2066
2256
|
}
|
|
2067
2257
|
}
|
|
2068
2258
|
class ConsoleHealthProvider {
|
|
@@ -2102,7 +2292,7 @@ class ConsoleHealthProvider {
|
|
|
2102
2292
|
}
|
|
2103
2293
|
onWorkerHeartbeat(info) {
|
|
2104
2294
|
console.log(
|
|
2105
|
-
`${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`
|
|
2295
|
+
`${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, consumers: ${info.active_consumers.length}`
|
|
2106
2296
|
);
|
|
2107
2297
|
}
|
|
2108
2298
|
onWorkerStopped(info) {
|
|
@@ -2120,6 +2310,49 @@ class ConsoleHealthProvider {
|
|
|
2120
2310
|
`${this.prefix} Batch completed: ${info.batch_size} tasks in ${info.duration_ms}ms - succeeded: ${info.succeeded}, failed: ${info.failed}`
|
|
2121
2311
|
);
|
|
2122
2312
|
}
|
|
2313
|
+
// ============ Consumer Lifecycle ============
|
|
2314
|
+
onConsumerStarted(info) {
|
|
2315
|
+
console.log(
|
|
2316
|
+
`${this.prefix} Consumer started: ${info.consumer_id} on worker ${info.worker_id} - queue: ${info.queue_id}`
|
|
2317
|
+
);
|
|
2318
|
+
}
|
|
2319
|
+
onConsumerStopped(info) {
|
|
2320
|
+
console.log(
|
|
2321
|
+
`${this.prefix} Consumer stopped: ${info.consumer_id} on worker ${info.worker_id} - reason: ${info.reason}, processed: ${info.stats.tasks_processed}, succeeded: ${info.stats.tasks_succeeded}, failed: ${info.stats.tasks_failed}`
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
// ============ Task Batch Lifecycle ============
|
|
2325
|
+
onTaskBatchStarted(ctx) {
|
|
2326
|
+
console.log(
|
|
2327
|
+
`${this.prefix} Task batch started: ${ctx.task_type} - ${ctx.tasks.length} tasks in queue ${ctx.queue_id}`
|
|
2328
|
+
);
|
|
2329
|
+
}
|
|
2330
|
+
onTaskBatchCompleted(ctx) {
|
|
2331
|
+
console.log(
|
|
2332
|
+
`${this.prefix} Task batch completed: ${ctx.task_type} in ${ctx.duration_ms}ms - succeeded: ${ctx.succeeded.length}, failed: ${ctx.failed.length}`
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
// ============ Flow Lifecycle ============
|
|
2336
|
+
onFlowStarted(ctx) {
|
|
2337
|
+
console.log(
|
|
2338
|
+
`${this.prefix} Flow started: ${ctx.flow_id} - ${ctx.total_steps} steps [${ctx.step_types.join(", ")}], join: ${ctx.join.type}, policy: ${ctx.failure_policy}`
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
onFlowCompleted(ctx) {
|
|
2342
|
+
console.log(
|
|
2343
|
+
`${this.prefix} Flow completed: ${ctx.flow_id} in ${ctx.duration_ms}ms - succeeded: ${ctx.steps_succeeded}, failed: ${ctx.steps_failed}`
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
onFlowAborted(ctx) {
|
|
2347
|
+
console.log(
|
|
2348
|
+
`${this.prefix} Flow aborted: ${ctx.flow_id} in ${ctx.duration_ms}ms - steps completed: ${ctx.steps_completed}, trigger: step ${ctx.trigger_step_index}`
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
onFlowTimedOut(ctx) {
|
|
2352
|
+
console.log(
|
|
2353
|
+
`${this.prefix} Flow timed out: ${ctx.flow_id} in ${ctx.duration_ms}ms - steps completed: ${ctx.steps_completed}`
|
|
2354
|
+
);
|
|
2355
|
+
}
|
|
2123
2356
|
}
|
|
2124
2357
|
export {
|
|
2125
2358
|
Actions,
|