@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.cjs
CHANGED
|
@@ -50,7 +50,7 @@ const mq = require("@supergrowthai/mq");
|
|
|
50
50
|
const client = require("./client-BxG7LzLv.cjs");
|
|
51
51
|
const utils_taskIdGen = require("./utils/task-id-gen.cjs");
|
|
52
52
|
const core_Actions = require("./core/Actions.cjs");
|
|
53
|
-
const core_async_AsyncActions = require("./AsyncActions-
|
|
53
|
+
const core_async_AsyncActions = require("./AsyncActions-BsxMX_Ib.cjs");
|
|
54
54
|
const node_async_hooks = require("node:async_hooks");
|
|
55
55
|
const moment = require("moment");
|
|
56
56
|
const os = require("os");
|
|
@@ -682,7 +682,22 @@ function getLogContext() {
|
|
|
682
682
|
return als.getStore();
|
|
683
683
|
}
|
|
684
684
|
class TaskRunner {
|
|
685
|
-
constructor(
|
|
685
|
+
constructor(opts) {
|
|
686
|
+
this.taskStartTimes = /* @__PURE__ */ new Map();
|
|
687
|
+
const {
|
|
688
|
+
messageQueue,
|
|
689
|
+
taskQueue,
|
|
690
|
+
taskStore,
|
|
691
|
+
cacheProvider,
|
|
692
|
+
generateId,
|
|
693
|
+
lifecycleProvider,
|
|
694
|
+
lifecycleConfig,
|
|
695
|
+
entityProjection,
|
|
696
|
+
entityProjectionConfig,
|
|
697
|
+
flowMiddleware,
|
|
698
|
+
flowLifecycleProvider,
|
|
699
|
+
workerId = ""
|
|
700
|
+
} = opts;
|
|
686
701
|
this.messageQueue = messageQueue;
|
|
687
702
|
this.taskQueue = taskQueue;
|
|
688
703
|
this.taskStore = taskStore;
|
|
@@ -692,7 +707,8 @@ class TaskRunner {
|
|
|
692
707
|
this.entityProjection = entityProjection;
|
|
693
708
|
this.entityProjectionConfig = entityProjectionConfig;
|
|
694
709
|
this.flowMiddleware = flowMiddleware;
|
|
695
|
-
this.
|
|
710
|
+
this.flowLifecycleProvider = flowLifecycleProvider;
|
|
711
|
+
this.workerId = workerId;
|
|
696
712
|
this.logger = new client.Logger("TaskRunner", client.LogLevel.INFO);
|
|
697
713
|
this.lockManager = new LockManager(cacheProvider, {
|
|
698
714
|
prefix: "task_lock_",
|
|
@@ -759,7 +775,7 @@ class TaskRunner {
|
|
|
759
775
|
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
760
776
|
}
|
|
761
777
|
}
|
|
762
|
-
const actions = new core_Actions.Actions(taskRunnerId);
|
|
778
|
+
const actions = new core_Actions.Actions(taskRunnerId, this.flowLifecycleProvider, this.workerId);
|
|
763
779
|
const asyncTasks = [];
|
|
764
780
|
const processedTaskIds = /* @__PURE__ */ new Set();
|
|
765
781
|
for (let i = 0; i < groupedTasksArray.length; i++) {
|
|
@@ -795,7 +811,20 @@ class TaskRunner {
|
|
|
795
811
|
taskGroup.tasks.forEach((task) => processedTaskIds.add(utils_taskIdGen.tId(task)));
|
|
796
812
|
this.logger.info(`[${taskRunnerId}] Processing ${taskGroup.tasks.length} tasks of type: ${taskGroup.type}`);
|
|
797
813
|
if (executor.multiple) {
|
|
798
|
-
const batchStore = this.buildBatchLogStore(taskGroup.tasks,
|
|
814
|
+
const batchStore = this.buildBatchLogStore(taskGroup.tasks, this.workerId);
|
|
815
|
+
const batchTaskContexts = taskGroup.tasks.map((t) => this.buildTaskContext(t, this.workerId, taskRunnerId));
|
|
816
|
+
const batchStartedAt = Date.now();
|
|
817
|
+
this.emitLifecycleEvent(
|
|
818
|
+
this.lifecycleProvider?.onTaskBatchStarted,
|
|
819
|
+
{
|
|
820
|
+
task_type: taskGroup.type,
|
|
821
|
+
queue_id: firstTask.queue_id,
|
|
822
|
+
tasks: batchTaskContexts,
|
|
823
|
+
worker_id: this.workerId,
|
|
824
|
+
consumer_id: taskRunnerId,
|
|
825
|
+
started_at: new Date(batchStartedAt)
|
|
826
|
+
}
|
|
827
|
+
);
|
|
799
828
|
await runWithLogContext(
|
|
800
829
|
batchStore,
|
|
801
830
|
() => executor.onTasks(taskGroup.tasks, actions).catch((err) => {
|
|
@@ -807,19 +836,40 @@ class TaskRunner {
|
|
|
807
836
|
}
|
|
808
837
|
})
|
|
809
838
|
);
|
|
839
|
+
const succeeded = [];
|
|
840
|
+
const failed = [];
|
|
841
|
+
for (const task of taskGroup.tasks) {
|
|
842
|
+
const status = actions.getTaskResultStatus(utils_taskIdGen.tId(task));
|
|
843
|
+
if (status === "success") succeeded.push(utils_taskIdGen.tId(task));
|
|
844
|
+
else if (status === "fail") failed.push(utils_taskIdGen.tId(task));
|
|
845
|
+
else failed.push(utils_taskIdGen.tId(task));
|
|
846
|
+
}
|
|
847
|
+
this.emitLifecycleEvent(
|
|
848
|
+
this.lifecycleProvider?.onTaskBatchCompleted,
|
|
849
|
+
{
|
|
850
|
+
task_type: taskGroup.type,
|
|
851
|
+
queue_id: firstTask.queue_id,
|
|
852
|
+
tasks: batchTaskContexts,
|
|
853
|
+
worker_id: this.workerId,
|
|
854
|
+
consumer_id: taskRunnerId,
|
|
855
|
+
succeeded,
|
|
856
|
+
failed,
|
|
857
|
+
duration_ms: Date.now() - batchStartedAt
|
|
858
|
+
}
|
|
859
|
+
);
|
|
810
860
|
} else {
|
|
811
861
|
if (executor.parallel) {
|
|
812
862
|
const chunks = chunk(taskGroup.tasks, executor.chunkSize);
|
|
813
863
|
this.logger.info(`[${taskRunnerId}] Processing in parallel chunks of ${executor.chunkSize}`);
|
|
814
864
|
for (const taskChunk of chunks) {
|
|
815
865
|
for (const task of taskChunk) {
|
|
816
|
-
this.emitTaskStarted(task, taskRunnerId);
|
|
866
|
+
this.emitTaskStarted(task, this.workerId, taskRunnerId);
|
|
817
867
|
}
|
|
818
868
|
const chunkPromises = [];
|
|
819
869
|
for (let j = 0; j < taskChunk.length; j++) {
|
|
820
870
|
const task = taskChunk[j];
|
|
821
871
|
const taskActions = actions.forkForTask(task);
|
|
822
|
-
const logStore = this.buildLogStore(task,
|
|
872
|
+
const logStore = this.buildLogStore(task, this.workerId);
|
|
823
873
|
chunkPromises.push(runWithLogContext(
|
|
824
874
|
logStore,
|
|
825
875
|
() => executor.onTask(task, taskActions).catch((err) => {
|
|
@@ -834,12 +884,12 @@ class TaskRunner {
|
|
|
834
884
|
for (const task of taskChunk) {
|
|
835
885
|
const resultStatus = actions.getTaskResultStatus(utils_taskIdGen.tId(task));
|
|
836
886
|
if (resultStatus === "success") {
|
|
837
|
-
this.emitTaskCompleted(task,
|
|
887
|
+
this.emitTaskCompleted(task, this.workerId, actions.getTaskResult(utils_taskIdGen.tId(task)), taskRunnerId);
|
|
838
888
|
} else if (resultStatus === "fail") {
|
|
839
889
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
840
890
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
841
891
|
const willRetry = retryCount < maxRetries;
|
|
842
|
-
this.emitTaskFailed(task,
|
|
892
|
+
this.emitTaskFailed(task, this.workerId, new Error("Task failed"), willRetry, void 0, taskRunnerId);
|
|
843
893
|
}
|
|
844
894
|
}
|
|
845
895
|
}
|
|
@@ -848,9 +898,9 @@ class TaskRunner {
|
|
|
848
898
|
for (let j = 0; j < taskGroup.tasks.length; j++) {
|
|
849
899
|
const task = taskGroup.tasks[j];
|
|
850
900
|
if (!timeoutMs) {
|
|
851
|
-
this.emitTaskStarted(task, taskRunnerId);
|
|
901
|
+
this.emitTaskStarted(task, this.workerId, taskRunnerId);
|
|
852
902
|
const taskActions = actions.forkForTask(task);
|
|
853
|
-
const logStore = this.buildLogStore(task,
|
|
903
|
+
const logStore = this.buildLogStore(task, this.workerId);
|
|
854
904
|
await runWithLogContext(
|
|
855
905
|
logStore,
|
|
856
906
|
() => executor.onTask(task, taskActions).catch((err) => {
|
|
@@ -862,18 +912,18 @@ class TaskRunner {
|
|
|
862
912
|
);
|
|
863
913
|
const resultStatus = actions.getTaskResultStatus(utils_taskIdGen.tId(task));
|
|
864
914
|
if (resultStatus === "success") {
|
|
865
|
-
this.emitTaskCompleted(task,
|
|
915
|
+
this.emitTaskCompleted(task, this.workerId, actions.getTaskResult(utils_taskIdGen.tId(task)), taskRunnerId);
|
|
866
916
|
} else if (resultStatus === "fail") {
|
|
867
917
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
868
918
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
869
919
|
const willRetry = retryCount < maxRetries;
|
|
870
|
-
this.emitTaskFailed(task,
|
|
920
|
+
this.emitTaskFailed(task, this.workerId, new Error("Task failed"), willRetry, void 0, taskRunnerId);
|
|
871
921
|
}
|
|
872
922
|
} else {
|
|
873
|
-
this.emitTaskStarted(task, taskRunnerId);
|
|
923
|
+
this.emitTaskStarted(task, this.workerId, taskRunnerId);
|
|
874
924
|
const startTime = Date.now();
|
|
875
925
|
const taskActions = actions.forkForTask(task);
|
|
876
|
-
const logStore = this.buildLogStore(task,
|
|
926
|
+
const logStore = this.buildLogStore(task, this.workerId);
|
|
877
927
|
const taskPromise = runWithLogContext(
|
|
878
928
|
logStore,
|
|
879
929
|
() => executor.onTask(task, taskActions).catch((err) => {
|
|
@@ -896,12 +946,12 @@ class TaskRunner {
|
|
|
896
946
|
if (result !== "~~~timeout") {
|
|
897
947
|
const resultStatus = actions.getTaskResultStatus(utils_taskIdGen.tId(task));
|
|
898
948
|
if (resultStatus === "success") {
|
|
899
|
-
this.emitTaskCompleted(task,
|
|
949
|
+
this.emitTaskCompleted(task, this.workerId, actions.getTaskResult(utils_taskIdGen.tId(task)), taskRunnerId);
|
|
900
950
|
} else if (resultStatus === "fail") {
|
|
901
951
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
902
952
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
903
953
|
const willRetry = retryCount < maxRetries;
|
|
904
|
-
this.emitTaskFailed(task,
|
|
954
|
+
this.emitTaskFailed(task, this.workerId, new Error("Task failed"), willRetry, void 0, taskRunnerId);
|
|
905
955
|
}
|
|
906
956
|
}
|
|
907
957
|
if (result === "~~~timeout") {
|
|
@@ -914,10 +964,18 @@ class TaskRunner {
|
|
|
914
964
|
} else {
|
|
915
965
|
const asyncLifecycleEmitter = this.lifecycleProvider ? {
|
|
916
966
|
onCompleted: (t, result2) => {
|
|
917
|
-
this.emitTaskCompleted(t,
|
|
967
|
+
this.emitTaskCompleted(t, this.workerId, result2, taskRunnerId);
|
|
918
968
|
},
|
|
919
969
|
onFailed: (t, error, willRetry) => {
|
|
920
|
-
this.emitTaskFailed(t,
|
|
970
|
+
this.emitTaskFailed(t, this.workerId, error, willRetry, void 0, taskRunnerId);
|
|
971
|
+
},
|
|
972
|
+
onScheduled: (t) => {
|
|
973
|
+
if (this.lifecycleProvider?.onTaskScheduled) {
|
|
974
|
+
this.emitLifecycleEvent(
|
|
975
|
+
this.lifecycleProvider.onTaskScheduled,
|
|
976
|
+
this.buildTaskContext(t, this.workerId, taskRunnerId)
|
|
977
|
+
);
|
|
978
|
+
}
|
|
921
979
|
}
|
|
922
980
|
} : void 0;
|
|
923
981
|
const asyncActions = new core_async_AsyncActions.AsyncActions(this.messageQueue, this.taskStore, this.taskQueue, actions, task, this.generateId, asyncLifecycleEmitter, this.entityProjection, this.entityProjectionConfig, this.flowMiddleware);
|
|
@@ -965,7 +1023,7 @@ class TaskRunner {
|
|
|
965
1023
|
this.logger.error(`[TQ] Lifecycle callback error: ${err}`);
|
|
966
1024
|
}
|
|
967
1025
|
}
|
|
968
|
-
buildTaskContext(task, workerId) {
|
|
1026
|
+
buildTaskContext(task, workerId, consumerId) {
|
|
969
1027
|
const retryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
|
|
970
1028
|
const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
|
|
971
1029
|
const maxRetries = task.retries ?? executor?.default_retries ?? 0;
|
|
@@ -980,14 +1038,15 @@ class TaskRunner {
|
|
|
980
1038
|
max_retries: maxRetries,
|
|
981
1039
|
scheduled_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
982
1040
|
worker_id: workerId,
|
|
1041
|
+
consumer_id: consumerId,
|
|
983
1042
|
log_context: task.metadata?.log_context
|
|
984
1043
|
};
|
|
985
1044
|
}
|
|
986
|
-
emitTaskStarted(task, workerId) {
|
|
1045
|
+
emitTaskStarted(task, workerId, consumerId) {
|
|
987
1046
|
const startedAt = Date.now();
|
|
988
1047
|
this.taskStartTimes.set(utils_taskIdGen.tId(task), startedAt);
|
|
989
1048
|
if (this.lifecycleProvider?.onTaskStarted) {
|
|
990
|
-
const ctx = this.buildTaskContext(task, workerId);
|
|
1049
|
+
const ctx = this.buildTaskContext(task, workerId, consumerId);
|
|
991
1050
|
const queuedDuration = startedAt - (task.created_at?.getTime() || startedAt);
|
|
992
1051
|
this.emitLifecycleEvent(
|
|
993
1052
|
this.lifecycleProvider.onTaskStarted,
|
|
@@ -999,12 +1058,12 @@ class TaskRunner {
|
|
|
999
1058
|
);
|
|
1000
1059
|
}
|
|
1001
1060
|
}
|
|
1002
|
-
emitTaskCompleted(task, workerId, result) {
|
|
1061
|
+
emitTaskCompleted(task, workerId, result, consumerId) {
|
|
1003
1062
|
const completedAt = Date.now();
|
|
1004
1063
|
const startedAt = this.taskStartTimes.get(utils_taskIdGen.tId(task)) || completedAt;
|
|
1005
1064
|
this.taskStartTimes.delete(utils_taskIdGen.tId(task));
|
|
1006
1065
|
if (this.lifecycleProvider?.onTaskCompleted) {
|
|
1007
|
-
const ctx = this.buildTaskContext(task, workerId);
|
|
1066
|
+
const ctx = this.buildTaskContext(task, workerId, consumerId);
|
|
1008
1067
|
const timing = {
|
|
1009
1068
|
queued_duration_ms: startedAt - (task.created_at?.getTime() || startedAt),
|
|
1010
1069
|
processing_duration_ms: completedAt - startedAt,
|
|
@@ -1016,12 +1075,12 @@ class TaskRunner {
|
|
|
1016
1075
|
);
|
|
1017
1076
|
}
|
|
1018
1077
|
}
|
|
1019
|
-
emitTaskFailed(task, workerId, error, willRetry, nextAttemptAt) {
|
|
1078
|
+
emitTaskFailed(task, workerId, error, willRetry, nextAttemptAt, consumerId) {
|
|
1020
1079
|
const completedAt = Date.now();
|
|
1021
1080
|
const startedAt = this.taskStartTimes.get(utils_taskIdGen.tId(task)) || completedAt;
|
|
1022
1081
|
this.taskStartTimes.delete(utils_taskIdGen.tId(task));
|
|
1023
1082
|
if (this.lifecycleProvider?.onTaskFailed) {
|
|
1024
|
-
const ctx = this.buildTaskContext(task, workerId);
|
|
1083
|
+
const ctx = this.buildTaskContext(task, workerId, consumerId);
|
|
1025
1084
|
const timing = {
|
|
1026
1085
|
queued_duration_ms: startedAt - (task.created_at?.getTime() || startedAt),
|
|
1027
1086
|
processing_duration_ms: completedAt - startedAt,
|
|
@@ -1039,6 +1098,247 @@ function getEnabledQueues() {
|
|
|
1039
1098
|
if (enabledQueues.length === 0) throw new Error("No queues enabled");
|
|
1040
1099
|
return enabledQueues.map(mq.getEnvironmentQueueName);
|
|
1041
1100
|
}
|
|
1101
|
+
function getFlowMeta(task) {
|
|
1102
|
+
return task.metadata?.flow_meta;
|
|
1103
|
+
}
|
|
1104
|
+
function getFlowMetaRequired(task) {
|
|
1105
|
+
return task.metadata.flow_meta;
|
|
1106
|
+
}
|
|
1107
|
+
class FlowMiddleware {
|
|
1108
|
+
constructor(barrierProvider, generateId, flowLifecycleProvider, workerId = "") {
|
|
1109
|
+
this.barrierProvider = barrierProvider;
|
|
1110
|
+
this.generateId = generateId;
|
|
1111
|
+
this.flowLifecycleProvider = flowLifecycleProvider;
|
|
1112
|
+
this.workerId = workerId;
|
|
1113
|
+
}
|
|
1114
|
+
emitFlowEvent(callback, ctx) {
|
|
1115
|
+
if (!callback) return;
|
|
1116
|
+
try {
|
|
1117
|
+
const result = callback(ctx);
|
|
1118
|
+
if (result instanceof Promise) {
|
|
1119
|
+
result.catch(() => {
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
1122
|
+
} catch {
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
buildFlowContext(flowMeta) {
|
|
1126
|
+
return {
|
|
1127
|
+
flow_id: flowMeta.flow_id,
|
|
1128
|
+
total_steps: flowMeta.total_steps,
|
|
1129
|
+
join: flowMeta.join,
|
|
1130
|
+
failure_policy: flowMeta.failure_policy,
|
|
1131
|
+
entity: flowMeta.entity,
|
|
1132
|
+
worker_id: this.workerId
|
|
1133
|
+
};
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Process completed tasks for flow orchestration.
|
|
1137
|
+
* Called from TaskHandler.postProcessTasks after markFailed/markSuccess.
|
|
1138
|
+
*
|
|
1139
|
+
* @param input Categorized terminal tasks — success and final-failed (no retries left)
|
|
1140
|
+
* @returns Join tasks to dispatch and entity projections to sync
|
|
1141
|
+
*/
|
|
1142
|
+
async onPostProcess(input) {
|
|
1143
|
+
const joinTasks = [];
|
|
1144
|
+
const projections = [];
|
|
1145
|
+
const successTaskSet = new Set(input.successTasks);
|
|
1146
|
+
const allTasks = [...input.successTasks, ...input.failedTasks];
|
|
1147
|
+
const joinCompletions = [];
|
|
1148
|
+
const timeoutTasks = [];
|
|
1149
|
+
const stepTasks = [];
|
|
1150
|
+
for (const task of allTasks) {
|
|
1151
|
+
const flowMeta = getFlowMeta(task);
|
|
1152
|
+
if (!flowMeta) continue;
|
|
1153
|
+
const isSuccess = successTaskSet.has(task);
|
|
1154
|
+
if (flowMeta.is_join) {
|
|
1155
|
+
joinCompletions.push(task);
|
|
1156
|
+
} else if (flowMeta.is_timeout) {
|
|
1157
|
+
timeoutTasks.push(task);
|
|
1158
|
+
} else {
|
|
1159
|
+
stepTasks.push({ task, isSuccess });
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
for (const task of joinCompletions) {
|
|
1163
|
+
const flowMeta = getFlowMetaRequired(task);
|
|
1164
|
+
const isSuccess = successTaskSet.has(task);
|
|
1165
|
+
if (flowMeta.entity) {
|
|
1166
|
+
try {
|
|
1167
|
+
const status = isSuccess ? "executed" : "failed";
|
|
1168
|
+
const error = !isSuccess ? task.execution_stats?.last_error || "Join task failed" : void 0;
|
|
1169
|
+
const p = core_async_AsyncActions.buildProjection(
|
|
1170
|
+
{
|
|
1171
|
+
...task,
|
|
1172
|
+
id: flowMeta.flow_id,
|
|
1173
|
+
entity: flowMeta.entity
|
|
1174
|
+
},
|
|
1175
|
+
status,
|
|
1176
|
+
{ result: task.execution_result, error }
|
|
1177
|
+
);
|
|
1178
|
+
if (p) projections.push(p);
|
|
1179
|
+
} catch {
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
for (const task of timeoutTasks) {
|
|
1184
|
+
const flowMeta = getFlowMetaRequired(task);
|
|
1185
|
+
const flowId = flowMeta.flow_id;
|
|
1186
|
+
await this.barrierProvider.initBarrier(flowId, flowMeta.total_steps);
|
|
1187
|
+
const isComplete = await this.barrierProvider.isComplete(flowId);
|
|
1188
|
+
if (isComplete) continue;
|
|
1189
|
+
const isFirstAbort = await this.barrierProvider.markAborted(flowId);
|
|
1190
|
+
if (!isFirstAbort) continue;
|
|
1191
|
+
const partialResults = await this.barrierProvider.getStepResults(flowId);
|
|
1192
|
+
const flowResults = {
|
|
1193
|
+
flow_id: flowId,
|
|
1194
|
+
steps: partialResults,
|
|
1195
|
+
timed_out: true
|
|
1196
|
+
};
|
|
1197
|
+
const joinTask = this.buildJoinTask(flowMeta, flowResults);
|
|
1198
|
+
joinTasks.push(joinTask);
|
|
1199
|
+
if (this.flowLifecycleProvider?.onFlowTimedOut) {
|
|
1200
|
+
const startedAt = await this.barrierProvider.getStartedAt(flowId);
|
|
1201
|
+
const durationMs = startedAt ? Date.now() - startedAt.getTime() : 0;
|
|
1202
|
+
this.emitFlowEvent(this.flowLifecycleProvider.onFlowTimedOut, {
|
|
1203
|
+
...this.buildFlowContext(flowMeta),
|
|
1204
|
+
duration_ms: durationMs,
|
|
1205
|
+
steps_completed: partialResults.length
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
if (flowMeta.entity) {
|
|
1209
|
+
try {
|
|
1210
|
+
const p = core_async_AsyncActions.buildProjection(
|
|
1211
|
+
{
|
|
1212
|
+
...joinTask,
|
|
1213
|
+
id: flowId,
|
|
1214
|
+
entity: flowMeta.entity
|
|
1215
|
+
},
|
|
1216
|
+
"failed",
|
|
1217
|
+
{ error: "flow_timeout" }
|
|
1218
|
+
);
|
|
1219
|
+
if (p) projections.push(p);
|
|
1220
|
+
} catch {
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const stepsByFlow = /* @__PURE__ */ new Map();
|
|
1225
|
+
for (const entry of stepTasks) {
|
|
1226
|
+
const flowMeta = getFlowMetaRequired(entry.task);
|
|
1227
|
+
const group = stepsByFlow.get(flowMeta.flow_id) || [];
|
|
1228
|
+
group.push(entry);
|
|
1229
|
+
stepsByFlow.set(flowMeta.flow_id, group);
|
|
1230
|
+
}
|
|
1231
|
+
for (const [flowId, entries] of stepsByFlow) {
|
|
1232
|
+
const firstFlowMeta = getFlowMetaRequired(entries[0].task);
|
|
1233
|
+
await this.barrierProvider.initBarrier(flowId, firstFlowMeta.total_steps);
|
|
1234
|
+
if (firstFlowMeta.failure_policy === "abort") {
|
|
1235
|
+
const hasFailure = entries.some((e) => !e.isSuccess);
|
|
1236
|
+
if (hasFailure) {
|
|
1237
|
+
const isFirstAbort = await this.barrierProvider.markAborted(flowId);
|
|
1238
|
+
if (isFirstAbort) {
|
|
1239
|
+
const stepResults2 = this.buildStepResults(entries);
|
|
1240
|
+
await this.barrierProvider.batchDecrementAndCheck(flowId, stepResults2);
|
|
1241
|
+
const allResults = await this.barrierProvider.getStepResults(flowId);
|
|
1242
|
+
const flowResults = {
|
|
1243
|
+
flow_id: flowId,
|
|
1244
|
+
steps: allResults,
|
|
1245
|
+
aborted: true
|
|
1246
|
+
};
|
|
1247
|
+
const joinTask = this.buildJoinTask(firstFlowMeta, flowResults);
|
|
1248
|
+
joinTasks.push(joinTask);
|
|
1249
|
+
if (this.flowLifecycleProvider?.onFlowAborted) {
|
|
1250
|
+
const startedAt = await this.barrierProvider.getStartedAt(flowId);
|
|
1251
|
+
const durationMs = startedAt ? Date.now() - startedAt.getTime() : 0;
|
|
1252
|
+
const failedEntry = entries.find((e) => !e.isSuccess);
|
|
1253
|
+
const triggerIndex = failedEntry ? failedEntry.task.metadata?.flow_meta?.step_index ?? -1 : -1;
|
|
1254
|
+
this.emitFlowEvent(this.flowLifecycleProvider.onFlowAborted, {
|
|
1255
|
+
...this.buildFlowContext(firstFlowMeta),
|
|
1256
|
+
duration_ms: durationMs,
|
|
1257
|
+
steps_completed: allResults.length,
|
|
1258
|
+
trigger_step_index: triggerIndex
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
if (firstFlowMeta.entity) {
|
|
1262
|
+
try {
|
|
1263
|
+
const p = core_async_AsyncActions.buildProjection(
|
|
1264
|
+
{
|
|
1265
|
+
...joinTask,
|
|
1266
|
+
id: flowId,
|
|
1267
|
+
entity: firstFlowMeta.entity
|
|
1268
|
+
},
|
|
1269
|
+
"failed",
|
|
1270
|
+
{ error: "flow_aborted" }
|
|
1271
|
+
);
|
|
1272
|
+
if (p) projections.push(p);
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const stepResults = this.buildStepResults(entries);
|
|
1281
|
+
const { remaining } = await this.barrierProvider.batchDecrementAndCheck(flowId, stepResults);
|
|
1282
|
+
if (remaining === 0) {
|
|
1283
|
+
const allResults = await this.barrierProvider.getStepResults(flowId);
|
|
1284
|
+
const flowResults = {
|
|
1285
|
+
flow_id: flowId,
|
|
1286
|
+
steps: allResults
|
|
1287
|
+
};
|
|
1288
|
+
const joinTask = this.buildJoinTask(firstFlowMeta, flowResults);
|
|
1289
|
+
joinTasks.push(joinTask);
|
|
1290
|
+
if (this.flowLifecycleProvider?.onFlowCompleted) {
|
|
1291
|
+
const startedAt = await this.barrierProvider.getStartedAt(flowId);
|
|
1292
|
+
const durationMs = startedAt ? Date.now() - startedAt.getTime() : 0;
|
|
1293
|
+
const stepsSucceeded = allResults.filter((r) => r.status === "success").length;
|
|
1294
|
+
const stepsFailed = allResults.filter((r) => r.status === "fail").length;
|
|
1295
|
+
this.emitFlowEvent(this.flowLifecycleProvider.onFlowCompleted, {
|
|
1296
|
+
...this.buildFlowContext(firstFlowMeta),
|
|
1297
|
+
duration_ms: durationMs,
|
|
1298
|
+
steps_succeeded: stepsSucceeded,
|
|
1299
|
+
steps_failed: stepsFailed
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return { joinTasks, projections };
|
|
1305
|
+
}
|
|
1306
|
+
buildStepResults(entries) {
|
|
1307
|
+
return entries.map(({ task, isSuccess }) => {
|
|
1308
|
+
const flowMeta = getFlowMetaRequired(task);
|
|
1309
|
+
return {
|
|
1310
|
+
step_index: flowMeta.step_index,
|
|
1311
|
+
status: isSuccess ? "success" : "fail",
|
|
1312
|
+
result: task.execution_result,
|
|
1313
|
+
error: !isSuccess ? task.execution_stats?.last_error || "Step failed" : void 0
|
|
1314
|
+
};
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
buildJoinTask(flowMeta, flowResults) {
|
|
1318
|
+
const now = /* @__PURE__ */ new Date();
|
|
1319
|
+
const joinFlowMeta = {
|
|
1320
|
+
...flowMeta,
|
|
1321
|
+
is_join: true,
|
|
1322
|
+
is_timeout: void 0,
|
|
1323
|
+
step_index: -1
|
|
1324
|
+
};
|
|
1325
|
+
return {
|
|
1326
|
+
id: this.generateId(),
|
|
1327
|
+
type: flowMeta.join.type,
|
|
1328
|
+
queue_id: flowMeta.join.queue_id,
|
|
1329
|
+
payload: { flow_results: flowResults },
|
|
1330
|
+
execute_at: now,
|
|
1331
|
+
status: "scheduled",
|
|
1332
|
+
created_at: now,
|
|
1333
|
+
updated_at: now,
|
|
1334
|
+
force_store: true,
|
|
1335
|
+
metadata: {
|
|
1336
|
+
flow_meta: joinFlowMeta
|
|
1337
|
+
},
|
|
1338
|
+
...flowMeta.entity ? { entity: flowMeta.entity } : {}
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1042
1342
|
const METRICS_KEY_PREFIX = "task_metrics:";
|
|
1043
1343
|
const DISCARDED_TASKS_KEY = `${METRICS_KEY_PREFIX}discarded_tasks`;
|
|
1044
1344
|
const STATS_THRESHOLD = parseInt(process.env.TQ_STATS_THRESHOLD || "1000");
|
|
@@ -1065,23 +1365,35 @@ class TaskHandler {
|
|
|
1065
1365
|
};
|
|
1066
1366
|
this.totalProcessingMs = 0;
|
|
1067
1367
|
this.queueStats = /* @__PURE__ */ new Map();
|
|
1368
|
+
this.consumerStatsMap = /* @__PURE__ */ new Map();
|
|
1068
1369
|
this.logger = new client.Logger("TaskHandler", client.LogLevel.INFO);
|
|
1069
1370
|
this.config = config || {};
|
|
1070
1371
|
this.workerId = `${os__namespace.hostname()}-${process.pid}-${Date.now()}`;
|
|
1071
1372
|
this.workerStartedAt = /* @__PURE__ */ new Date();
|
|
1072
1373
|
this.taskStore = new TaskStore(databaseAdapter);
|
|
1073
|
-
this.
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
this.
|
|
1374
|
+
if (this.config.flowLifecycleProvider && !this.config.flowBarrierProvider) {
|
|
1375
|
+
throw new Error("[TQ] flowLifecycleProvider requires flowBarrierProvider — flow lifecycle events need flow orchestration enabled");
|
|
1376
|
+
}
|
|
1377
|
+
this.flowMiddleware = this.config.flowBarrierProvider ? new FlowMiddleware(
|
|
1378
|
+
this.config.flowBarrierProvider,
|
|
1078
1379
|
databaseAdapter.generateId.bind(databaseAdapter),
|
|
1079
|
-
this.config.
|
|
1080
|
-
this.
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1380
|
+
this.config.flowLifecycleProvider,
|
|
1381
|
+
this.workerId
|
|
1382
|
+
) : void 0;
|
|
1383
|
+
this.taskRunner = new TaskRunner({
|
|
1384
|
+
messageQueue,
|
|
1385
|
+
taskQueue: taskQueuesManager,
|
|
1386
|
+
taskStore: this.taskStore,
|
|
1387
|
+
cacheProvider: this.cacheAdapter,
|
|
1388
|
+
generateId: databaseAdapter.generateId.bind(databaseAdapter),
|
|
1389
|
+
lifecycleProvider: this.config.lifecycleProvider,
|
|
1390
|
+
lifecycleConfig: this.config.lifecycle,
|
|
1391
|
+
entityProjection: this.config.entityProjection,
|
|
1392
|
+
entityProjectionConfig: this.config.entityProjectionConfig,
|
|
1393
|
+
flowMiddleware: this.flowMiddleware,
|
|
1394
|
+
flowLifecycleProvider: this.config.flowLifecycleProvider,
|
|
1395
|
+
workerId: this.workerId
|
|
1396
|
+
});
|
|
1085
1397
|
}
|
|
1086
1398
|
// ============ Lifecycle Event Helpers ============
|
|
1087
1399
|
get lifecycleProvider() {
|
|
@@ -1386,9 +1698,9 @@ class TaskHandler {
|
|
|
1386
1698
|
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
1387
1699
|
}
|
|
1388
1700
|
}
|
|
1389
|
-
if (this.
|
|
1701
|
+
if (this.flowMiddleware) {
|
|
1390
1702
|
try {
|
|
1391
|
-
const flowResult = await this.
|
|
1703
|
+
const flowResult = await this.flowMiddleware.onPostProcess({ successTasks, failedTasks: finalFailedTasks });
|
|
1392
1704
|
if (flowResult.projections.length > 0 && this.entityProjectionProvider) {
|
|
1393
1705
|
await core_async_AsyncActions.syncProjections(flowResult.projections, this.entityProjectionProvider, this.logger);
|
|
1394
1706
|
}
|
|
@@ -1411,6 +1723,7 @@ class TaskHandler {
|
|
|
1411
1723
|
}
|
|
1412
1724
|
const batchStartTime = Date.now();
|
|
1413
1725
|
const taskTypes = [...new Set(tasks.map((t) => t.type))];
|
|
1726
|
+
this.registerConsumerIfNew(id, streamName);
|
|
1414
1727
|
if (this.workerProvider?.onBatchStarted) {
|
|
1415
1728
|
this.emitLifecycleEvent(
|
|
1416
1729
|
this.workerProvider.onBatchStarted,
|
|
@@ -1487,6 +1800,7 @@ class TaskHandler {
|
|
|
1487
1800
|
await this.reportQueueStats(streamName);
|
|
1488
1801
|
const batchDuration = Date.now() - batchStartTime;
|
|
1489
1802
|
this.updateWorkerStats(successTasks.length, failedTasks.length, batchDuration);
|
|
1803
|
+
this.updateConsumerStats(id, successTasks.length, failedTasks.length);
|
|
1490
1804
|
if (this.workerProvider?.onBatchCompleted) {
|
|
1491
1805
|
this.emitLifecycleEvent(
|
|
1492
1806
|
this.workerProvider.onBatchCompleted,
|
|
@@ -1519,6 +1833,7 @@ class TaskHandler {
|
|
|
1519
1833
|
this.processMatureTasks(abortSignal);
|
|
1520
1834
|
abortSignal?.addEventListener("abort", () => {
|
|
1521
1835
|
this.stopHeartbeat();
|
|
1836
|
+
this.emitAllConsumersStopped("shutdown");
|
|
1522
1837
|
this.emitWorkerStopped("shutdown");
|
|
1523
1838
|
});
|
|
1524
1839
|
}
|
|
@@ -1554,7 +1869,8 @@ class TaskHandler {
|
|
|
1554
1869
|
{
|
|
1555
1870
|
...this.buildWorkerInfo(),
|
|
1556
1871
|
stats: { ...this.workerStats },
|
|
1557
|
-
memory_usage_mb: memUsage.heapUsed / 1024 / 1024
|
|
1872
|
+
memory_usage_mb: memUsage.heapUsed / 1024 / 1024,
|
|
1873
|
+
active_consumers: this.getActiveConsumerStats()
|
|
1558
1874
|
}
|
|
1559
1875
|
);
|
|
1560
1876
|
}
|
|
@@ -1580,6 +1896,58 @@ class TaskHandler {
|
|
|
1580
1896
|
this.workerStats.avg_processing_ms = this.totalProcessingMs / this.workerStats.tasks_processed;
|
|
1581
1897
|
}
|
|
1582
1898
|
}
|
|
1899
|
+
// ============ Consumer Tracking ============
|
|
1900
|
+
registerConsumerIfNew(consumerId, queueId) {
|
|
1901
|
+
if (this.consumerStatsMap.has(consumerId)) return;
|
|
1902
|
+
const now = /* @__PURE__ */ new Date();
|
|
1903
|
+
this.consumerStatsMap.set(consumerId, {
|
|
1904
|
+
consumer_id: consumerId,
|
|
1905
|
+
queue_id: queueId,
|
|
1906
|
+
tasks_processed: 0,
|
|
1907
|
+
tasks_succeeded: 0,
|
|
1908
|
+
tasks_failed: 0,
|
|
1909
|
+
started_at: now
|
|
1910
|
+
});
|
|
1911
|
+
if (this.workerProvider?.onConsumerStarted) {
|
|
1912
|
+
this.emitLifecycleEvent(
|
|
1913
|
+
this.workerProvider.onConsumerStarted,
|
|
1914
|
+
{
|
|
1915
|
+
consumer_id: consumerId,
|
|
1916
|
+
queue_id: queueId,
|
|
1917
|
+
worker_id: this.workerId,
|
|
1918
|
+
started_at: now
|
|
1919
|
+
}
|
|
1920
|
+
);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
updateConsumerStats(consumerId, succeeded, failed) {
|
|
1924
|
+
const stats = this.consumerStatsMap.get(consumerId);
|
|
1925
|
+
if (!stats) return;
|
|
1926
|
+
stats.tasks_processed += succeeded + failed;
|
|
1927
|
+
stats.tasks_succeeded += succeeded;
|
|
1928
|
+
stats.tasks_failed += failed;
|
|
1929
|
+
stats.last_task_at = /* @__PURE__ */ new Date();
|
|
1930
|
+
}
|
|
1931
|
+
getActiveConsumerStats() {
|
|
1932
|
+
return Array.from(this.consumerStatsMap.values()).map(({ started_at, ...stats }) => stats);
|
|
1933
|
+
}
|
|
1934
|
+
emitAllConsumersStopped(reason) {
|
|
1935
|
+
if (!this.workerProvider?.onConsumerStopped) return;
|
|
1936
|
+
for (const entry of this.consumerStatsMap.values()) {
|
|
1937
|
+
const { started_at, ...stats } = entry;
|
|
1938
|
+
this.emitLifecycleEvent(
|
|
1939
|
+
this.workerProvider.onConsumerStopped,
|
|
1940
|
+
{
|
|
1941
|
+
consumer_id: entry.consumer_id,
|
|
1942
|
+
queue_id: entry.queue_id,
|
|
1943
|
+
worker_id: this.workerId,
|
|
1944
|
+
started_at,
|
|
1945
|
+
reason,
|
|
1946
|
+
stats
|
|
1947
|
+
}
|
|
1948
|
+
);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1583
1951
|
emitLifecycleEvent(callback, ctx) {
|
|
1584
1952
|
if (!callback) return;
|
|
1585
1953
|
try {
|
|
@@ -1675,6 +2043,7 @@ class TaskHandler {
|
|
|
1675
2043
|
attempt: retryCount + 1,
|
|
1676
2044
|
max_retries: maxRetries,
|
|
1677
2045
|
scheduled_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
2046
|
+
worker_id: this.workerId,
|
|
1678
2047
|
log_context: task.metadata?.log_context
|
|
1679
2048
|
};
|
|
1680
2049
|
}
|
|
@@ -1854,7 +2223,8 @@ class InMemoryFlowBarrierProvider {
|
|
|
1854
2223
|
this.barriers.set(flowId, {
|
|
1855
2224
|
remaining: totalSteps,
|
|
1856
2225
|
status: "active",
|
|
1857
|
-
results: /* @__PURE__ */ new Map()
|
|
2226
|
+
results: /* @__PURE__ */ new Map(),
|
|
2227
|
+
started_at: /* @__PURE__ */ new Date()
|
|
1858
2228
|
});
|
|
1859
2229
|
}
|
|
1860
2230
|
async batchDecrementAndCheck(flowId, results) {
|
|
@@ -1898,190 +2268,10 @@ class InMemoryFlowBarrierProvider {
|
|
|
1898
2268
|
if (!state) return false;
|
|
1899
2269
|
return state.status === "complete";
|
|
1900
2270
|
}
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
function getFlowMetaRequired(task) {
|
|
1906
|
-
return task.metadata.flow_meta;
|
|
1907
|
-
}
|
|
1908
|
-
class FlowMiddleware {
|
|
1909
|
-
constructor(barrierProvider, generateId) {
|
|
1910
|
-
this.barrierProvider = barrierProvider;
|
|
1911
|
-
this.generateId = generateId;
|
|
1912
|
-
}
|
|
1913
|
-
/**
|
|
1914
|
-
* Process completed tasks for flow orchestration.
|
|
1915
|
-
* Called from TaskHandler.postProcessTasks after markFailed/markSuccess.
|
|
1916
|
-
*
|
|
1917
|
-
* @param input Categorized terminal tasks — success and final-failed (no retries left)
|
|
1918
|
-
* @returns Join tasks to dispatch and entity projections to sync
|
|
1919
|
-
*/
|
|
1920
|
-
async onPostProcess(input) {
|
|
1921
|
-
const joinTasks = [];
|
|
1922
|
-
const projections = [];
|
|
1923
|
-
const successTaskSet = new Set(input.successTasks);
|
|
1924
|
-
const allTasks = [...input.successTasks, ...input.failedTasks];
|
|
1925
|
-
const joinCompletions = [];
|
|
1926
|
-
const timeoutTasks = [];
|
|
1927
|
-
const stepTasks = [];
|
|
1928
|
-
for (const task of allTasks) {
|
|
1929
|
-
const flowMeta = getFlowMeta(task);
|
|
1930
|
-
if (!flowMeta) continue;
|
|
1931
|
-
const isSuccess = successTaskSet.has(task);
|
|
1932
|
-
if (flowMeta.is_join) {
|
|
1933
|
-
joinCompletions.push(task);
|
|
1934
|
-
} else if (flowMeta.is_timeout) {
|
|
1935
|
-
timeoutTasks.push(task);
|
|
1936
|
-
} else {
|
|
1937
|
-
stepTasks.push({ task, isSuccess });
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
for (const task of joinCompletions) {
|
|
1941
|
-
const flowMeta = getFlowMetaRequired(task);
|
|
1942
|
-
const isSuccess = successTaskSet.has(task);
|
|
1943
|
-
if (flowMeta.entity) {
|
|
1944
|
-
try {
|
|
1945
|
-
const status = isSuccess ? "executed" : "failed";
|
|
1946
|
-
const error = !isSuccess ? task.execution_stats?.last_error || "Join task failed" : void 0;
|
|
1947
|
-
const p = core_async_AsyncActions.buildProjection(
|
|
1948
|
-
{
|
|
1949
|
-
...task,
|
|
1950
|
-
id: flowMeta.flow_id,
|
|
1951
|
-
entity: flowMeta.entity
|
|
1952
|
-
},
|
|
1953
|
-
status,
|
|
1954
|
-
{ result: task.execution_result, error }
|
|
1955
|
-
);
|
|
1956
|
-
if (p) projections.push(p);
|
|
1957
|
-
} catch {
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
}
|
|
1961
|
-
for (const task of timeoutTasks) {
|
|
1962
|
-
const flowMeta = getFlowMetaRequired(task);
|
|
1963
|
-
const flowId = flowMeta.flow_id;
|
|
1964
|
-
await this.barrierProvider.initBarrier(flowId, flowMeta.total_steps);
|
|
1965
|
-
const isComplete = await this.barrierProvider.isComplete(flowId);
|
|
1966
|
-
if (isComplete) continue;
|
|
1967
|
-
const isFirstAbort = await this.barrierProvider.markAborted(flowId);
|
|
1968
|
-
if (!isFirstAbort) continue;
|
|
1969
|
-
const partialResults = await this.barrierProvider.getStepResults(flowId);
|
|
1970
|
-
const flowResults = {
|
|
1971
|
-
flow_id: flowId,
|
|
1972
|
-
steps: partialResults,
|
|
1973
|
-
timed_out: true
|
|
1974
|
-
};
|
|
1975
|
-
const joinTask = this.buildJoinTask(flowMeta, flowResults);
|
|
1976
|
-
joinTasks.push(joinTask);
|
|
1977
|
-
if (flowMeta.entity) {
|
|
1978
|
-
try {
|
|
1979
|
-
const p = core_async_AsyncActions.buildProjection(
|
|
1980
|
-
{
|
|
1981
|
-
...joinTask,
|
|
1982
|
-
id: flowId,
|
|
1983
|
-
entity: flowMeta.entity
|
|
1984
|
-
},
|
|
1985
|
-
"failed",
|
|
1986
|
-
{ error: "flow_timeout" }
|
|
1987
|
-
);
|
|
1988
|
-
if (p) projections.push(p);
|
|
1989
|
-
} catch {
|
|
1990
|
-
}
|
|
1991
|
-
}
|
|
1992
|
-
}
|
|
1993
|
-
const stepsByFlow = /* @__PURE__ */ new Map();
|
|
1994
|
-
for (const entry of stepTasks) {
|
|
1995
|
-
const flowMeta = getFlowMetaRequired(entry.task);
|
|
1996
|
-
const group = stepsByFlow.get(flowMeta.flow_id) || [];
|
|
1997
|
-
group.push(entry);
|
|
1998
|
-
stepsByFlow.set(flowMeta.flow_id, group);
|
|
1999
|
-
}
|
|
2000
|
-
for (const [flowId, entries] of stepsByFlow) {
|
|
2001
|
-
const firstFlowMeta = getFlowMetaRequired(entries[0].task);
|
|
2002
|
-
await this.barrierProvider.initBarrier(flowId, firstFlowMeta.total_steps);
|
|
2003
|
-
if (firstFlowMeta.failure_policy === "abort") {
|
|
2004
|
-
const hasFailure = entries.some((e) => !e.isSuccess);
|
|
2005
|
-
if (hasFailure) {
|
|
2006
|
-
const isFirstAbort = await this.barrierProvider.markAborted(flowId);
|
|
2007
|
-
if (isFirstAbort) {
|
|
2008
|
-
const stepResults2 = this.buildStepResults(entries);
|
|
2009
|
-
await this.barrierProvider.batchDecrementAndCheck(flowId, stepResults2);
|
|
2010
|
-
const allResults = await this.barrierProvider.getStepResults(flowId);
|
|
2011
|
-
const flowResults = {
|
|
2012
|
-
flow_id: flowId,
|
|
2013
|
-
steps: allResults,
|
|
2014
|
-
aborted: true
|
|
2015
|
-
};
|
|
2016
|
-
const joinTask = this.buildJoinTask(firstFlowMeta, flowResults);
|
|
2017
|
-
joinTasks.push(joinTask);
|
|
2018
|
-
if (firstFlowMeta.entity) {
|
|
2019
|
-
try {
|
|
2020
|
-
const p = core_async_AsyncActions.buildProjection(
|
|
2021
|
-
{
|
|
2022
|
-
...joinTask,
|
|
2023
|
-
id: flowId,
|
|
2024
|
-
entity: firstFlowMeta.entity
|
|
2025
|
-
},
|
|
2026
|
-
"failed",
|
|
2027
|
-
{ error: "flow_aborted" }
|
|
2028
|
-
);
|
|
2029
|
-
if (p) projections.push(p);
|
|
2030
|
-
} catch {
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
}
|
|
2034
|
-
continue;
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
const stepResults = this.buildStepResults(entries);
|
|
2038
|
-
const { remaining } = await this.barrierProvider.batchDecrementAndCheck(flowId, stepResults);
|
|
2039
|
-
if (remaining === 0) {
|
|
2040
|
-
const allResults = await this.barrierProvider.getStepResults(flowId);
|
|
2041
|
-
const flowResults = {
|
|
2042
|
-
flow_id: flowId,
|
|
2043
|
-
steps: allResults
|
|
2044
|
-
};
|
|
2045
|
-
const joinTask = this.buildJoinTask(firstFlowMeta, flowResults);
|
|
2046
|
-
joinTasks.push(joinTask);
|
|
2047
|
-
}
|
|
2048
|
-
}
|
|
2049
|
-
return { joinTasks, projections };
|
|
2050
|
-
}
|
|
2051
|
-
buildStepResults(entries) {
|
|
2052
|
-
return entries.map(({ task, isSuccess }) => {
|
|
2053
|
-
const flowMeta = getFlowMetaRequired(task);
|
|
2054
|
-
return {
|
|
2055
|
-
step_index: flowMeta.step_index,
|
|
2056
|
-
status: isSuccess ? "success" : "fail",
|
|
2057
|
-
result: task.execution_result,
|
|
2058
|
-
error: !isSuccess ? task.execution_stats?.last_error || "Step failed" : void 0
|
|
2059
|
-
};
|
|
2060
|
-
});
|
|
2061
|
-
}
|
|
2062
|
-
buildJoinTask(flowMeta, flowResults) {
|
|
2063
|
-
const now = /* @__PURE__ */ new Date();
|
|
2064
|
-
const joinFlowMeta = {
|
|
2065
|
-
...flowMeta,
|
|
2066
|
-
is_join: true,
|
|
2067
|
-
is_timeout: void 0,
|
|
2068
|
-
step_index: -1
|
|
2069
|
-
};
|
|
2070
|
-
return {
|
|
2071
|
-
id: this.generateId(),
|
|
2072
|
-
type: flowMeta.join.type,
|
|
2073
|
-
queue_id: flowMeta.join.queue_id,
|
|
2074
|
-
payload: { flow_results: flowResults },
|
|
2075
|
-
execute_at: now,
|
|
2076
|
-
status: "scheduled",
|
|
2077
|
-
created_at: now,
|
|
2078
|
-
updated_at: now,
|
|
2079
|
-
force_store: true,
|
|
2080
|
-
metadata: {
|
|
2081
|
-
flow_meta: joinFlowMeta
|
|
2082
|
-
},
|
|
2083
|
-
...flowMeta.entity ? { entity: flowMeta.entity } : {}
|
|
2084
|
-
};
|
|
2271
|
+
async getStartedAt(flowId) {
|
|
2272
|
+
const state = this.barriers.get(flowId);
|
|
2273
|
+
if (!state) return null;
|
|
2274
|
+
return state.started_at;
|
|
2085
2275
|
}
|
|
2086
2276
|
}
|
|
2087
2277
|
class ConsoleHealthProvider {
|
|
@@ -2121,7 +2311,7 @@ class ConsoleHealthProvider {
|
|
|
2121
2311
|
}
|
|
2122
2312
|
onWorkerHeartbeat(info) {
|
|
2123
2313
|
console.log(
|
|
2124
|
-
`${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`
|
|
2314
|
+
`${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}`
|
|
2125
2315
|
);
|
|
2126
2316
|
}
|
|
2127
2317
|
onWorkerStopped(info) {
|
|
@@ -2139,6 +2329,49 @@ class ConsoleHealthProvider {
|
|
|
2139
2329
|
`${this.prefix} Batch completed: ${info.batch_size} tasks in ${info.duration_ms}ms - succeeded: ${info.succeeded}, failed: ${info.failed}`
|
|
2140
2330
|
);
|
|
2141
2331
|
}
|
|
2332
|
+
// ============ Consumer Lifecycle ============
|
|
2333
|
+
onConsumerStarted(info) {
|
|
2334
|
+
console.log(
|
|
2335
|
+
`${this.prefix} Consumer started: ${info.consumer_id} on worker ${info.worker_id} - queue: ${info.queue_id}`
|
|
2336
|
+
);
|
|
2337
|
+
}
|
|
2338
|
+
onConsumerStopped(info) {
|
|
2339
|
+
console.log(
|
|
2340
|
+
`${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}`
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
// ============ Task Batch Lifecycle ============
|
|
2344
|
+
onTaskBatchStarted(ctx) {
|
|
2345
|
+
console.log(
|
|
2346
|
+
`${this.prefix} Task batch started: ${ctx.task_type} - ${ctx.tasks.length} tasks in queue ${ctx.queue_id}`
|
|
2347
|
+
);
|
|
2348
|
+
}
|
|
2349
|
+
onTaskBatchCompleted(ctx) {
|
|
2350
|
+
console.log(
|
|
2351
|
+
`${this.prefix} Task batch completed: ${ctx.task_type} in ${ctx.duration_ms}ms - succeeded: ${ctx.succeeded.length}, failed: ${ctx.failed.length}`
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
// ============ Flow Lifecycle ============
|
|
2355
|
+
onFlowStarted(ctx) {
|
|
2356
|
+
console.log(
|
|
2357
|
+
`${this.prefix} Flow started: ${ctx.flow_id} - ${ctx.total_steps} steps [${ctx.step_types.join(", ")}], join: ${ctx.join.type}, policy: ${ctx.failure_policy}`
|
|
2358
|
+
);
|
|
2359
|
+
}
|
|
2360
|
+
onFlowCompleted(ctx) {
|
|
2361
|
+
console.log(
|
|
2362
|
+
`${this.prefix} Flow completed: ${ctx.flow_id} in ${ctx.duration_ms}ms - succeeded: ${ctx.steps_succeeded}, failed: ${ctx.steps_failed}`
|
|
2363
|
+
);
|
|
2364
|
+
}
|
|
2365
|
+
onFlowAborted(ctx) {
|
|
2366
|
+
console.log(
|
|
2367
|
+
`${this.prefix} Flow aborted: ${ctx.flow_id} in ${ctx.duration_ms}ms - steps completed: ${ctx.steps_completed}, trigger: step ${ctx.trigger_step_index}`
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2370
|
+
onFlowTimedOut(ctx) {
|
|
2371
|
+
console.log(
|
|
2372
|
+
`${this.prefix} Flow timed out: ${ctx.flow_id} in ${ctx.duration_ms}ms - steps completed: ${ctx.steps_completed}`
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2142
2375
|
}
|
|
2143
2376
|
exports.InMemoryAdapter = PrismaAdapter.InMemoryAdapter;
|
|
2144
2377
|
exports.MongoDbAdapter = PrismaAdapter.MongoDbAdapter;
|