@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.
Files changed (100) hide show
  1. package/README.md +338 -20
  2. package/dist/AsyncActions-BOO1ikWz.cjs +241 -0
  3. package/dist/AsyncActions-BOO1ikWz.cjs.map +1 -0
  4. package/dist/AsyncActions-CZYO8ShR.js +242 -0
  5. package/dist/AsyncActions-CZYO8ShR.js.map +1 -0
  6. package/dist/{PrismaAdapter-CvM_XNtE.cjs → PrismaAdapter-CUIWhjms.cjs} +57 -81
  7. package/dist/PrismaAdapter-CUIWhjms.cjs.map +1 -0
  8. package/dist/{PrismaAdapter-Dy7MV090.js → PrismaAdapter-D5ACKPbS.js} +57 -81
  9. package/dist/PrismaAdapter-D5ACKPbS.js.map +1 -0
  10. package/dist/adapters/index.cjs +1 -1
  11. package/dist/adapters/index.mjs +1 -1
  12. package/dist/client-BxG7LzLv.cjs +90 -0
  13. package/dist/client-BxG7LzLv.cjs.map +1 -0
  14. package/dist/client-dvHNt8qU.js +91 -0
  15. package/dist/client-dvHNt8qU.js.map +1 -0
  16. package/dist/core/Actions.cjs +184 -16
  17. package/dist/core/Actions.cjs.map +1 -1
  18. package/dist/core/Actions.mjs +184 -16
  19. package/dist/core/Actions.mjs.map +1 -1
  20. package/dist/core/async/AsyncActions.cjs +4 -98
  21. package/dist/core/async/AsyncActions.cjs.map +1 -1
  22. package/dist/core/async/AsyncActions.mjs +4 -98
  23. package/dist/core/async/AsyncActions.mjs.map +1 -1
  24. package/dist/core/async/AsyncTaskManager.cjs +133 -22
  25. package/dist/core/async/AsyncTaskManager.cjs.map +1 -1
  26. package/dist/core/async/AsyncTaskManager.mjs +133 -22
  27. package/dist/core/async/AsyncTaskManager.mjs.map +1 -1
  28. package/dist/index.cjs +517 -35
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.mjs +517 -35
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/src/adapters/ITaskStorageAdapter.d.cts +0 -5
  33. package/dist/src/adapters/ITaskStorageAdapter.d.ts +0 -5
  34. package/dist/src/adapters/InMemoryAdapter.d.cts +0 -2
  35. package/dist/src/adapters/InMemoryAdapter.d.ts +0 -2
  36. package/dist/src/adapters/MongoDbAdapter.d.cts +0 -2
  37. package/dist/src/adapters/MongoDbAdapter.d.ts +0 -2
  38. package/dist/src/adapters/PrismaAdapter.d.cts +0 -2
  39. package/dist/src/adapters/PrismaAdapter.d.ts +0 -2
  40. package/dist/src/adapters/types.d.cts +7 -0
  41. package/dist/src/adapters/types.d.ts +7 -0
  42. package/dist/src/core/Actions.d.cts +25 -2
  43. package/dist/src/core/Actions.d.ts +25 -2
  44. package/dist/src/core/TaskHandler.d.cts +13 -5
  45. package/dist/src/core/TaskHandler.d.ts +13 -5
  46. package/dist/src/core/TaskRunner.d.cts +16 -1
  47. package/dist/src/core/TaskRunner.d.ts +16 -1
  48. package/dist/src/core/async/AsyncActions.d.cts +20 -1
  49. package/dist/src/core/async/AsyncActions.d.ts +20 -1
  50. package/dist/src/core/async/AsyncTaskManager.d.cts +36 -4
  51. package/dist/src/core/async/AsyncTaskManager.d.ts +36 -4
  52. package/dist/src/core/async/async-task-manager.d.cts +21 -3
  53. package/dist/src/core/async/async-task-manager.d.ts +21 -3
  54. package/dist/src/core/async/retry-utils.d.cts +15 -0
  55. package/dist/src/core/async/retry-utils.d.ts +15 -0
  56. package/dist/src/core/base/interfaces.d.cts +10 -2
  57. package/dist/src/core/base/interfaces.d.ts +10 -2
  58. package/dist/src/core/entity/IEntityProjectionProvider.d.cts +45 -0
  59. package/dist/src/core/entity/IEntityProjectionProvider.d.ts +45 -0
  60. package/dist/src/core/entity/index.d.cts +1 -0
  61. package/dist/src/core/entity/index.d.ts +1 -0
  62. package/dist/src/core/flow/FlowMiddleware.d.cts +26 -0
  63. package/dist/src/core/flow/FlowMiddleware.d.ts +26 -0
  64. package/dist/src/core/flow/IFlowBarrierProvider.d.cts +46 -0
  65. package/dist/src/core/flow/IFlowBarrierProvider.d.ts +46 -0
  66. package/dist/src/core/flow/InMemoryFlowBarrierProvider.d.cts +10 -0
  67. package/dist/src/core/flow/InMemoryFlowBarrierProvider.d.ts +10 -0
  68. package/dist/src/core/flow/index.d.cts +4 -0
  69. package/dist/src/core/flow/index.d.ts +4 -0
  70. package/dist/src/core/flow/types.d.cts +82 -0
  71. package/dist/src/core/flow/types.d.ts +82 -0
  72. package/dist/src/core/lifecycle.d.cts +9 -4
  73. package/dist/src/core/lifecycle.d.ts +9 -4
  74. package/dist/src/core/log-context.d.cts +10 -0
  75. package/dist/src/core/log-context.d.ts +10 -0
  76. package/dist/src/index.d.cts +4 -0
  77. package/dist/src/index.d.ts +4 -0
  78. package/dist/src/test/adapter-consistency.test.d.cts +11 -0
  79. package/dist/src/test/adapter-consistency.test.d.ts +11 -0
  80. package/dist/src/test/immediate-mode-bugs.test.d.cts +11 -0
  81. package/dist/src/test/immediate-mode-bugs.test.d.ts +11 -0
  82. package/dist/src/test/rfc-001-result-persistence.test.d.cts +17 -0
  83. package/dist/src/test/rfc-001-result-persistence.test.d.ts +17 -0
  84. package/dist/src/test/rfc-002-flow-orchestration.test.d.cts +24 -0
  85. package/dist/src/test/rfc-002-flow-orchestration.test.d.ts +24 -0
  86. package/dist/src/test/rfc-003-entity-projection.test.d.cts +14 -0
  87. package/dist/src/test/rfc-003-entity-projection.test.d.ts +14 -0
  88. package/dist/src/test/rfc-004-async-hardening.test.d.cts +14 -0
  89. package/dist/src/test/rfc-004-async-hardening.test.d.ts +14 -0
  90. package/dist/src/test/rfc-005-log-context.test.d.cts +14 -0
  91. package/dist/src/test/rfc-005-log-context.test.d.ts +14 -0
  92. package/dist/src/test/tq-fixes.test.d.cts +17 -0
  93. package/dist/src/test/tq-fixes.test.d.ts +17 -0
  94. package/package.json +2 -2
  95. package/dist/PrismaAdapter-CvM_XNtE.cjs.map +0 -1
  96. package/dist/PrismaAdapter-Dy7MV090.js.map +0 -1
  97. package/dist/client-BAiCkZv7.js +0 -52
  98. package/dist/client-BAiCkZv7.js.map +0 -1
  99. package/dist/client-DgdG7pT6.cjs +0 -51
  100. package/dist/client-DgdG7pT6.cjs.map +0 -1
@@ -0,0 +1,241 @@
1
+ "use strict";
2
+ const client = require("./client-BxG7LzLv.cjs");
3
+ const utils_taskIdGen = require("./utils/task-id-gen.cjs");
4
+ const MAX_RETRY_DELAY_MS = 5 * 60 * 1e3;
5
+ function computeRetryDecision(task, maxRetries) {
6
+ const retryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
7
+ if (retryCount >= maxRetries) {
8
+ return { action: "fail" };
9
+ }
10
+ const taskRetryAfter = Math.max(task.retry_after || 2e3, 0);
11
+ const calculatedDelay = taskRetryAfter * Math.pow(retryCount + 1, 2);
12
+ const retryAfter = Math.min(calculatedDelay, MAX_RETRY_DELAY_MS);
13
+ const executeAt = Date.now() + retryAfter;
14
+ return {
15
+ action: "retry",
16
+ retryTask: {
17
+ ...task,
18
+ status: "scheduled",
19
+ execute_at: new Date(executeAt),
20
+ execution_stats: {
21
+ ...task.execution_stats || {},
22
+ retry_count: retryCount + 1
23
+ }
24
+ }
25
+ };
26
+ }
27
+ async function syncProjections(projections, provider, logger2) {
28
+ if (projections.length === 0 || !provider) return;
29
+ try {
30
+ await provider.upsertProjections(projections);
31
+ } catch (err) {
32
+ logger2.error(`[TQ] Entity projection sync failed (non-fatal): ${err}`);
33
+ }
34
+ }
35
+ function buildProjection(task, status, options) {
36
+ if (!task.entity) return null;
37
+ if (task.id == null) {
38
+ throw new Error(
39
+ `[TQ/RFC-003] Task with entity (${task.entity.type}:${task.entity.id}) has no task ID. Entity-bearing tasks must have an ID for projection keying. Set store_on_failure:true, force_store:true, or assign an ID before addTasks().`
40
+ );
41
+ }
42
+ return {
43
+ task_id: task.id,
44
+ entity_id: task.entity.id,
45
+ entity_type: task.entity.type,
46
+ task_type: task.type,
47
+ queue_id: task.queue_id,
48
+ status,
49
+ payload: options?.includePayload ? task.payload : void 0,
50
+ error: options?.error,
51
+ result: options?.result,
52
+ created_at: task.created_at || /* @__PURE__ */ new Date(),
53
+ updated_at: /* @__PURE__ */ new Date()
54
+ };
55
+ }
56
+ const logger = new client.Logger("AsyncActions", client.LogLevel.INFO);
57
+ class AsyncActions {
58
+ constructor(messageQueue, taskStore, taskQueue, actions, task, generateId, lifecycleEmitter, entityProjection, entityProjectionConfig, flowMiddleware) {
59
+ this.messageQueue = messageQueue;
60
+ this.taskStore = taskStore;
61
+ this.taskQueue = taskQueue;
62
+ this.task = task;
63
+ this.generateId = generateId;
64
+ this.lifecycleEmitter = lifecycleEmitter;
65
+ this.entityProjection = entityProjection;
66
+ this.entityProjectionConfig = entityProjectionConfig;
67
+ this.flowMiddleware = flowMiddleware;
68
+ this.actions = actions;
69
+ this.taskId = utils_taskIdGen.tId(task);
70
+ }
71
+ /**
72
+ * Called when the async promise completes to execute the collected actions
73
+ */
74
+ async onPromiseFulfilled() {
75
+ const results = this.actions.extractTaskActions(this.taskId);
76
+ const hasCompletion = results.successTasks.length > 0 || results.failedTasks.length > 0;
77
+ if (!hasCompletion) {
78
+ logger.warn(`Async task ${this.taskId} completed without calling success() or fail() — defaulting to fail`);
79
+ results.failedTasks.push({
80
+ ...this.task,
81
+ execution_stats: {
82
+ ...this.task.execution_stats || {},
83
+ last_error: `Async task ${this.taskId} completed without calling success() or fail()`
84
+ }
85
+ });
86
+ }
87
+ logger.info(`[AsyncActions] Processing results for async task ${this.taskId}: ${results.successTasks.length} success, ${results.failedTasks.length} failed, ${results.newTasks.length} new tasks`);
88
+ if (results.failedTasks.length > 0) {
89
+ for (const failedTask of results.failedTasks) {
90
+ try {
91
+ await this.processFailedTaskWithRetry(failedTask);
92
+ } catch (err) {
93
+ logger.error(`[AsyncActions] Failed to process failed task:`, err);
94
+ throw err;
95
+ }
96
+ }
97
+ }
98
+ if (results.successTasks.length > 0) {
99
+ try {
100
+ await this.taskStore.markTasksAsSuccess(results.successTasks);
101
+ logger.info(`[AsyncActions] Marked ${results.successTasks.length} tasks as success in database`);
102
+ if (this.lifecycleEmitter) {
103
+ for (const task of results.successTasks) {
104
+ try {
105
+ this.lifecycleEmitter.onCompleted(task, task.execution_result);
106
+ } catch (err) {
107
+ logger.error(`[AsyncActions] Lifecycle onCompleted error:`, err);
108
+ }
109
+ }
110
+ }
111
+ await syncProjections(
112
+ results.successTasks.map((t) => buildProjection(t, "executed", {
113
+ includePayload: this.entityProjectionConfig?.includePayload,
114
+ result: t.execution_result
115
+ })).filter((p) => p !== null),
116
+ this.entityProjection,
117
+ logger
118
+ );
119
+ } catch (err) {
120
+ logger.error(`[AsyncActions] Failed to mark tasks as success:`, err);
121
+ throw err;
122
+ }
123
+ }
124
+ if (this.flowMiddleware) {
125
+ try {
126
+ const finalFailedTasks = [];
127
+ for (const failedTask of results.failedTasks) {
128
+ const executor = this.taskQueue.getExecutor(failedTask.queue_id, failedTask.type);
129
+ const maxRetries = failedTask.retries ?? executor?.default_retries ?? 0;
130
+ const decision = computeRetryDecision(failedTask, maxRetries);
131
+ if (decision.action !== "retry" || !decision.retryTask) {
132
+ finalFailedTasks.push(failedTask);
133
+ }
134
+ }
135
+ if (results.successTasks.length > 0 || finalFailedTasks.length > 0) {
136
+ const flowResult = await this.flowMiddleware.onPostProcess({
137
+ successTasks: results.successTasks,
138
+ failedTasks: finalFailedTasks
139
+ });
140
+ if (flowResult.projections.length > 0 && this.entityProjection) {
141
+ await syncProjections(flowResult.projections, this.entityProjection, logger);
142
+ }
143
+ if (flowResult.joinTasks.length > 0) {
144
+ await this.scheduleNewTasks(flowResult.joinTasks);
145
+ }
146
+ }
147
+ } catch (err) {
148
+ logger.error(`[AsyncActions] Flow middleware failed (non-fatal): ${err}`);
149
+ }
150
+ }
151
+ if (results.newTasks.length > 0) {
152
+ logger.info(`[AsyncActions] Scheduling ${results.newTasks.length} new tasks`);
153
+ await this.scheduleNewTasks(results.newTasks);
154
+ }
155
+ }
156
+ /**
157
+ * Process a failed task through the retry pipeline
158
+ */
159
+ async processFailedTaskWithRetry(failedTask) {
160
+ const executor = this.taskQueue.getExecutor(failedTask.queue_id, failedTask.type);
161
+ const maxRetries = failedTask.retries ?? executor?.default_retries ?? 0;
162
+ const decision = computeRetryDecision(failedTask, maxRetries);
163
+ const willRetry = decision.action === "retry" && !!decision.retryTask;
164
+ if (willRetry) {
165
+ logger.info(`[AsyncActions] Retrying async task ${this.taskId} (attempt ${decision.retryTask.execution_stats?.retry_count || 0})`);
166
+ await this.taskStore.updateTasksForRetry([decision.retryTask]);
167
+ } else {
168
+ logger.info(`[AsyncActions] Async task ${this.taskId} exhausted retries, marking as failed`);
169
+ await this.taskStore.markTasksAsFailed([failedTask]);
170
+ const errorMsg = failedTask.execution_stats?.last_error || "Task failed";
171
+ const p = buildProjection(failedTask, "failed", {
172
+ includePayload: this.entityProjectionConfig?.includePayload,
173
+ error: errorMsg
174
+ });
175
+ if (p) await syncProjections([p], this.entityProjection, logger);
176
+ }
177
+ if (this.lifecycleEmitter) {
178
+ const errorMsg = failedTask.execution_stats?.last_error || "Task failed";
179
+ try {
180
+ this.lifecycleEmitter.onFailed(failedTask, new Error(errorMsg), willRetry);
181
+ } catch (err) {
182
+ logger.error(`[AsyncActions] Lifecycle onFailed error:`, err);
183
+ }
184
+ }
185
+ }
186
+ /**
187
+ * Schedule new tasks - replicates the logic from task-handler's addTasks
188
+ */
189
+ async scheduleNewTasks(tasks) {
190
+ const now = /* @__PURE__ */ new Date();
191
+ const immediate = {};
192
+ const future = [];
193
+ for (const task of tasks) {
194
+ const timeDiff = (task.execute_at.getTime() - now.getTime()) / 1e3 / 60;
195
+ if (timeDiff > 2) {
196
+ future.push(task);
197
+ } else {
198
+ const queue = task.queue_id;
199
+ if (!immediate[queue]) {
200
+ immediate[queue] = [];
201
+ }
202
+ immediate[queue].push(task);
203
+ }
204
+ }
205
+ const iQueues = Object.keys(immediate);
206
+ for (const queue of iQueues) {
207
+ const queueTasks = immediate[queue].map((task) => {
208
+ const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
209
+ const shouldStoreOnFailure = executor?.store_on_failure ?? false;
210
+ const id = shouldStoreOnFailure ? { id: this.generateId() } : {};
211
+ return { ...id, ...task };
212
+ });
213
+ try {
214
+ await this.messageQueue.addMessages(queue, queueTasks);
215
+ logger.info(`[AsyncActions] Added ${queueTasks.length} immediate tasks to queue ${queue}`);
216
+ } catch (err) {
217
+ logger.error(`[AsyncActions] Failed to add tasks to queue ${queue}:`, err);
218
+ throw err;
219
+ }
220
+ }
221
+ if (future.length > 0) {
222
+ const futureTasks = future.map((task) => {
223
+ const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
224
+ const shouldStoreOnFailure = executor?.store_on_failure ?? false;
225
+ const id = shouldStoreOnFailure ? { id: this.generateId() } : {};
226
+ return { ...id, ...task };
227
+ });
228
+ try {
229
+ await this.taskStore.addTasksToScheduled(futureTasks);
230
+ logger.info(`[AsyncActions] Added ${futureTasks.length} future tasks to database`);
231
+ } catch (err) {
232
+ logger.error(`[AsyncActions] Failed to add tasks to database:`, err);
233
+ throw err;
234
+ }
235
+ }
236
+ }
237
+ }
238
+ exports.AsyncActions = AsyncActions;
239
+ exports.buildProjection = buildProjection;
240
+ exports.syncProjections = syncProjections;
241
+ //# sourceMappingURL=AsyncActions-BOO1ikWz.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AsyncActions-BOO1ikWz.cjs","sources":["../src/core/async/retry-utils.ts","../src/core/entity/IEntityProjectionProvider.ts","../src/core/async/AsyncActions.ts"],"sourcesContent":["import {CronTask} from \"../../adapters\";\n\nconst MAX_RETRY_DELAY_MS = 5 * 60 * 1000; // 5 minutes\n\nexport interface RetryDecision<ID = any> {\n action: 'retry' | 'fail';\n retryTask?: CronTask<ID>;\n}\n\n/**\n * Compute whether a failed task should be retried and produce the retry-ready task.\n *\n * Mirrors the logic in TaskHandler.postProcessTasks() but usable from the async path.\n *\n * @param task The failed task\n * @param maxRetries Maximum retry attempts for this task type\n * @returns RetryDecision — either { action: 'retry', retryTask } or { action: 'fail' }\n */\nexport function computeRetryDecision<ID>(\n task: CronTask<ID>,\n maxRetries: number\n): RetryDecision<ID> {\n const retryCount = (task.execution_stats && typeof task.execution_stats.retry_count === 'number')\n ? task.execution_stats.retry_count\n : 0;\n\n if (retryCount >= maxRetries) {\n return {action: 'fail'};\n }\n\n const taskRetryAfter = Math.max(task.retry_after || 2000, 0);\n const calculatedDelay = taskRetryAfter * Math.pow(retryCount + 1, 2);\n const retryAfter = Math.min(calculatedDelay, MAX_RETRY_DELAY_MS);\n const executeAt = Date.now() + retryAfter;\n\n return {\n action: 'retry',\n retryTask: {\n ...task,\n status: 'scheduled' as const,\n execute_at: new Date(executeAt),\n execution_stats: {\n ...(task.execution_stats || {}),\n retry_count: retryCount + 1\n }\n }\n };\n}\n","/**\n * RFC-003: Entity Task Projection\n *\n * Automatic entity-task status projection so dashboards can track\n * task lifecycle without querying the internal tasks table.\n */\n\nimport type {CronTask} from \"../../adapters/types.js\";\n\nexport type EntityTaskProjectionStatus = 'scheduled' | 'processing' | 'executed' | 'failed';\n\nexport interface EntityTaskProjection<ID = any> {\n task_id: ID;\n entity_id: string;\n entity_type: string;\n task_type: string;\n queue_id: string;\n status: EntityTaskProjectionStatus;\n payload?: unknown;\n error?: string;\n result?: unknown;\n created_at: Date;\n updated_at: Date;\n}\n\n/**\n * Provider interface for persisting entity-task projections.\n * Implementations might write to a database table, cache, or external service.\n */\nexport interface IEntityProjectionProvider<ID = any> {\n upsertProjections(entries: EntityTaskProjection<ID>[]): Promise<void>;\n}\n\n/**\n * Configuration for entity projection behavior.\n */\nexport interface EntityProjectionConfig {\n /** Include task payload in projection (default: false for performance) */\n includePayload?: boolean;\n}\n\n/**\n * Sync entity projections to the provider. Non-fatal — logs and continues on error.\n */\nexport async function syncProjections<ID>(\n projections: EntityTaskProjection<ID>[],\n provider: IEntityProjectionProvider<ID> | undefined,\n logger: { error(msg: string): void }\n): Promise<void> {\n if (projections.length === 0 || !provider) return;\n try {\n await provider.upsertProjections(projections);\n } catch (err) {\n logger.error(`[TQ] Entity projection sync failed (non-fatal): ${err}`);\n }\n}\n\n/**\n * Build a single projection entry from a CronTask and target status.\n * Returns null if the task has no entity binding.\n * Throws if entity is present but task has no ID — fail-fast for developer errors.\n */\nexport function buildProjection<ID>(\n task: CronTask<ID>,\n status: EntityTaskProjectionStatus,\n options?: {\n includePayload?: boolean;\n error?: string;\n result?: unknown;\n }\n): EntityTaskProjection<ID> | null {\n if (!task.entity) return null;\n\n if (task.id == null) {\n throw new Error(\n `[TQ/RFC-003] Task with entity (${task.entity.type}:${task.entity.id}) has no task ID. ` +\n `Entity-bearing tasks must have an ID for projection keying. ` +\n `Set store_on_failure:true, force_store:true, or assign an ID before addTasks().`\n );\n }\n\n return {\n task_id: task.id,\n entity_id: task.entity.id,\n entity_type: task.entity.type,\n task_type: task.type,\n queue_id: task.queue_id,\n status,\n payload: options?.includePayload ? task.payload : undefined,\n error: options?.error,\n result: options?.result,\n created_at: task.created_at || new Date(),\n updated_at: new Date(),\n };\n}","import {Logger, LogLevel} from \"@supergrowthai/utils\";\nimport {TaskStore} from \"../TaskStore.js\";\nimport {Actions} from \"../Actions.js\";\nimport {IMessageQueue, QueueName} from \"@supergrowthai/mq\";\nimport {tId} from \"../../utils/task-id-gen.js\";\nimport {CronTask} from \"../../adapters\";\nimport {TaskQueuesManager} from \"../TaskQueuesManager.js\";\nimport {computeRetryDecision} from \"./retry-utils.js\";\nimport type {IEntityProjectionProvider, EntityProjectionConfig, EntityTaskProjection} from \"../entity/IEntityProjectionProvider.js\";\nimport {buildProjection, syncProjections} from \"../entity/IEntityProjectionProvider.js\";\nimport type {FlowMiddleware} from \"../flow/FlowMiddleware.js\";\n\nconst logger = new Logger('AsyncActions', LogLevel.INFO);\n\n/**\n * Interface for emitting async task lifecycle events.\n * Constructed from the sync-path's ITaskLifecycleProvider by TaskRunner.\n */\nexport interface AsyncLifecycleEmitter {\n onCompleted(task: CronTask<any>, result?: unknown): void;\n onFailed(task: CronTask<any>, error: Error, willRetry: boolean): void;\n}\n\nexport class AsyncActions<ID = any> {\n private readonly actions: Actions<ID>;\n private readonly taskId: string;\n\n constructor(\n private messageQueue: IMessageQueue<ID>,\n private taskStore: TaskStore<ID>,\n private taskQueue: TaskQueuesManager<ID>,\n actions: Actions<ID>,\n private task: CronTask<ID>,\n private generateId: () => ID,\n private lifecycleEmitter?: AsyncLifecycleEmitter,\n private entityProjection?: IEntityProjectionProvider<ID>,\n private entityProjectionConfig?: EntityProjectionConfig,\n private flowMiddleware?: FlowMiddleware<ID>\n ) {\n this.actions = actions;\n this.taskId = tId(task);\n }\n\n /**\n * Called when the async promise completes to execute the collected actions\n */\n async onPromiseFulfilled(): Promise<void> {\n // Extract this task's results (NO batch context for async tasks)\n const results = this.actions.extractTaskActions(this.taskId);\n\n // If task didn't call success or fail, default to fail (forgetful executor)\n const hasCompletion = results.successTasks.length > 0 || results.failedTasks.length > 0;\n if (!hasCompletion) {\n logger.warn(`Async task ${this.taskId} completed without calling success() or fail() — defaulting to fail`);\n results.failedTasks.push({\n ...this.task,\n execution_stats: {\n ...(this.task.execution_stats || {}),\n last_error: `Async task ${this.taskId} completed without calling success() or fail()`,\n }\n });\n }\n\n logger.info(`[AsyncActions] Processing results for async task ${this.taskId}: ` +\n `${results.successTasks.length} success, ${results.failedTasks.length} failed, ` +\n `${results.newTasks.length} new tasks`);\n\n // Process failed tasks with retry logic\n if (results.failedTasks.length > 0) {\n for (const failedTask of results.failedTasks) {\n try {\n await this.processFailedTaskWithRetry(failedTask);\n } catch (err) {\n logger.error(`[AsyncActions] Failed to process failed task:`, err);\n throw err;\n }\n }\n }\n\n if (results.successTasks.length > 0) {\n try {\n await this.taskStore.markTasksAsSuccess(results.successTasks);\n logger.info(`[AsyncActions] Marked ${results.successTasks.length} tasks as success in database`);\n\n // Emit lifecycle event for each success\n if (this.lifecycleEmitter) {\n for (const task of results.successTasks) {\n try {\n this.lifecycleEmitter.onCompleted(task, task.execution_result);\n } catch (err) {\n logger.error(`[AsyncActions] Lifecycle onCompleted error:`, err);\n }\n }\n }\n\n // RFC-003: Emit 'executed' entity projections for async success tasks\n await syncProjections(\n results.successTasks\n .map(t => buildProjection(t, 'executed', {\n includePayload: this.entityProjectionConfig?.includePayload,\n result: t.execution_result,\n }))\n .filter((p): p is EntityTaskProjection<ID> => p !== null),\n this.entityProjection,\n logger\n );\n } catch (err) {\n logger.error(`[AsyncActions] Failed to mark tasks as success:`, err);\n throw err;\n }\n }\n\n // RFC-002: Flow middleware — process terminal tasks for barrier tracking and join dispatch\n if (this.flowMiddleware) {\n try {\n // Collect final failures (not retries) for flow middleware\n const finalFailedTasks: CronTask<ID>[] = [];\n for (const failedTask of results.failedTasks) {\n const executor = this.taskQueue.getExecutor(failedTask.queue_id, failedTask.type);\n const maxRetries = failedTask.retries ?? executor?.default_retries ?? 0;\n const decision = computeRetryDecision(failedTask, maxRetries);\n // Only include final failures (no more retries)\n if (decision.action !== 'retry' || !decision.retryTask) {\n finalFailedTasks.push(failedTask);\n }\n }\n\n if (results.successTasks.length > 0 || finalFailedTasks.length > 0) {\n const flowResult = await this.flowMiddleware.onPostProcess({\n successTasks: results.successTasks,\n failedTasks: finalFailedTasks,\n });\n\n if (flowResult.projections.length > 0 && this.entityProjection) {\n await syncProjections(flowResult.projections, this.entityProjection, logger);\n }\n\n if (flowResult.joinTasks.length > 0) {\n await this.scheduleNewTasks(flowResult.joinTasks);\n }\n }\n } catch (err) {\n logger.error(`[AsyncActions] Flow middleware failed (non-fatal): ${err}`);\n }\n }\n\n if (results.newTasks.length > 0) {\n logger.info(`[AsyncActions] Scheduling ${results.newTasks.length} new tasks`);\n await this.scheduleNewTasks(results.newTasks);\n }\n }\n\n /**\n * Process a failed task through the retry pipeline\n */\n private async processFailedTaskWithRetry(failedTask: CronTask<ID>): Promise<void> {\n const executor = this.taskQueue.getExecutor(failedTask.queue_id, failedTask.type);\n const maxRetries = failedTask.retries ?? executor?.default_retries ?? 0;\n const decision = computeRetryDecision(failedTask, maxRetries);\n const willRetry = decision.action === 'retry' && !!decision.retryTask;\n\n if (willRetry) {\n logger.info(`[AsyncActions] Retrying async task ${this.taskId} (attempt ${(decision.retryTask!.execution_stats?.retry_count as number) || 0})`);\n await this.taskStore.updateTasksForRetry([decision.retryTask!]);\n } else {\n logger.info(`[AsyncActions] Async task ${this.taskId} exhausted retries, marking as failed`);\n await this.taskStore.markTasksAsFailed([failedTask]);\n\n // RFC-003: Emit 'failed' entity projection for final-failed async tasks\n const errorMsg = failedTask.execution_stats?.last_error as string || 'Task failed';\n const p = buildProjection(failedTask, 'failed', {\n includePayload: this.entityProjectionConfig?.includePayload,\n error: errorMsg,\n });\n if (p) await syncProjections([p], this.entityProjection, logger);\n }\n\n if (this.lifecycleEmitter) {\n const errorMsg = failedTask.execution_stats?.last_error as string || 'Task failed';\n try {\n this.lifecycleEmitter.onFailed(failedTask, new Error(errorMsg), willRetry);\n } catch (err) {\n logger.error(`[AsyncActions] Lifecycle onFailed error:`, err);\n }\n }\n }\n\n /**\n * Schedule new tasks - replicates the logic from task-handler's addTasks\n */\n private async scheduleNewTasks(tasks: CronTask<ID>[]): Promise<void> {\n const now = new Date();\n const immediate: { [key in QueueName]?: CronTask<ID>[] } = {};\n const future: CronTask<ID>[] = [];\n\n // Split tasks by timing\n for (const task of tasks) {\n const timeDiff = (task.execute_at.getTime() - now.getTime()) / 1000 / 60; // in minutes\n\n if (timeDiff > 2) {\n // Future task - goes to database\n future.push(task);\n } else {\n // Immediate task - goes to message queue\n const queue = task.queue_id;\n if (!immediate[queue]) {\n immediate[queue] = [];\n }\n immediate[queue].push(task);\n }\n }\n\n // Process immediate tasks\n const iQueues = Object.keys(immediate) as QueueName[];\n for (const queue of iQueues) {\n const queueTasks = immediate[queue]!.map((task) => {\n const executor = this.taskQueue.getExecutor(task.queue_id, task.type);\n const shouldStoreOnFailure = executor?.store_on_failure ?? false;\n const id = shouldStoreOnFailure ? {id: this.generateId()} : {};\n return {...id, ...task};\n });\n\n try {\n await this.messageQueue.addMessages(queue, queueTasks as CronTask<ID>[]);\n logger.info(`[AsyncActions] Added ${queueTasks.length} immediate tasks to queue ${queue}`);\n } catch (err) {\n logger.error(`[AsyncActions] Failed to add tasks to queue ${queue}:`, err);\n throw err;\n }\n }\n\n // Process future tasks\n if (future.length > 0) {\n const futureTasks = future.map((task) => {\n const executor = this.taskQueue.getExecutor(task.queue_id, task.type);\n const shouldStoreOnFailure = executor?.store_on_failure ?? false;\n const id = shouldStoreOnFailure ? {id: this.generateId()} : {};\n return {...id, ...task};\n });\n\n try {\n await this.taskStore.addTasksToScheduled(futureTasks);\n logger.info(`[AsyncActions] Added ${futureTasks.length} future tasks to database`);\n } catch (err) {\n logger.error(`[AsyncActions] Failed to add tasks to database:`, err);\n throw err;\n }\n }\n }\n}\n"],"names":["logger","Logger","LogLevel","tId"],"mappings":"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"}
@@ -0,0 +1,242 @@
1
+ import { L as Logger, a as LogLevel } from "./client-dvHNt8qU.js";
2
+ import { tId } from "./utils/task-id-gen.mjs";
3
+ const MAX_RETRY_DELAY_MS = 5 * 60 * 1e3;
4
+ function computeRetryDecision(task, maxRetries) {
5
+ const retryCount = task.execution_stats && typeof task.execution_stats.retry_count === "number" ? task.execution_stats.retry_count : 0;
6
+ if (retryCount >= maxRetries) {
7
+ return { action: "fail" };
8
+ }
9
+ const taskRetryAfter = Math.max(task.retry_after || 2e3, 0);
10
+ const calculatedDelay = taskRetryAfter * Math.pow(retryCount + 1, 2);
11
+ const retryAfter = Math.min(calculatedDelay, MAX_RETRY_DELAY_MS);
12
+ const executeAt = Date.now() + retryAfter;
13
+ return {
14
+ action: "retry",
15
+ retryTask: {
16
+ ...task,
17
+ status: "scheduled",
18
+ execute_at: new Date(executeAt),
19
+ execution_stats: {
20
+ ...task.execution_stats || {},
21
+ retry_count: retryCount + 1
22
+ }
23
+ }
24
+ };
25
+ }
26
+ async function syncProjections(projections, provider, logger2) {
27
+ if (projections.length === 0 || !provider) return;
28
+ try {
29
+ await provider.upsertProjections(projections);
30
+ } catch (err) {
31
+ logger2.error(`[TQ] Entity projection sync failed (non-fatal): ${err}`);
32
+ }
33
+ }
34
+ function buildProjection(task, status, options) {
35
+ if (!task.entity) return null;
36
+ if (task.id == null) {
37
+ throw new Error(
38
+ `[TQ/RFC-003] Task with entity (${task.entity.type}:${task.entity.id}) has no task ID. Entity-bearing tasks must have an ID for projection keying. Set store_on_failure:true, force_store:true, or assign an ID before addTasks().`
39
+ );
40
+ }
41
+ return {
42
+ task_id: task.id,
43
+ entity_id: task.entity.id,
44
+ entity_type: task.entity.type,
45
+ task_type: task.type,
46
+ queue_id: task.queue_id,
47
+ status,
48
+ payload: options?.includePayload ? task.payload : void 0,
49
+ error: options?.error,
50
+ result: options?.result,
51
+ created_at: task.created_at || /* @__PURE__ */ new Date(),
52
+ updated_at: /* @__PURE__ */ new Date()
53
+ };
54
+ }
55
+ const logger = new Logger("AsyncActions", LogLevel.INFO);
56
+ class AsyncActions {
57
+ constructor(messageQueue, taskStore, taskQueue, actions, task, generateId, lifecycleEmitter, entityProjection, entityProjectionConfig, flowMiddleware) {
58
+ this.messageQueue = messageQueue;
59
+ this.taskStore = taskStore;
60
+ this.taskQueue = taskQueue;
61
+ this.task = task;
62
+ this.generateId = generateId;
63
+ this.lifecycleEmitter = lifecycleEmitter;
64
+ this.entityProjection = entityProjection;
65
+ this.entityProjectionConfig = entityProjectionConfig;
66
+ this.flowMiddleware = flowMiddleware;
67
+ this.actions = actions;
68
+ this.taskId = tId(task);
69
+ }
70
+ /**
71
+ * Called when the async promise completes to execute the collected actions
72
+ */
73
+ async onPromiseFulfilled() {
74
+ const results = this.actions.extractTaskActions(this.taskId);
75
+ const hasCompletion = results.successTasks.length > 0 || results.failedTasks.length > 0;
76
+ if (!hasCompletion) {
77
+ logger.warn(`Async task ${this.taskId} completed without calling success() or fail() — defaulting to fail`);
78
+ results.failedTasks.push({
79
+ ...this.task,
80
+ execution_stats: {
81
+ ...this.task.execution_stats || {},
82
+ last_error: `Async task ${this.taskId} completed without calling success() or fail()`
83
+ }
84
+ });
85
+ }
86
+ logger.info(`[AsyncActions] Processing results for async task ${this.taskId}: ${results.successTasks.length} success, ${results.failedTasks.length} failed, ${results.newTasks.length} new tasks`);
87
+ if (results.failedTasks.length > 0) {
88
+ for (const failedTask of results.failedTasks) {
89
+ try {
90
+ await this.processFailedTaskWithRetry(failedTask);
91
+ } catch (err) {
92
+ logger.error(`[AsyncActions] Failed to process failed task:`, err);
93
+ throw err;
94
+ }
95
+ }
96
+ }
97
+ if (results.successTasks.length > 0) {
98
+ try {
99
+ await this.taskStore.markTasksAsSuccess(results.successTasks);
100
+ logger.info(`[AsyncActions] Marked ${results.successTasks.length} tasks as success in database`);
101
+ if (this.lifecycleEmitter) {
102
+ for (const task of results.successTasks) {
103
+ try {
104
+ this.lifecycleEmitter.onCompleted(task, task.execution_result);
105
+ } catch (err) {
106
+ logger.error(`[AsyncActions] Lifecycle onCompleted error:`, err);
107
+ }
108
+ }
109
+ }
110
+ await syncProjections(
111
+ results.successTasks.map((t) => buildProjection(t, "executed", {
112
+ includePayload: this.entityProjectionConfig?.includePayload,
113
+ result: t.execution_result
114
+ })).filter((p) => p !== null),
115
+ this.entityProjection,
116
+ logger
117
+ );
118
+ } catch (err) {
119
+ logger.error(`[AsyncActions] Failed to mark tasks as success:`, err);
120
+ throw err;
121
+ }
122
+ }
123
+ if (this.flowMiddleware) {
124
+ try {
125
+ const finalFailedTasks = [];
126
+ for (const failedTask of results.failedTasks) {
127
+ const executor = this.taskQueue.getExecutor(failedTask.queue_id, failedTask.type);
128
+ const maxRetries = failedTask.retries ?? executor?.default_retries ?? 0;
129
+ const decision = computeRetryDecision(failedTask, maxRetries);
130
+ if (decision.action !== "retry" || !decision.retryTask) {
131
+ finalFailedTasks.push(failedTask);
132
+ }
133
+ }
134
+ if (results.successTasks.length > 0 || finalFailedTasks.length > 0) {
135
+ const flowResult = await this.flowMiddleware.onPostProcess({
136
+ successTasks: results.successTasks,
137
+ failedTasks: finalFailedTasks
138
+ });
139
+ if (flowResult.projections.length > 0 && this.entityProjection) {
140
+ await syncProjections(flowResult.projections, this.entityProjection, logger);
141
+ }
142
+ if (flowResult.joinTasks.length > 0) {
143
+ await this.scheduleNewTasks(flowResult.joinTasks);
144
+ }
145
+ }
146
+ } catch (err) {
147
+ logger.error(`[AsyncActions] Flow middleware failed (non-fatal): ${err}`);
148
+ }
149
+ }
150
+ if (results.newTasks.length > 0) {
151
+ logger.info(`[AsyncActions] Scheduling ${results.newTasks.length} new tasks`);
152
+ await this.scheduleNewTasks(results.newTasks);
153
+ }
154
+ }
155
+ /**
156
+ * Process a failed task through the retry pipeline
157
+ */
158
+ async processFailedTaskWithRetry(failedTask) {
159
+ const executor = this.taskQueue.getExecutor(failedTask.queue_id, failedTask.type);
160
+ const maxRetries = failedTask.retries ?? executor?.default_retries ?? 0;
161
+ const decision = computeRetryDecision(failedTask, maxRetries);
162
+ const willRetry = decision.action === "retry" && !!decision.retryTask;
163
+ if (willRetry) {
164
+ logger.info(`[AsyncActions] Retrying async task ${this.taskId} (attempt ${decision.retryTask.execution_stats?.retry_count || 0})`);
165
+ await this.taskStore.updateTasksForRetry([decision.retryTask]);
166
+ } else {
167
+ logger.info(`[AsyncActions] Async task ${this.taskId} exhausted retries, marking as failed`);
168
+ await this.taskStore.markTasksAsFailed([failedTask]);
169
+ const errorMsg = failedTask.execution_stats?.last_error || "Task failed";
170
+ const p = buildProjection(failedTask, "failed", {
171
+ includePayload: this.entityProjectionConfig?.includePayload,
172
+ error: errorMsg
173
+ });
174
+ if (p) await syncProjections([p], this.entityProjection, logger);
175
+ }
176
+ if (this.lifecycleEmitter) {
177
+ const errorMsg = failedTask.execution_stats?.last_error || "Task failed";
178
+ try {
179
+ this.lifecycleEmitter.onFailed(failedTask, new Error(errorMsg), willRetry);
180
+ } catch (err) {
181
+ logger.error(`[AsyncActions] Lifecycle onFailed error:`, err);
182
+ }
183
+ }
184
+ }
185
+ /**
186
+ * Schedule new tasks - replicates the logic from task-handler's addTasks
187
+ */
188
+ async scheduleNewTasks(tasks) {
189
+ const now = /* @__PURE__ */ new Date();
190
+ const immediate = {};
191
+ const future = [];
192
+ for (const task of tasks) {
193
+ const timeDiff = (task.execute_at.getTime() - now.getTime()) / 1e3 / 60;
194
+ if (timeDiff > 2) {
195
+ future.push(task);
196
+ } else {
197
+ const queue = task.queue_id;
198
+ if (!immediate[queue]) {
199
+ immediate[queue] = [];
200
+ }
201
+ immediate[queue].push(task);
202
+ }
203
+ }
204
+ const iQueues = Object.keys(immediate);
205
+ for (const queue of iQueues) {
206
+ const queueTasks = immediate[queue].map((task) => {
207
+ const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
208
+ const shouldStoreOnFailure = executor?.store_on_failure ?? false;
209
+ const id = shouldStoreOnFailure ? { id: this.generateId() } : {};
210
+ return { ...id, ...task };
211
+ });
212
+ try {
213
+ await this.messageQueue.addMessages(queue, queueTasks);
214
+ logger.info(`[AsyncActions] Added ${queueTasks.length} immediate tasks to queue ${queue}`);
215
+ } catch (err) {
216
+ logger.error(`[AsyncActions] Failed to add tasks to queue ${queue}:`, err);
217
+ throw err;
218
+ }
219
+ }
220
+ if (future.length > 0) {
221
+ const futureTasks = future.map((task) => {
222
+ const executor = this.taskQueue.getExecutor(task.queue_id, task.type);
223
+ const shouldStoreOnFailure = executor?.store_on_failure ?? false;
224
+ const id = shouldStoreOnFailure ? { id: this.generateId() } : {};
225
+ return { ...id, ...task };
226
+ });
227
+ try {
228
+ await this.taskStore.addTasksToScheduled(futureTasks);
229
+ logger.info(`[AsyncActions] Added ${futureTasks.length} future tasks to database`);
230
+ } catch (err) {
231
+ logger.error(`[AsyncActions] Failed to add tasks to database:`, err);
232
+ throw err;
233
+ }
234
+ }
235
+ }
236
+ }
237
+ export {
238
+ AsyncActions as A,
239
+ buildProjection as b,
240
+ syncProjections as s
241
+ };
242
+ //# sourceMappingURL=AsyncActions-CZYO8ShR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AsyncActions-CZYO8ShR.js","sources":["../src/core/async/retry-utils.ts","../src/core/entity/IEntityProjectionProvider.ts","../src/core/async/AsyncActions.ts"],"sourcesContent":["import {CronTask} from \"../../adapters\";\n\nconst MAX_RETRY_DELAY_MS = 5 * 60 * 1000; // 5 minutes\n\nexport interface RetryDecision<ID = any> {\n action: 'retry' | 'fail';\n retryTask?: CronTask<ID>;\n}\n\n/**\n * Compute whether a failed task should be retried and produce the retry-ready task.\n *\n * Mirrors the logic in TaskHandler.postProcessTasks() but usable from the async path.\n *\n * @param task The failed task\n * @param maxRetries Maximum retry attempts for this task type\n * @returns RetryDecision — either { action: 'retry', retryTask } or { action: 'fail' }\n */\nexport function computeRetryDecision<ID>(\n task: CronTask<ID>,\n maxRetries: number\n): RetryDecision<ID> {\n const retryCount = (task.execution_stats && typeof task.execution_stats.retry_count === 'number')\n ? task.execution_stats.retry_count\n : 0;\n\n if (retryCount >= maxRetries) {\n return {action: 'fail'};\n }\n\n const taskRetryAfter = Math.max(task.retry_after || 2000, 0);\n const calculatedDelay = taskRetryAfter * Math.pow(retryCount + 1, 2);\n const retryAfter = Math.min(calculatedDelay, MAX_RETRY_DELAY_MS);\n const executeAt = Date.now() + retryAfter;\n\n return {\n action: 'retry',\n retryTask: {\n ...task,\n status: 'scheduled' as const,\n execute_at: new Date(executeAt),\n execution_stats: {\n ...(task.execution_stats || {}),\n retry_count: retryCount + 1\n }\n }\n };\n}\n","/**\n * RFC-003: Entity Task Projection\n *\n * Automatic entity-task status projection so dashboards can track\n * task lifecycle without querying the internal tasks table.\n */\n\nimport type {CronTask} from \"../../adapters/types.js\";\n\nexport type EntityTaskProjectionStatus = 'scheduled' | 'processing' | 'executed' | 'failed';\n\nexport interface EntityTaskProjection<ID = any> {\n task_id: ID;\n entity_id: string;\n entity_type: string;\n task_type: string;\n queue_id: string;\n status: EntityTaskProjectionStatus;\n payload?: unknown;\n error?: string;\n result?: unknown;\n created_at: Date;\n updated_at: Date;\n}\n\n/**\n * Provider interface for persisting entity-task projections.\n * Implementations might write to a database table, cache, or external service.\n */\nexport interface IEntityProjectionProvider<ID = any> {\n upsertProjections(entries: EntityTaskProjection<ID>[]): Promise<void>;\n}\n\n/**\n * Configuration for entity projection behavior.\n */\nexport interface EntityProjectionConfig {\n /** Include task payload in projection (default: false for performance) */\n includePayload?: boolean;\n}\n\n/**\n * Sync entity projections to the provider. Non-fatal — logs and continues on error.\n */\nexport async function syncProjections<ID>(\n projections: EntityTaskProjection<ID>[],\n provider: IEntityProjectionProvider<ID> | undefined,\n logger: { error(msg: string): void }\n): Promise<void> {\n if (projections.length === 0 || !provider) return;\n try {\n await provider.upsertProjections(projections);\n } catch (err) {\n logger.error(`[TQ] Entity projection sync failed (non-fatal): ${err}`);\n }\n}\n\n/**\n * Build a single projection entry from a CronTask and target status.\n * Returns null if the task has no entity binding.\n * Throws if entity is present but task has no ID — fail-fast for developer errors.\n */\nexport function buildProjection<ID>(\n task: CronTask<ID>,\n status: EntityTaskProjectionStatus,\n options?: {\n includePayload?: boolean;\n error?: string;\n result?: unknown;\n }\n): EntityTaskProjection<ID> | null {\n if (!task.entity) return null;\n\n if (task.id == null) {\n throw new Error(\n `[TQ/RFC-003] Task with entity (${task.entity.type}:${task.entity.id}) has no task ID. ` +\n `Entity-bearing tasks must have an ID for projection keying. ` +\n `Set store_on_failure:true, force_store:true, or assign an ID before addTasks().`\n );\n }\n\n return {\n task_id: task.id,\n entity_id: task.entity.id,\n entity_type: task.entity.type,\n task_type: task.type,\n queue_id: task.queue_id,\n status,\n payload: options?.includePayload ? task.payload : undefined,\n error: options?.error,\n result: options?.result,\n created_at: task.created_at || new Date(),\n updated_at: new Date(),\n };\n}","import {Logger, LogLevel} from \"@supergrowthai/utils\";\nimport {TaskStore} from \"../TaskStore.js\";\nimport {Actions} from \"../Actions.js\";\nimport {IMessageQueue, QueueName} from \"@supergrowthai/mq\";\nimport {tId} from \"../../utils/task-id-gen.js\";\nimport {CronTask} from \"../../adapters\";\nimport {TaskQueuesManager} from \"../TaskQueuesManager.js\";\nimport {computeRetryDecision} from \"./retry-utils.js\";\nimport type {IEntityProjectionProvider, EntityProjectionConfig, EntityTaskProjection} from \"../entity/IEntityProjectionProvider.js\";\nimport {buildProjection, syncProjections} from \"../entity/IEntityProjectionProvider.js\";\nimport type {FlowMiddleware} from \"../flow/FlowMiddleware.js\";\n\nconst logger = new Logger('AsyncActions', LogLevel.INFO);\n\n/**\n * Interface for emitting async task lifecycle events.\n * Constructed from the sync-path's ITaskLifecycleProvider by TaskRunner.\n */\nexport interface AsyncLifecycleEmitter {\n onCompleted(task: CronTask<any>, result?: unknown): void;\n onFailed(task: CronTask<any>, error: Error, willRetry: boolean): void;\n}\n\nexport class AsyncActions<ID = any> {\n private readonly actions: Actions<ID>;\n private readonly taskId: string;\n\n constructor(\n private messageQueue: IMessageQueue<ID>,\n private taskStore: TaskStore<ID>,\n private taskQueue: TaskQueuesManager<ID>,\n actions: Actions<ID>,\n private task: CronTask<ID>,\n private generateId: () => ID,\n private lifecycleEmitter?: AsyncLifecycleEmitter,\n private entityProjection?: IEntityProjectionProvider<ID>,\n private entityProjectionConfig?: EntityProjectionConfig,\n private flowMiddleware?: FlowMiddleware<ID>\n ) {\n this.actions = actions;\n this.taskId = tId(task);\n }\n\n /**\n * Called when the async promise completes to execute the collected actions\n */\n async onPromiseFulfilled(): Promise<void> {\n // Extract this task's results (NO batch context for async tasks)\n const results = this.actions.extractTaskActions(this.taskId);\n\n // If task didn't call success or fail, default to fail (forgetful executor)\n const hasCompletion = results.successTasks.length > 0 || results.failedTasks.length > 0;\n if (!hasCompletion) {\n logger.warn(`Async task ${this.taskId} completed without calling success() or fail() — defaulting to fail`);\n results.failedTasks.push({\n ...this.task,\n execution_stats: {\n ...(this.task.execution_stats || {}),\n last_error: `Async task ${this.taskId} completed without calling success() or fail()`,\n }\n });\n }\n\n logger.info(`[AsyncActions] Processing results for async task ${this.taskId}: ` +\n `${results.successTasks.length} success, ${results.failedTasks.length} failed, ` +\n `${results.newTasks.length} new tasks`);\n\n // Process failed tasks with retry logic\n if (results.failedTasks.length > 0) {\n for (const failedTask of results.failedTasks) {\n try {\n await this.processFailedTaskWithRetry(failedTask);\n } catch (err) {\n logger.error(`[AsyncActions] Failed to process failed task:`, err);\n throw err;\n }\n }\n }\n\n if (results.successTasks.length > 0) {\n try {\n await this.taskStore.markTasksAsSuccess(results.successTasks);\n logger.info(`[AsyncActions] Marked ${results.successTasks.length} tasks as success in database`);\n\n // Emit lifecycle event for each success\n if (this.lifecycleEmitter) {\n for (const task of results.successTasks) {\n try {\n this.lifecycleEmitter.onCompleted(task, task.execution_result);\n } catch (err) {\n logger.error(`[AsyncActions] Lifecycle onCompleted error:`, err);\n }\n }\n }\n\n // RFC-003: Emit 'executed' entity projections for async success tasks\n await syncProjections(\n results.successTasks\n .map(t => buildProjection(t, 'executed', {\n includePayload: this.entityProjectionConfig?.includePayload,\n result: t.execution_result,\n }))\n .filter((p): p is EntityTaskProjection<ID> => p !== null),\n this.entityProjection,\n logger\n );\n } catch (err) {\n logger.error(`[AsyncActions] Failed to mark tasks as success:`, err);\n throw err;\n }\n }\n\n // RFC-002: Flow middleware — process terminal tasks for barrier tracking and join dispatch\n if (this.flowMiddleware) {\n try {\n // Collect final failures (not retries) for flow middleware\n const finalFailedTasks: CronTask<ID>[] = [];\n for (const failedTask of results.failedTasks) {\n const executor = this.taskQueue.getExecutor(failedTask.queue_id, failedTask.type);\n const maxRetries = failedTask.retries ?? executor?.default_retries ?? 0;\n const decision = computeRetryDecision(failedTask, maxRetries);\n // Only include final failures (no more retries)\n if (decision.action !== 'retry' || !decision.retryTask) {\n finalFailedTasks.push(failedTask);\n }\n }\n\n if (results.successTasks.length > 0 || finalFailedTasks.length > 0) {\n const flowResult = await this.flowMiddleware.onPostProcess({\n successTasks: results.successTasks,\n failedTasks: finalFailedTasks,\n });\n\n if (flowResult.projections.length > 0 && this.entityProjection) {\n await syncProjections(flowResult.projections, this.entityProjection, logger);\n }\n\n if (flowResult.joinTasks.length > 0) {\n await this.scheduleNewTasks(flowResult.joinTasks);\n }\n }\n } catch (err) {\n logger.error(`[AsyncActions] Flow middleware failed (non-fatal): ${err}`);\n }\n }\n\n if (results.newTasks.length > 0) {\n logger.info(`[AsyncActions] Scheduling ${results.newTasks.length} new tasks`);\n await this.scheduleNewTasks(results.newTasks);\n }\n }\n\n /**\n * Process a failed task through the retry pipeline\n */\n private async processFailedTaskWithRetry(failedTask: CronTask<ID>): Promise<void> {\n const executor = this.taskQueue.getExecutor(failedTask.queue_id, failedTask.type);\n const maxRetries = failedTask.retries ?? executor?.default_retries ?? 0;\n const decision = computeRetryDecision(failedTask, maxRetries);\n const willRetry = decision.action === 'retry' && !!decision.retryTask;\n\n if (willRetry) {\n logger.info(`[AsyncActions] Retrying async task ${this.taskId} (attempt ${(decision.retryTask!.execution_stats?.retry_count as number) || 0})`);\n await this.taskStore.updateTasksForRetry([decision.retryTask!]);\n } else {\n logger.info(`[AsyncActions] Async task ${this.taskId} exhausted retries, marking as failed`);\n await this.taskStore.markTasksAsFailed([failedTask]);\n\n // RFC-003: Emit 'failed' entity projection for final-failed async tasks\n const errorMsg = failedTask.execution_stats?.last_error as string || 'Task failed';\n const p = buildProjection(failedTask, 'failed', {\n includePayload: this.entityProjectionConfig?.includePayload,\n error: errorMsg,\n });\n if (p) await syncProjections([p], this.entityProjection, logger);\n }\n\n if (this.lifecycleEmitter) {\n const errorMsg = failedTask.execution_stats?.last_error as string || 'Task failed';\n try {\n this.lifecycleEmitter.onFailed(failedTask, new Error(errorMsg), willRetry);\n } catch (err) {\n logger.error(`[AsyncActions] Lifecycle onFailed error:`, err);\n }\n }\n }\n\n /**\n * Schedule new tasks - replicates the logic from task-handler's addTasks\n */\n private async scheduleNewTasks(tasks: CronTask<ID>[]): Promise<void> {\n const now = new Date();\n const immediate: { [key in QueueName]?: CronTask<ID>[] } = {};\n const future: CronTask<ID>[] = [];\n\n // Split tasks by timing\n for (const task of tasks) {\n const timeDiff = (task.execute_at.getTime() - now.getTime()) / 1000 / 60; // in minutes\n\n if (timeDiff > 2) {\n // Future task - goes to database\n future.push(task);\n } else {\n // Immediate task - goes to message queue\n const queue = task.queue_id;\n if (!immediate[queue]) {\n immediate[queue] = [];\n }\n immediate[queue].push(task);\n }\n }\n\n // Process immediate tasks\n const iQueues = Object.keys(immediate) as QueueName[];\n for (const queue of iQueues) {\n const queueTasks = immediate[queue]!.map((task) => {\n const executor = this.taskQueue.getExecutor(task.queue_id, task.type);\n const shouldStoreOnFailure = executor?.store_on_failure ?? false;\n const id = shouldStoreOnFailure ? {id: this.generateId()} : {};\n return {...id, ...task};\n });\n\n try {\n await this.messageQueue.addMessages(queue, queueTasks as CronTask<ID>[]);\n logger.info(`[AsyncActions] Added ${queueTasks.length} immediate tasks to queue ${queue}`);\n } catch (err) {\n logger.error(`[AsyncActions] Failed to add tasks to queue ${queue}:`, err);\n throw err;\n }\n }\n\n // Process future tasks\n if (future.length > 0) {\n const futureTasks = future.map((task) => {\n const executor = this.taskQueue.getExecutor(task.queue_id, task.type);\n const shouldStoreOnFailure = executor?.store_on_failure ?? false;\n const id = shouldStoreOnFailure ? {id: this.generateId()} : {};\n return {...id, ...task};\n });\n\n try {\n await this.taskStore.addTasksToScheduled(futureTasks);\n logger.info(`[AsyncActions] Added ${futureTasks.length} future tasks to database`);\n } catch (err) {\n logger.error(`[AsyncActions] Failed to add tasks to database:`, err);\n throw err;\n }\n }\n }\n}\n"],"names":["logger"],"mappings":"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"}