@supergrowthai/tq 1.0.11 → 1.0.13
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 +338 -20
- package/dist/AsyncActions-BOO1ikWz.cjs +241 -0
- package/dist/AsyncActions-BOO1ikWz.cjs.map +1 -0
- package/dist/AsyncActions-CZYO8ShR.js +242 -0
- package/dist/AsyncActions-CZYO8ShR.js.map +1 -0
- package/dist/{PrismaAdapter-CvM_XNtE.cjs → PrismaAdapter-CUIWhjms.cjs} +57 -81
- package/dist/PrismaAdapter-CUIWhjms.cjs.map +1 -0
- package/dist/{PrismaAdapter-Dy7MV090.js → PrismaAdapter-D5ACKPbS.js} +57 -81
- package/dist/PrismaAdapter-D5ACKPbS.js.map +1 -0
- package/dist/adapters/index.cjs +1 -1
- package/dist/adapters/index.mjs +1 -1
- package/dist/client-BxG7LzLv.cjs +90 -0
- package/dist/client-BxG7LzLv.cjs.map +1 -0
- package/dist/client-dvHNt8qU.js +91 -0
- package/dist/client-dvHNt8qU.js.map +1 -0
- package/dist/core/Actions.cjs +184 -16
- package/dist/core/Actions.cjs.map +1 -1
- package/dist/core/Actions.mjs +184 -16
- package/dist/core/Actions.mjs.map +1 -1
- package/dist/core/async/AsyncActions.cjs +4 -98
- package/dist/core/async/AsyncActions.cjs.map +1 -1
- package/dist/core/async/AsyncActions.mjs +4 -98
- package/dist/core/async/AsyncActions.mjs.map +1 -1
- package/dist/core/async/AsyncTaskManager.cjs +133 -22
- package/dist/core/async/AsyncTaskManager.cjs.map +1 -1
- package/dist/core/async/AsyncTaskManager.mjs +133 -22
- package/dist/core/async/AsyncTaskManager.mjs.map +1 -1
- package/dist/index.cjs +517 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +517 -35
- package/dist/index.mjs.map +1 -1
- package/dist/src/adapters/ITaskStorageAdapter.d.cts +0 -5
- package/dist/src/adapters/ITaskStorageAdapter.d.ts +0 -5
- package/dist/src/adapters/InMemoryAdapter.d.cts +0 -2
- package/dist/src/adapters/InMemoryAdapter.d.ts +0 -2
- package/dist/src/adapters/MongoDbAdapter.d.cts +0 -2
- package/dist/src/adapters/MongoDbAdapter.d.ts +0 -2
- package/dist/src/adapters/PrismaAdapter.d.cts +0 -2
- package/dist/src/adapters/PrismaAdapter.d.ts +0 -2
- package/dist/src/adapters/types.d.cts +7 -0
- package/dist/src/adapters/types.d.ts +7 -0
- package/dist/src/core/Actions.d.cts +25 -2
- package/dist/src/core/Actions.d.ts +25 -2
- package/dist/src/core/TaskHandler.d.cts +13 -5
- package/dist/src/core/TaskHandler.d.ts +13 -5
- package/dist/src/core/TaskRunner.d.cts +16 -1
- package/dist/src/core/TaskRunner.d.ts +16 -1
- package/dist/src/core/async/AsyncActions.d.cts +20 -1
- package/dist/src/core/async/AsyncActions.d.ts +20 -1
- package/dist/src/core/async/AsyncTaskManager.d.cts +36 -4
- package/dist/src/core/async/AsyncTaskManager.d.ts +36 -4
- package/dist/src/core/async/async-task-manager.d.cts +21 -3
- package/dist/src/core/async/async-task-manager.d.ts +21 -3
- package/dist/src/core/async/retry-utils.d.cts +15 -0
- package/dist/src/core/async/retry-utils.d.ts +15 -0
- package/dist/src/core/base/interfaces.d.cts +10 -2
- package/dist/src/core/base/interfaces.d.ts +10 -2
- package/dist/src/core/entity/IEntityProjectionProvider.d.cts +45 -0
- package/dist/src/core/entity/IEntityProjectionProvider.d.ts +45 -0
- package/dist/src/core/entity/index.d.cts +1 -0
- package/dist/src/core/entity/index.d.ts +1 -0
- package/dist/src/core/flow/FlowMiddleware.d.cts +26 -0
- package/dist/src/core/flow/FlowMiddleware.d.ts +26 -0
- package/dist/src/core/flow/IFlowBarrierProvider.d.cts +46 -0
- package/dist/src/core/flow/IFlowBarrierProvider.d.ts +46 -0
- package/dist/src/core/flow/InMemoryFlowBarrierProvider.d.cts +10 -0
- package/dist/src/core/flow/InMemoryFlowBarrierProvider.d.ts +10 -0
- package/dist/src/core/flow/index.d.cts +4 -0
- package/dist/src/core/flow/index.d.ts +4 -0
- package/dist/src/core/flow/types.d.cts +82 -0
- package/dist/src/core/flow/types.d.ts +82 -0
- package/dist/src/core/lifecycle.d.cts +9 -4
- package/dist/src/core/lifecycle.d.ts +9 -4
- package/dist/src/core/log-context.d.cts +10 -0
- package/dist/src/core/log-context.d.ts +10 -0
- package/dist/src/index.d.cts +4 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/test/adapter-consistency.test.d.cts +11 -0
- package/dist/src/test/adapter-consistency.test.d.ts +11 -0
- package/dist/src/test/immediate-mode-bugs.test.d.cts +11 -0
- package/dist/src/test/immediate-mode-bugs.test.d.ts +11 -0
- package/dist/src/test/rfc-001-result-persistence.test.d.cts +17 -0
- package/dist/src/test/rfc-001-result-persistence.test.d.ts +17 -0
- package/dist/src/test/rfc-002-flow-orchestration.test.d.cts +24 -0
- package/dist/src/test/rfc-002-flow-orchestration.test.d.ts +24 -0
- package/dist/src/test/rfc-003-entity-projection.test.d.cts +14 -0
- package/dist/src/test/rfc-003-entity-projection.test.d.ts +14 -0
- package/dist/src/test/rfc-004-async-hardening.test.d.cts +14 -0
- package/dist/src/test/rfc-004-async-hardening.test.d.ts +14 -0
- package/dist/src/test/rfc-005-log-context.test.d.cts +14 -0
- package/dist/src/test/rfc-005-log-context.test.d.ts +14 -0
- package/dist/src/test/tq-fixes.test.d.cts +17 -0
- package/dist/src/test/tq-fixes.test.d.ts +17 -0
- package/package.json +2 -2
- package/dist/PrismaAdapter-CvM_XNtE.cjs.map +0 -1
- package/dist/PrismaAdapter-Dy7MV090.js.map +0 -1
- package/dist/client-BAiCkZv7.js +0 -52
- package/dist/client-BAiCkZv7.js.map +0 -1
- package/dist/client-DgdG7pT6.cjs +0 -51
- package/dist/client-DgdG7pT6.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -45,12 +45,13 @@ var __callDispose = (stack, error, hasError) => {
|
|
|
45
45
|
return next();
|
|
46
46
|
};
|
|
47
47
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
48
|
-
const PrismaAdapter = require("./PrismaAdapter-
|
|
48
|
+
const PrismaAdapter = require("./PrismaAdapter-CUIWhjms.cjs");
|
|
49
49
|
const mq = require("@supergrowthai/mq");
|
|
50
|
-
const client = require("./client-
|
|
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("./
|
|
53
|
+
const core_async_AsyncActions = require("./AsyncActions-BOO1ikWz.cjs");
|
|
54
|
+
const node_async_hooks = require("node:async_hooks");
|
|
54
55
|
const moment = require("moment");
|
|
55
56
|
const os = require("os");
|
|
56
57
|
const core_async_AsyncTaskManager = require("./core/async/AsyncTaskManager.cjs");
|
|
@@ -161,7 +162,10 @@ class TaskStore {
|
|
|
161
162
|
created_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
162
163
|
updated_at: task.updated_at || /* @__PURE__ */ new Date(),
|
|
163
164
|
processing_started_at: task.processing_started_at || /* @__PURE__ */ new Date(),
|
|
164
|
-
force_store: task.force_store
|
|
165
|
+
force_store: task.force_store,
|
|
166
|
+
metadata: task.metadata,
|
|
167
|
+
partition_key: task.partition_key,
|
|
168
|
+
entity: task.entity
|
|
165
169
|
}));
|
|
166
170
|
return await this.databaseAdapter.addTasksToScheduled(transformedTasks);
|
|
167
171
|
}
|
|
@@ -669,14 +673,25 @@ class DisposableLockBatch {
|
|
|
669
673
|
);
|
|
670
674
|
}
|
|
671
675
|
}
|
|
676
|
+
const als = new node_async_hooks.AsyncLocalStorage();
|
|
677
|
+
client.Logger.setContextProvider(() => als.getStore());
|
|
678
|
+
function runWithLogContext(store, fn) {
|
|
679
|
+
return als.run(store, fn);
|
|
680
|
+
}
|
|
681
|
+
function getLogContext() {
|
|
682
|
+
return als.getStore();
|
|
683
|
+
}
|
|
672
684
|
class TaskRunner {
|
|
673
|
-
constructor(messageQueue, taskQueue, taskStore, cacheProvider, generateId, lifecycleProvider, lifecycleConfig) {
|
|
685
|
+
constructor(messageQueue, taskQueue, taskStore, cacheProvider, generateId, lifecycleProvider, lifecycleConfig, entityProjection, entityProjectionConfig, flowMiddleware) {
|
|
674
686
|
this.messageQueue = messageQueue;
|
|
675
687
|
this.taskQueue = taskQueue;
|
|
676
688
|
this.taskStore = taskStore;
|
|
677
689
|
this.generateId = generateId;
|
|
678
690
|
this.lifecycleProvider = lifecycleProvider;
|
|
679
691
|
this.lifecycleConfig = lifecycleConfig;
|
|
692
|
+
this.entityProjection = entityProjection;
|
|
693
|
+
this.entityProjectionConfig = entityProjectionConfig;
|
|
694
|
+
this.flowMiddleware = flowMiddleware;
|
|
680
695
|
this.taskStartTimes = /* @__PURE__ */ new Map();
|
|
681
696
|
this.logger = new client.Logger("TaskRunner", client.LogLevel.INFO);
|
|
682
697
|
this.lockManager = new LockManager(cacheProvider, {
|
|
@@ -684,6 +699,29 @@ class TaskRunner {
|
|
|
684
699
|
defaultTimeout: 30 * 60
|
|
685
700
|
});
|
|
686
701
|
}
|
|
702
|
+
// ============ Log Context Helpers (RFC-005) ============
|
|
703
|
+
/**
|
|
704
|
+
* Build ALS log store for a single task execution.
|
|
705
|
+
* Runtime keys (task_id, task_type, worker_id) override user-supplied log_context.
|
|
706
|
+
*/
|
|
707
|
+
buildLogStore(task, workerId) {
|
|
708
|
+
return {
|
|
709
|
+
...task.metadata?.log_context || {},
|
|
710
|
+
task_id: task.id?.toString() || utils_taskIdGen.tId(task),
|
|
711
|
+
task_type: task.type,
|
|
712
|
+
worker_id: workerId
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Build ALS log store for a multi-task (batch) execution.
|
|
717
|
+
* Only runtime keys — no user log_context (ambiguous across tasks).
|
|
718
|
+
*/
|
|
719
|
+
buildBatchLogStore(tasks, workerId) {
|
|
720
|
+
return {
|
|
721
|
+
worker_id: workerId,
|
|
722
|
+
batch_size: String(tasks.length)
|
|
723
|
+
};
|
|
724
|
+
}
|
|
687
725
|
// ============ Lifecycle Helpers ============
|
|
688
726
|
async run(taskRunnerId, tasksRaw, asyncTaskManager, abortSignal) {
|
|
689
727
|
var _stack = [];
|
|
@@ -692,7 +730,7 @@ class TaskRunner {
|
|
|
692
730
|
this.logger.info(`[${taskRunnerId}] Processing ${tasksRaw.length} provided tasks`);
|
|
693
731
|
if (abortSignal?.aborted) {
|
|
694
732
|
this.logger.info(`[${taskRunnerId}] AbortSignal already aborted, returning empty results`);
|
|
695
|
-
return { successTasks: [], failedTasks: [], newTasks: [], ignoredTasks: [], asyncTasks: [] };
|
|
733
|
+
return { successTasks: [], failedTasks: [], newTasks: [], ignoredTasks: [], asyncTasks: [], flowProjections: [] };
|
|
696
734
|
}
|
|
697
735
|
const tasks = await this.lockManager.filterLocked(tasksRaw, utils_taskIdGen.tId);
|
|
698
736
|
this.logger.info(`[${taskRunnerId}] Found ${tasks.length} not locked tasks to process`);
|
|
@@ -713,6 +751,14 @@ class TaskRunner {
|
|
|
713
751
|
return acc;
|
|
714
752
|
}, []);
|
|
715
753
|
this.logger.info(`[${taskRunnerId}] Task groups: ${groupedTasksArray.map((g) => `${g.type}: ${g.tasks.length}`).join(", ")}`);
|
|
754
|
+
if (this.entityProjection) {
|
|
755
|
+
try {
|
|
756
|
+
const processingProjections = tasks.filter((t) => t.entity && !t.execution_stats?.retry_count).map((t) => core_async_AsyncActions.buildProjection(t, "processing", { includePayload: this.entityProjectionConfig?.includePayload })).filter((p) => p !== null);
|
|
757
|
+
await core_async_AsyncActions.syncProjections(processingProjections, this.entityProjection, this.logger);
|
|
758
|
+
} catch (err) {
|
|
759
|
+
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
716
762
|
const actions = new core_Actions.Actions(taskRunnerId);
|
|
717
763
|
const asyncTasks = [];
|
|
718
764
|
const processedTaskIds = /* @__PURE__ */ new Set();
|
|
@@ -749,7 +795,18 @@ class TaskRunner {
|
|
|
749
795
|
taskGroup.tasks.forEach((task) => processedTaskIds.add(utils_taskIdGen.tId(task)));
|
|
750
796
|
this.logger.info(`[${taskRunnerId}] Processing ${taskGroup.tasks.length} tasks of type: ${taskGroup.type}`);
|
|
751
797
|
if (executor.multiple) {
|
|
752
|
-
|
|
798
|
+
const batchStore = this.buildBatchLogStore(taskGroup.tasks, taskRunnerId);
|
|
799
|
+
await runWithLogContext(
|
|
800
|
+
batchStore,
|
|
801
|
+
() => executor.onTasks(taskGroup.tasks, actions).catch((err) => {
|
|
802
|
+
this.logger.error(`[${taskRunnerId}] executor.onTasks failed: ${err}`);
|
|
803
|
+
for (const task of taskGroup.tasks) {
|
|
804
|
+
if (actions.getTaskResultStatus(utils_taskIdGen.tId(task)) === "pending") {
|
|
805
|
+
actions.fail(task, err instanceof Error ? err : new Error(String(err)));
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
})
|
|
809
|
+
);
|
|
753
810
|
} else {
|
|
754
811
|
if (executor.parallel) {
|
|
755
812
|
const chunks = chunk(taskGroup.tasks, executor.chunkSize);
|
|
@@ -760,14 +817,24 @@ class TaskRunner {
|
|
|
760
817
|
}
|
|
761
818
|
const chunkPromises = [];
|
|
762
819
|
for (let j = 0; j < taskChunk.length; j++) {
|
|
763
|
-
const
|
|
764
|
-
|
|
820
|
+
const task = taskChunk[j];
|
|
821
|
+
const taskActions = actions.forkForTask(task);
|
|
822
|
+
const logStore = this.buildLogStore(task, taskRunnerId);
|
|
823
|
+
chunkPromises.push(runWithLogContext(
|
|
824
|
+
logStore,
|
|
825
|
+
() => executor.onTask(task, taskActions).catch((err) => {
|
|
826
|
+
this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`);
|
|
827
|
+
if (actions.getTaskResultStatus(utils_taskIdGen.tId(task)) === "pending") {
|
|
828
|
+
actions.fail(task, err instanceof Error ? err : new Error(String(err)));
|
|
829
|
+
}
|
|
830
|
+
})
|
|
831
|
+
));
|
|
765
832
|
}
|
|
766
833
|
await Promise.all(chunkPromises);
|
|
767
834
|
for (const task of taskChunk) {
|
|
768
835
|
const resultStatus = actions.getTaskResultStatus(utils_taskIdGen.tId(task));
|
|
769
836
|
if (resultStatus === "success") {
|
|
770
|
-
this.emitTaskCompleted(task, taskRunnerId);
|
|
837
|
+
this.emitTaskCompleted(task, taskRunnerId, actions.getTaskResult(utils_taskIdGen.tId(task)));
|
|
771
838
|
} else if (resultStatus === "fail") {
|
|
772
839
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
773
840
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
@@ -783,10 +850,19 @@ class TaskRunner {
|
|
|
783
850
|
if (!timeoutMs) {
|
|
784
851
|
this.emitTaskStarted(task, taskRunnerId);
|
|
785
852
|
const taskActions = actions.forkForTask(task);
|
|
786
|
-
|
|
853
|
+
const logStore = this.buildLogStore(task, taskRunnerId);
|
|
854
|
+
await runWithLogContext(
|
|
855
|
+
logStore,
|
|
856
|
+
() => executor.onTask(task, taskActions).catch((err) => {
|
|
857
|
+
this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`);
|
|
858
|
+
if (actions.getTaskResultStatus(utils_taskIdGen.tId(task)) === "pending") {
|
|
859
|
+
actions.fail(task, err instanceof Error ? err : new Error(String(err)));
|
|
860
|
+
}
|
|
861
|
+
})
|
|
862
|
+
);
|
|
787
863
|
const resultStatus = actions.getTaskResultStatus(utils_taskIdGen.tId(task));
|
|
788
864
|
if (resultStatus === "success") {
|
|
789
|
-
this.emitTaskCompleted(task, taskRunnerId);
|
|
865
|
+
this.emitTaskCompleted(task, taskRunnerId, actions.getTaskResult(utils_taskIdGen.tId(task)));
|
|
790
866
|
} else if (resultStatus === "fail") {
|
|
791
867
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
792
868
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
@@ -797,9 +873,16 @@ class TaskRunner {
|
|
|
797
873
|
this.emitTaskStarted(task, taskRunnerId);
|
|
798
874
|
const startTime = Date.now();
|
|
799
875
|
const taskActions = actions.forkForTask(task);
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
876
|
+
const logStore = this.buildLogStore(task, taskRunnerId);
|
|
877
|
+
const taskPromise = runWithLogContext(
|
|
878
|
+
logStore,
|
|
879
|
+
() => executor.onTask(task, taskActions).catch((err) => {
|
|
880
|
+
this.logger.error(`[${taskRunnerId}] executor.onTask failed: ${err}`);
|
|
881
|
+
if (actions.getTaskResultStatus(utils_taskIdGen.tId(task)) === "pending") {
|
|
882
|
+
actions.fail(task, err instanceof Error ? err : new Error(String(err)));
|
|
883
|
+
}
|
|
884
|
+
})
|
|
885
|
+
);
|
|
803
886
|
let timeoutId;
|
|
804
887
|
const timeoutPromise = new Promise((resolve) => {
|
|
805
888
|
timeoutId = setTimeout(() => {
|
|
@@ -813,7 +896,7 @@ class TaskRunner {
|
|
|
813
896
|
if (result !== "~~~timeout") {
|
|
814
897
|
const resultStatus = actions.getTaskResultStatus(utils_taskIdGen.tId(task));
|
|
815
898
|
if (resultStatus === "success") {
|
|
816
|
-
this.emitTaskCompleted(task, taskRunnerId);
|
|
899
|
+
this.emitTaskCompleted(task, taskRunnerId, actions.getTaskResult(utils_taskIdGen.tId(task)));
|
|
817
900
|
} else if (resultStatus === "fail") {
|
|
818
901
|
const retryCount = task.execution_stats?.retry_count || 0;
|
|
819
902
|
const maxRetries = task.retries ?? executor.default_retries ?? 0;
|
|
@@ -829,7 +912,15 @@ class TaskRunner {
|
|
|
829
912
|
if (!task.id) {
|
|
830
913
|
this.logger.error(`[${taskRunnerId}] Cannot hand off task without id (type: ${task.type}). Task will continue but won't be tracked.`);
|
|
831
914
|
} else {
|
|
832
|
-
const
|
|
915
|
+
const asyncLifecycleEmitter = this.lifecycleProvider ? {
|
|
916
|
+
onCompleted: (t, result2) => {
|
|
917
|
+
this.emitTaskCompleted(t, taskRunnerId, result2);
|
|
918
|
+
},
|
|
919
|
+
onFailed: (t, error, willRetry) => {
|
|
920
|
+
this.emitTaskFailed(t, taskRunnerId, error, willRetry);
|
|
921
|
+
}
|
|
922
|
+
} : void 0;
|
|
923
|
+
const asyncActions = new core_async_AsyncActions.AsyncActions(this.messageQueue, this.taskStore, this.taskQueue, actions, task, this.generateId, asyncLifecycleEmitter, this.entityProjection, this.entityProjectionConfig, this.flowMiddleware);
|
|
833
924
|
const asyncPromise = taskPromise.finally(async () => {
|
|
834
925
|
try {
|
|
835
926
|
await asyncActions.onPromiseFulfilled();
|
|
@@ -888,7 +979,8 @@ class TaskRunner {
|
|
|
888
979
|
attempt: retryCount + 1,
|
|
889
980
|
max_retries: maxRetries,
|
|
890
981
|
scheduled_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
891
|
-
worker_id: workerId
|
|
982
|
+
worker_id: workerId,
|
|
983
|
+
log_context: task.metadata?.log_context
|
|
892
984
|
};
|
|
893
985
|
}
|
|
894
986
|
emitTaskStarted(task, workerId) {
|
|
@@ -985,7 +1077,10 @@ class TaskHandler {
|
|
|
985
1077
|
this.cacheAdapter,
|
|
986
1078
|
databaseAdapter.generateId.bind(databaseAdapter),
|
|
987
1079
|
this.config.lifecycleProvider,
|
|
988
|
-
this.config.lifecycle
|
|
1080
|
+
this.config.lifecycle,
|
|
1081
|
+
this.config.entityProjection,
|
|
1082
|
+
this.config.entityProjectionConfig,
|
|
1083
|
+
this.config.flowMiddleware
|
|
989
1084
|
);
|
|
990
1085
|
}
|
|
991
1086
|
// ============ Lifecycle Event Helpers ============
|
|
@@ -995,7 +1090,38 @@ class TaskHandler {
|
|
|
995
1090
|
get workerProvider() {
|
|
996
1091
|
return this.config.workerProvider;
|
|
997
1092
|
}
|
|
1093
|
+
get entityProjectionProvider() {
|
|
1094
|
+
return this.config.entityProjection;
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Validate and sanitize log_context on a task (RFC-005).
|
|
1098
|
+
* - >10 keys: truncate to first 10 alphabetically, warn
|
|
1099
|
+
* - >1024 bytes total: drop entire context, warn
|
|
1100
|
+
*/
|
|
1101
|
+
validateLogContext(task) {
|
|
1102
|
+
const logCtx = task.metadata?.log_context;
|
|
1103
|
+
if (!logCtx) return task;
|
|
1104
|
+
let validatedCtx = logCtx;
|
|
1105
|
+
const keys = Object.keys(logCtx).sort();
|
|
1106
|
+
if (keys.length > 10) {
|
|
1107
|
+
this.logger.warn(`[TQ] log_context has ${keys.length} keys (max 10), truncating for task type ${task.type}`);
|
|
1108
|
+
const truncated = {};
|
|
1109
|
+
for (let i = 0; i < 10; i++) truncated[keys[i]] = logCtx[keys[i]];
|
|
1110
|
+
validatedCtx = truncated;
|
|
1111
|
+
}
|
|
1112
|
+
const serialized = JSON.stringify(validatedCtx);
|
|
1113
|
+
if (serialized.length > 1024) {
|
|
1114
|
+
this.logger.warn(`[TQ] log_context exceeds 1KB (${serialized.length} chars), dropping for task type ${task.type}`);
|
|
1115
|
+
const { log_context: _, ...restMeta } = task.metadata;
|
|
1116
|
+
return { ...task, metadata: Object.keys(restMeta).length > 0 ? restMeta : void 0 };
|
|
1117
|
+
}
|
|
1118
|
+
if (validatedCtx !== logCtx) {
|
|
1119
|
+
return { ...task, metadata: { ...task.metadata, log_context: validatedCtx } };
|
|
1120
|
+
}
|
|
1121
|
+
return task;
|
|
1122
|
+
}
|
|
998
1123
|
async addTasks(tasks) {
|
|
1124
|
+
tasks = tasks.map((t) => this.validateLogContext(t));
|
|
999
1125
|
const diffedItems = tasks.reduce(
|
|
1000
1126
|
(acc, { force_store, ...task }) => {
|
|
1001
1127
|
const currentTime = /* @__PURE__ */ new Date();
|
|
@@ -1027,8 +1153,18 @@ class TaskHandler {
|
|
|
1027
1153
|
const executor = this.taskQueuesManager.getExecutor(task.queue_id, task.type);
|
|
1028
1154
|
const shouldStoreOnFailure = executor?.store_on_failure ?? false;
|
|
1029
1155
|
const id = shouldStoreOnFailure ? { id: this.databaseAdapter.generateId() } : {};
|
|
1030
|
-
|
|
1156
|
+
const partitionKey = executor?.getPartitionKey?.(task);
|
|
1157
|
+
return { ...id, ...task, ...partitionKey ? { partition_key: partitionKey } : {} };
|
|
1031
1158
|
});
|
|
1159
|
+
if (this.entityProjectionProvider) {
|
|
1160
|
+
try {
|
|
1161
|
+
const includePayload = this.config.entityProjectionConfig?.includePayload;
|
|
1162
|
+
const entityProjections = queueTasks.filter((t) => t.entity).map((t) => core_async_AsyncActions.buildProjection(t, "scheduled", { includePayload })).filter((p) => p !== null);
|
|
1163
|
+
await core_async_AsyncActions.syncProjections(entityProjections, this.entityProjectionProvider, this.logger);
|
|
1164
|
+
} catch (err) {
|
|
1165
|
+
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1032
1168
|
await this.messageQueue.addMessages(queue, queueTasks);
|
|
1033
1169
|
if (this.lifecycleProvider?.onTaskScheduled) {
|
|
1034
1170
|
for (const task of queueTasks) {
|
|
@@ -1047,8 +1183,18 @@ class TaskHandler {
|
|
|
1047
1183
|
return { ...id, ...task, status: "processing", processing_started_at: /* @__PURE__ */ new Date() };
|
|
1048
1184
|
});
|
|
1049
1185
|
await this.taskStore.addTasksToScheduled(queueTasks);
|
|
1186
|
+
if (this.entityProjectionProvider) {
|
|
1187
|
+
try {
|
|
1188
|
+
const includePayload = this.config.entityProjectionConfig?.includePayload;
|
|
1189
|
+
const entityProjections = queueTasks.filter((t) => t.entity).map((t) => core_async_AsyncActions.buildProjection(t, "scheduled", { includePayload })).filter((p) => p !== null);
|
|
1190
|
+
await core_async_AsyncActions.syncProjections(entityProjections, this.entityProjectionProvider, this.logger);
|
|
1191
|
+
} catch (err) {
|
|
1192
|
+
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
const mqTasks = queueTasks.map((t) => ({ ...t, status: "scheduled" }));
|
|
1050
1196
|
try {
|
|
1051
|
-
await this.messageQueue.addMessages(queue,
|
|
1197
|
+
await this.messageQueue.addMessages(queue, mqTasks);
|
|
1052
1198
|
} catch (mqError) {
|
|
1053
1199
|
this.logger.error(`MQ write failed for forceStoreImmediate tasks, resetting to scheduled: ${mqError}`);
|
|
1054
1200
|
const taskIds = queueTasks.map((t) => t.id).filter(Boolean);
|
|
@@ -1080,6 +1226,15 @@ class TaskHandler {
|
|
|
1080
1226
|
return { ...id, ...task };
|
|
1081
1227
|
});
|
|
1082
1228
|
await this.taskStore.addTasksToScheduled(queueTasks);
|
|
1229
|
+
if (this.entityProjectionProvider) {
|
|
1230
|
+
try {
|
|
1231
|
+
const includePayload = this.config.entityProjectionConfig?.includePayload;
|
|
1232
|
+
const entityProjections = queueTasks.filter((t) => t.entity).map((t) => core_async_AsyncActions.buildProjection(t, "scheduled", { includePayload })).filter((p) => p !== null);
|
|
1233
|
+
await core_async_AsyncActions.syncProjections(entityProjections, this.entityProjectionProvider, this.logger);
|
|
1234
|
+
} catch (err) {
|
|
1235
|
+
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1083
1238
|
if (this.lifecycleProvider?.onTaskScheduled) {
|
|
1084
1239
|
for (const task of queueTasks) {
|
|
1085
1240
|
this.emitLifecycleEvent(
|
|
@@ -1090,6 +1245,9 @@ class TaskHandler {
|
|
|
1090
1245
|
}
|
|
1091
1246
|
}
|
|
1092
1247
|
}
|
|
1248
|
+
// TODO(P2): Wrap retry upsert → new tasks → mark failed → mark success in a
|
|
1249
|
+
// transaction. If an intermediate step fails, tasks can be lost or re-executed
|
|
1250
|
+
// after 2-day stale recovery. Reorder to mark success first as a quick win.
|
|
1093
1251
|
async postProcessTasks({
|
|
1094
1252
|
failedTasks: failedTasksRaw,
|
|
1095
1253
|
newTasks,
|
|
@@ -1097,11 +1255,11 @@ class TaskHandler {
|
|
|
1097
1255
|
}) {
|
|
1098
1256
|
const tasksToRetry = [];
|
|
1099
1257
|
const finalFailedTasks = [];
|
|
1100
|
-
|
|
1258
|
+
const discardedTasks = [];
|
|
1101
1259
|
const MAX_RETRY_DELAY_MS = 5 * 60 * 1e3;
|
|
1102
1260
|
for (const task of failedTasksRaw) {
|
|
1103
1261
|
const taskRetryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
|
|
1104
|
-
const taskRetryAfter = task.retry_after || 2e3;
|
|
1262
|
+
const taskRetryAfter = Math.max(task.retry_after || 2e3, 0);
|
|
1105
1263
|
const calculatedDelay = taskRetryAfter * Math.pow(taskRetryCount + 1, 2);
|
|
1106
1264
|
const retryAfter = Math.min(calculatedDelay, MAX_RETRY_DELAY_MS);
|
|
1107
1265
|
const executeAt = Date.now() + retryAfter;
|
|
@@ -1143,7 +1301,7 @@ class TaskHandler {
|
|
|
1143
1301
|
}]);
|
|
1144
1302
|
}
|
|
1145
1303
|
} else {
|
|
1146
|
-
|
|
1304
|
+
discardedTasks.push(task);
|
|
1147
1305
|
this.logger.info(`Discarding task of type ${task.type} after ${taskRetryCount} retries`);
|
|
1148
1306
|
if (this.lifecycleProvider?.onTaskExhausted) {
|
|
1149
1307
|
const ctx = this.buildTaskContext(task);
|
|
@@ -1164,14 +1322,29 @@ class TaskHandler {
|
|
|
1164
1322
|
}
|
|
1165
1323
|
}
|
|
1166
1324
|
}
|
|
1167
|
-
if (
|
|
1168
|
-
await this.trackDiscardedTasks(
|
|
1325
|
+
if (discardedTasks.length > 0) {
|
|
1326
|
+
await this.trackDiscardedTasks(discardedTasks.length);
|
|
1169
1327
|
}
|
|
1170
1328
|
if (tasksToRetry.length > 0) {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1329
|
+
const dbRetryTasks = tasksToRetry.map((t) => ({
|
|
1330
|
+
...t,
|
|
1331
|
+
status: "processing",
|
|
1332
|
+
processing_started_at: /* @__PURE__ */ new Date()
|
|
1333
|
+
}));
|
|
1334
|
+
await this.taskStore.updateTasksForRetry(dbRetryTasks);
|
|
1335
|
+
const retryByQueue = /* @__PURE__ */ new Map();
|
|
1336
|
+
for (const task of tasksToRetry) {
|
|
1337
|
+
const queue = task.queue_id;
|
|
1338
|
+
if (!retryByQueue.has(queue)) retryByQueue.set(queue, []);
|
|
1339
|
+
retryByQueue.get(queue).push(task);
|
|
1340
|
+
}
|
|
1341
|
+
for (const [queue, retryQueueTasks] of retryByQueue) {
|
|
1342
|
+
try {
|
|
1343
|
+
await this.messageQueue.addMessages(queue, retryQueueTasks);
|
|
1344
|
+
} catch (mqErr) {
|
|
1345
|
+
this.logger.error(`[TQ] Failed to re-enqueue retry tasks to MQ (stale recovery will handle): ${mqErr}`);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1175
1348
|
}
|
|
1176
1349
|
if (finalFailedTasks.length > 0) {
|
|
1177
1350
|
await this.taskStore.markTasksAsFailed(finalFailedTasks);
|
|
@@ -1179,6 +1352,56 @@ class TaskHandler {
|
|
|
1179
1352
|
if (successTasks.length > 0) {
|
|
1180
1353
|
await this.taskStore.markTasksAsSuccess(successTasks);
|
|
1181
1354
|
}
|
|
1355
|
+
if (this.entityProjectionProvider) {
|
|
1356
|
+
try {
|
|
1357
|
+
const includePayload = this.config.entityProjectionConfig?.includePayload;
|
|
1358
|
+
const terminalProjections = [];
|
|
1359
|
+
for (const task of successTasks) {
|
|
1360
|
+
const p = core_async_AsyncActions.buildProjection(task, "executed", {
|
|
1361
|
+
includePayload,
|
|
1362
|
+
result: task.execution_result
|
|
1363
|
+
});
|
|
1364
|
+
if (p) terminalProjections.push(p);
|
|
1365
|
+
}
|
|
1366
|
+
for (const task of finalFailedTasks) {
|
|
1367
|
+
const p = core_async_AsyncActions.buildProjection(task, "failed", {
|
|
1368
|
+
includePayload,
|
|
1369
|
+
error: task.execution_stats?.last_error || "Task failed"
|
|
1370
|
+
});
|
|
1371
|
+
if (p) terminalProjections.push(p);
|
|
1372
|
+
}
|
|
1373
|
+
for (const task of discardedTasks) {
|
|
1374
|
+
try {
|
|
1375
|
+
const p = core_async_AsyncActions.buildProjection(task, "failed", {
|
|
1376
|
+
includePayload,
|
|
1377
|
+
error: task.execution_stats?.last_error || "Task exhausted all retries"
|
|
1378
|
+
});
|
|
1379
|
+
if (p) terminalProjections.push(p);
|
|
1380
|
+
} catch (projErr) {
|
|
1381
|
+
this.logger.error(`[TQ] Entity projection build failed (non-fatal): ${projErr}`);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
await core_async_AsyncActions.syncProjections(terminalProjections, this.entityProjectionProvider, this.logger);
|
|
1385
|
+
} catch (err) {
|
|
1386
|
+
this.logger.error(`[TQ] Entity projection failed (non-fatal): ${err}`);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
if (this.config.flowMiddleware) {
|
|
1390
|
+
try {
|
|
1391
|
+
const flowResult = await this.config.flowMiddleware.onPostProcess({ successTasks, failedTasks: finalFailedTasks });
|
|
1392
|
+
if (flowResult.projections.length > 0 && this.entityProjectionProvider) {
|
|
1393
|
+
await core_async_AsyncActions.syncProjections(flowResult.projections, this.entityProjectionProvider, this.logger);
|
|
1394
|
+
}
|
|
1395
|
+
if (flowResult.joinTasks.length > 0) {
|
|
1396
|
+
await this.addTasks(flowResult.joinTasks);
|
|
1397
|
+
}
|
|
1398
|
+
} catch (err) {
|
|
1399
|
+
this.logger.error(`[TQ] Flow middleware failed (non-fatal): ${err}`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (newTasks.length > 0) {
|
|
1403
|
+
await this.addTasks(newTasks);
|
|
1404
|
+
}
|
|
1182
1405
|
}
|
|
1183
1406
|
startConsumingTasks(streamName, abortSignal) {
|
|
1184
1407
|
return this.messageQueue.consumeMessagesStream(streamName, async (id, tasks) => {
|
|
@@ -1204,10 +1427,18 @@ class TaskHandler {
|
|
|
1204
1427
|
newTasks,
|
|
1205
1428
|
successTasks,
|
|
1206
1429
|
asyncTasks,
|
|
1207
|
-
ignoredTasks
|
|
1430
|
+
ignoredTasks,
|
|
1431
|
+
flowProjections
|
|
1208
1432
|
} = await this.taskRunner.run(id, tasks, this.asyncTaskManager, abortSignal).catch((err) => {
|
|
1209
|
-
this.logger.error("Failed to execute tasks
|
|
1210
|
-
return {
|
|
1433
|
+
this.logger.error("Failed to execute tasks, returning all as failed for retry:", err);
|
|
1434
|
+
return {
|
|
1435
|
+
failedTasks: tasks,
|
|
1436
|
+
newTasks: [],
|
|
1437
|
+
successTasks: [],
|
|
1438
|
+
asyncTasks: [],
|
|
1439
|
+
ignoredTasks: [],
|
|
1440
|
+
flowProjections: []
|
|
1441
|
+
};
|
|
1211
1442
|
});
|
|
1212
1443
|
if (asyncTasks.length > 0 && !this.asyncTaskManager) {
|
|
1213
1444
|
throw new Error("Async tasks detected but AsyncTaskManager not initialized!");
|
|
@@ -1215,7 +1446,9 @@ class TaskHandler {
|
|
|
1215
1446
|
if (asyncTasks.length > 0) {
|
|
1216
1447
|
this.logger.info(`Handling ${asyncTasks.length} async tasks for stream ${streamName}`);
|
|
1217
1448
|
for (const asyncTask of asyncTasks) {
|
|
1218
|
-
const
|
|
1449
|
+
const executor = this.taskQueuesManager.getExecutor(asyncTask.task.queue_id, asyncTask.task.type);
|
|
1450
|
+
const taskTimeout = executor?.asyncConfig?.handoffTimeout ? executor.asyncConfig.handoffTimeout * 2 : void 0;
|
|
1451
|
+
const accepted = this.asyncTaskManager.handoffTask(asyncTask.task, asyncTask.promise, taskTimeout);
|
|
1219
1452
|
if (!accepted) {
|
|
1220
1453
|
this.logger.warn(`Async queue full, requeueing task ${asyncTask.task.id} with 30s delay`);
|
|
1221
1454
|
await this.addTasks([{
|
|
@@ -1231,6 +1464,9 @@ class TaskHandler {
|
|
|
1231
1464
|
this.logger.error("Failed to mark tasks as ignored", err);
|
|
1232
1465
|
});
|
|
1233
1466
|
}
|
|
1467
|
+
if (this.entityProjectionProvider && flowProjections?.length > 0) {
|
|
1468
|
+
await core_async_AsyncActions.syncProjections(flowProjections, this.entityProjectionProvider, this.logger);
|
|
1469
|
+
}
|
|
1234
1470
|
await this.postProcessTasks({ failedTasks, newTasks, successTasks }).catch((err) => {
|
|
1235
1471
|
this.logger.error("Failed to postProcessTasks", err);
|
|
1236
1472
|
throw err;
|
|
@@ -1438,7 +1674,8 @@ class TaskHandler {
|
|
|
1438
1674
|
payload: this.config.lifecycle?.include_payload ? payload : {},
|
|
1439
1675
|
attempt: retryCount + 1,
|
|
1440
1676
|
max_retries: maxRetries,
|
|
1441
|
-
scheduled_at: task.created_at || /* @__PURE__ */ new Date()
|
|
1677
|
+
scheduled_at: task.created_at || /* @__PURE__ */ new Date(),
|
|
1678
|
+
log_context: task.metadata?.log_context
|
|
1442
1679
|
};
|
|
1443
1680
|
}
|
|
1444
1681
|
processBatch(queueId, processor, limit, abortSignal) {
|
|
@@ -1608,6 +1845,245 @@ class TaskQueuesManager {
|
|
|
1608
1845
|
return Array.from(queueMap.keys());
|
|
1609
1846
|
}
|
|
1610
1847
|
}
|
|
1848
|
+
class InMemoryFlowBarrierProvider {
|
|
1849
|
+
constructor() {
|
|
1850
|
+
this.barriers = /* @__PURE__ */ new Map();
|
|
1851
|
+
}
|
|
1852
|
+
async initBarrier(flowId, totalSteps) {
|
|
1853
|
+
if (this.barriers.has(flowId)) return;
|
|
1854
|
+
this.barriers.set(flowId, {
|
|
1855
|
+
remaining: totalSteps,
|
|
1856
|
+
status: "active",
|
|
1857
|
+
results: /* @__PURE__ */ new Map()
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
async batchDecrementAndCheck(flowId, results) {
|
|
1861
|
+
const state = this.barriers.get(flowId);
|
|
1862
|
+
if (!state) {
|
|
1863
|
+
return { remaining: -1 };
|
|
1864
|
+
}
|
|
1865
|
+
if (state.status === "aborted" || state.status === "complete") {
|
|
1866
|
+
return { remaining: -1 };
|
|
1867
|
+
}
|
|
1868
|
+
let actualNew = 0;
|
|
1869
|
+
for (const result of results) {
|
|
1870
|
+
if (!state.results.has(result.step_index)) {
|
|
1871
|
+
state.results.set(result.step_index, result);
|
|
1872
|
+
actualNew++;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
state.remaining -= actualNew;
|
|
1876
|
+
if (state.remaining <= 0) {
|
|
1877
|
+
state.remaining = 0;
|
|
1878
|
+
state.status = "complete";
|
|
1879
|
+
}
|
|
1880
|
+
return { remaining: state.remaining };
|
|
1881
|
+
}
|
|
1882
|
+
async getStepResults(flowId) {
|
|
1883
|
+
const state = this.barriers.get(flowId);
|
|
1884
|
+
if (!state) return [];
|
|
1885
|
+
return Array.from(state.results.values()).sort((a, b) => a.step_index - b.step_index);
|
|
1886
|
+
}
|
|
1887
|
+
async markAborted(flowId) {
|
|
1888
|
+
const state = this.barriers.get(flowId);
|
|
1889
|
+
if (!state) return false;
|
|
1890
|
+
if (state.status === "aborted") {
|
|
1891
|
+
return false;
|
|
1892
|
+
}
|
|
1893
|
+
state.status = "aborted";
|
|
1894
|
+
return true;
|
|
1895
|
+
}
|
|
1896
|
+
async isComplete(flowId) {
|
|
1897
|
+
const state = this.barriers.get(flowId);
|
|
1898
|
+
if (!state) return false;
|
|
1899
|
+
return state.status === "complete";
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
function getFlowMeta(task) {
|
|
1903
|
+
return task.metadata?.flow_meta;
|
|
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
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
1611
2087
|
class ConsoleHealthProvider {
|
|
1612
2088
|
constructor(prefix = "[Health]") {
|
|
1613
2089
|
this.prefix = prefix;
|
|
@@ -1670,11 +2146,17 @@ exports.PrismaAdapter = PrismaAdapter.PrismaAdapter;
|
|
|
1670
2146
|
exports.tId = utils_taskIdGen.tId;
|
|
1671
2147
|
exports.Actions = core_Actions.Actions;
|
|
1672
2148
|
exports.AsyncActions = core_async_AsyncActions.AsyncActions;
|
|
2149
|
+
exports.buildProjection = core_async_AsyncActions.buildProjection;
|
|
2150
|
+
exports.syncProjections = core_async_AsyncActions.syncProjections;
|
|
1673
2151
|
exports.AsyncTaskManager = core_async_AsyncTaskManager.AsyncTaskManager;
|
|
1674
2152
|
exports.ConsoleHealthProvider = ConsoleHealthProvider;
|
|
2153
|
+
exports.FlowMiddleware = FlowMiddleware;
|
|
2154
|
+
exports.InMemoryFlowBarrierProvider = InMemoryFlowBarrierProvider;
|
|
1675
2155
|
exports.TaskHandler = TaskHandler;
|
|
1676
2156
|
exports.TaskQueuesManager = TaskQueuesManager;
|
|
1677
2157
|
exports.TaskRunner = TaskRunner;
|
|
1678
2158
|
exports.TaskStore = TaskStore;
|
|
1679
2159
|
exports.getEnabledQueues = getEnabledQueues;
|
|
2160
|
+
exports.getLogContext = getLogContext;
|
|
2161
|
+
exports.runWithLogContext = runWithLogContext;
|
|
1680
2162
|
//# sourceMappingURL=index.cjs.map
|