pg-workflows 0.9.0 → 0.11.0
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/dist/client.entry.cjs +49 -21
- package/dist/client.entry.d.cts +48 -11
- package/dist/client.entry.d.ts +48 -11
- package/dist/client.entry.js +6 -2
- package/dist/client.entry.js.map +8 -8
- package/dist/index.cjs +401 -125
- package/dist/index.d.cts +68 -35
- package/dist/index.d.ts +68 -35
- package/dist/index.js +359 -107
- package/dist/index.js.map +12 -12
- package/dist/shared/{chunk-fr76gdwj.js → chunk-ahxqsytt.js} +49 -23
- package/dist/shared/chunk-ahxqsytt.js.map +16 -0
- package/package.json +1 -1
- package/dist/shared/chunk-fr76gdwj.js.map +0 -16
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DEFAULT_PGBOSS_SCHEMA,
|
|
3
3
|
PAUSE_EVENT_NAME,
|
|
4
|
-
StepType,
|
|
5
4
|
WORKFLOW_RUN_DLQ_QUEUE_NAME,
|
|
6
5
|
WORKFLOW_RUN_QUEUE_NAME,
|
|
7
6
|
WorkflowClient,
|
|
@@ -12,38 +11,16 @@ import {
|
|
|
12
11
|
getWorkflowRun,
|
|
13
12
|
getWorkflowRuns,
|
|
14
13
|
insertWorkflowRun,
|
|
14
|
+
invokeChildWorkflowTimelineKey,
|
|
15
|
+
isInvokeChildWorkflowTimelineEntry,
|
|
15
16
|
runMigrations,
|
|
16
17
|
updateWorkflowRun,
|
|
17
18
|
validateResourceId,
|
|
18
19
|
validateWorkflowId,
|
|
20
|
+
waitForTimelineKey,
|
|
19
21
|
withPostgresTransaction,
|
|
20
22
|
workflow
|
|
21
|
-
} from "./shared/chunk-
|
|
22
|
-
// src/duration.ts
|
|
23
|
-
import parse from "parse-duration";
|
|
24
|
-
var MS_PER_SECOND = 1000;
|
|
25
|
-
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
26
|
-
var MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
27
|
-
var MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
28
|
-
var MS_PER_WEEK = 7 * MS_PER_DAY;
|
|
29
|
-
function parseDuration(duration) {
|
|
30
|
-
if (typeof duration === "string") {
|
|
31
|
-
if (duration.trim() === "") {
|
|
32
|
-
throw new WorkflowEngineError("Invalid duration: empty string");
|
|
33
|
-
}
|
|
34
|
-
const ms2 = parse(duration);
|
|
35
|
-
if (ms2 == null || ms2 <= 0) {
|
|
36
|
-
throw new WorkflowEngineError(`Invalid duration: "${duration}"`);
|
|
37
|
-
}
|
|
38
|
-
return ms2;
|
|
39
|
-
}
|
|
40
|
-
const { weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0 } = duration;
|
|
41
|
-
const ms = weeks * MS_PER_WEEK + days * MS_PER_DAY + hours * MS_PER_HOUR + minutes * MS_PER_MINUTE + seconds * MS_PER_SECOND;
|
|
42
|
-
if (ms <= 0) {
|
|
43
|
-
throw new WorkflowEngineError("Invalid duration: must be a positive value");
|
|
44
|
-
}
|
|
45
|
-
return ms;
|
|
46
|
-
}
|
|
23
|
+
} from "./shared/chunk-ahxqsytt.js";
|
|
47
24
|
// src/engine.ts
|
|
48
25
|
import { merge } from "es-toolkit";
|
|
49
26
|
import pg from "pg";
|
|
@@ -94,7 +71,7 @@ function parseWorkflowHandler(handler) {
|
|
|
94
71
|
const propertyAccess = node.expression;
|
|
95
72
|
const objectName = propertyAccess.expression.getText(sourceFile);
|
|
96
73
|
const methodName = propertyAccess.name.text;
|
|
97
|
-
if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll")) {
|
|
74
|
+
if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll" || methodName === "invokeChildWorkflow")) {
|
|
98
75
|
const firstArg = node.arguments[0];
|
|
99
76
|
if (firstArg) {
|
|
100
77
|
const { id, isDynamic } = extractStepId(firstArg);
|
|
@@ -119,6 +96,32 @@ function parseWorkflowHandler(handler) {
|
|
|
119
96
|
return { steps: Array.from(steps.values()) };
|
|
120
97
|
}
|
|
121
98
|
|
|
99
|
+
// src/duration.ts
|
|
100
|
+
import parse from "parse-duration";
|
|
101
|
+
var MS_PER_SECOND = 1000;
|
|
102
|
+
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
103
|
+
var MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
104
|
+
var MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
105
|
+
var MS_PER_WEEK = 7 * MS_PER_DAY;
|
|
106
|
+
function parseDuration(duration) {
|
|
107
|
+
if (typeof duration === "string") {
|
|
108
|
+
if (duration.trim() === "") {
|
|
109
|
+
throw new WorkflowEngineError("Invalid duration: empty string");
|
|
110
|
+
}
|
|
111
|
+
const ms2 = parse(duration);
|
|
112
|
+
if (ms2 == null || ms2 <= 0) {
|
|
113
|
+
throw new WorkflowEngineError(`Invalid duration: "${duration}"`);
|
|
114
|
+
}
|
|
115
|
+
return ms2;
|
|
116
|
+
}
|
|
117
|
+
const { weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0 } = duration;
|
|
118
|
+
const ms = weeks * MS_PER_WEEK + days * MS_PER_DAY + hours * MS_PER_HOUR + minutes * MS_PER_MINUTE + seconds * MS_PER_SECOND;
|
|
119
|
+
if (ms <= 0) {
|
|
120
|
+
throw new WorkflowEngineError("Invalid duration: must be a positive value");
|
|
121
|
+
}
|
|
122
|
+
return ms;
|
|
123
|
+
}
|
|
124
|
+
|
|
122
125
|
// src/engine.ts
|
|
123
126
|
var LOG_PREFIX = "[WorkflowEngine]";
|
|
124
127
|
var StepTypeToIcon = {
|
|
@@ -127,7 +130,8 @@ var StepTypeToIcon = {
|
|
|
127
130
|
["pause" /* PAUSE */]: "⏸",
|
|
128
131
|
["waitUntil" /* WAIT_UNTIL */]: "⏲",
|
|
129
132
|
["delay" /* DELAY */]: "⏱",
|
|
130
|
-
["poll" /* POLL */]: "↻"
|
|
133
|
+
["poll" /* POLL */]: "↻",
|
|
134
|
+
["invokeChildWorkflow" /* INVOKE_CHILD_WORKFLOW */]: "↪"
|
|
131
135
|
};
|
|
132
136
|
var defaultLogger = {
|
|
133
137
|
log: (_message) => console.warn(_message),
|
|
@@ -139,6 +143,7 @@ var retrySendOptions = (maxRetries) => ({
|
|
|
139
143
|
retryBackoff: true,
|
|
140
144
|
retryDelay: 1
|
|
141
145
|
});
|
|
146
|
+
var getInvokeChildWorkflowEventName = (childRunId) => `__invoke_child_workflow_completed:${childRunId}`;
|
|
142
147
|
var defaultHeartbeatSeconds = process.env.WORKFLOW_RUN_HEARTBEAT_SECONDS ? Number.parseInt(process.env.WORKFLOW_RUN_HEARTBEAT_SECONDS, 10) : 30;
|
|
143
148
|
|
|
144
149
|
class WorkflowEngine {
|
|
@@ -244,31 +249,57 @@ class WorkflowEngine {
|
|
|
244
249
|
this.workflows.clear();
|
|
245
250
|
return this;
|
|
246
251
|
}
|
|
247
|
-
|
|
248
|
-
let workflowId;
|
|
249
|
-
let input;
|
|
250
|
-
let resourceId;
|
|
251
|
-
let idempotencyKey;
|
|
252
|
-
let options;
|
|
252
|
+
resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg) {
|
|
253
253
|
if (typeof refOrParams === "function" && "id" in refOrParams) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
workflowId = params.workflowId;
|
|
262
|
-
input = params.input;
|
|
263
|
-
resourceId = params.resourceId;
|
|
264
|
-
idempotencyKey = params.idempotencyKey;
|
|
265
|
-
options = params.options;
|
|
254
|
+
return {
|
|
255
|
+
workflowId: refOrParams.id,
|
|
256
|
+
input: inputArg,
|
|
257
|
+
options: optionsArg,
|
|
258
|
+
resourceId: optionsArg?.resourceId,
|
|
259
|
+
idempotencyKey: optionsArg?.idempotencyKey
|
|
260
|
+
};
|
|
266
261
|
}
|
|
267
|
-
|
|
268
|
-
|
|
262
|
+
const params = refOrParams;
|
|
263
|
+
return {
|
|
264
|
+
workflowId: params.workflowId,
|
|
265
|
+
input: params.input,
|
|
266
|
+
resourceId: params.resourceId ?? params.options?.resourceId,
|
|
267
|
+
idempotencyKey: params.idempotencyKey ?? params.options?.idempotencyKey,
|
|
268
|
+
options: params.options
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
async startWorkflow(refOrParams, inputArg, optionsArg) {
|
|
272
|
+
const { workflowId, input, resourceId, idempotencyKey, options } = this.resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg);
|
|
269
273
|
if (!this._started) {
|
|
270
|
-
await this.start(false
|
|
274
|
+
await this.start(false);
|
|
271
275
|
}
|
|
276
|
+
const { run } = await this.createWorkflowRun({
|
|
277
|
+
workflowId,
|
|
278
|
+
input,
|
|
279
|
+
resourceId,
|
|
280
|
+
idempotencyKey,
|
|
281
|
+
options
|
|
282
|
+
});
|
|
283
|
+
this.logger.log("Started workflow run", {
|
|
284
|
+
runId: run.id,
|
|
285
|
+
workflowId
|
|
286
|
+
});
|
|
287
|
+
return run;
|
|
288
|
+
}
|
|
289
|
+
async createWorkflowRun({
|
|
290
|
+
workflowId,
|
|
291
|
+
input,
|
|
292
|
+
resourceId,
|
|
293
|
+
idempotencyKey,
|
|
294
|
+
options,
|
|
295
|
+
parentRunId,
|
|
296
|
+
parentStepId,
|
|
297
|
+
parentResourceId,
|
|
298
|
+
enqueue = true,
|
|
299
|
+
db
|
|
300
|
+
}) {
|
|
301
|
+
validateWorkflowId(workflowId);
|
|
302
|
+
validateResourceId(resourceId);
|
|
272
303
|
const workflow2 = this.workflows.get(workflowId);
|
|
273
304
|
if (!workflow2) {
|
|
274
305
|
throw new WorkflowEngineError(`Unknown workflow ${workflowId}`);
|
|
@@ -285,38 +316,60 @@ class WorkflowEngine {
|
|
|
285
316
|
}
|
|
286
317
|
}
|
|
287
318
|
const initialStepId = workflow2.steps[0]?.id ?? "__start__";
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
};
|
|
307
|
-
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
308
|
-
startAfter: new Date,
|
|
309
|
-
expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
|
|
310
|
-
...retrySendOptions(insertedRun.maxRetries)
|
|
311
|
-
});
|
|
319
|
+
const timeoutAt = options?.timeout ? new Date(Date.now() + options.timeout) : workflow2.timeout ? new Date(Date.now() + workflow2.timeout) : null;
|
|
320
|
+
const insertRun = async (targetDb) => await insertWorkflowRun({
|
|
321
|
+
resourceId,
|
|
322
|
+
workflowId,
|
|
323
|
+
currentStepId: initialStepId,
|
|
324
|
+
status: "running" /* RUNNING */,
|
|
325
|
+
input,
|
|
326
|
+
maxRetries: options?.retries ?? workflow2.retries ?? 0,
|
|
327
|
+
timeoutAt,
|
|
328
|
+
idempotencyKey,
|
|
329
|
+
parentRunId,
|
|
330
|
+
parentStepId,
|
|
331
|
+
parentResourceId
|
|
332
|
+
}, targetDb);
|
|
333
|
+
const insertAndEnqueue = async (targetDb) => {
|
|
334
|
+
const result = await insertRun(targetDb);
|
|
335
|
+
if (enqueue && result.created) {
|
|
336
|
+
await this.enqueueWorkflowRun(result.run, options, targetDb);
|
|
312
337
|
}
|
|
313
|
-
return
|
|
314
|
-
}
|
|
315
|
-
this.
|
|
338
|
+
return result;
|
|
339
|
+
};
|
|
340
|
+
const { run, created } = db ? await insertAndEnqueue(db) : await withPostgresTransaction(this.boss.getDb(), insertAndEnqueue, this.pool);
|
|
341
|
+
return { run, created };
|
|
342
|
+
}
|
|
343
|
+
async enqueueWorkflowRun(run, options, db) {
|
|
344
|
+
const job = {
|
|
316
345
|
runId: run.id,
|
|
317
|
-
|
|
346
|
+
resourceId: run.resourceId ?? undefined,
|
|
347
|
+
workflowId: run.workflowId,
|
|
348
|
+
input: run.input
|
|
349
|
+
};
|
|
350
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
351
|
+
startAfter: new Date,
|
|
352
|
+
expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
|
|
353
|
+
...retrySendOptions(run.maxRetries),
|
|
354
|
+
...db ? { db } : {}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
async notifyParentOfChildTerminalRun(childRun) {
|
|
358
|
+
if (!childRun.parentRunId || !childRun.parentStepId) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const parentRun = await getWorkflowRun({
|
|
362
|
+
runId: childRun.parentRunId,
|
|
363
|
+
resourceId: childRun.parentResourceId ?? undefined
|
|
364
|
+
}, { db: this.db });
|
|
365
|
+
if (!parentRun || parentRun.status === "completed" /* COMPLETED */ || parentRun.status === "failed" /* FAILED */ || parentRun.status === "cancelled" /* CANCELLED */) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
await this.triggerEvent({
|
|
369
|
+
runId: parentRun.id,
|
|
370
|
+
resourceId: parentRun.resourceId ?? undefined,
|
|
371
|
+
eventName: getInvokeChildWorkflowEventName(childRun.id)
|
|
318
372
|
});
|
|
319
|
-
return run;
|
|
320
373
|
}
|
|
321
374
|
async pauseWorkflow({
|
|
322
375
|
runId,
|
|
@@ -348,6 +401,9 @@ class WorkflowEngine {
|
|
|
348
401
|
if (current.status !== "paused" /* PAUSED */) {
|
|
349
402
|
throw new WorkflowEngineError(`Cannot resume workflow run in '${current.status}' status, must be 'paused'`, current.workflowId, runId);
|
|
350
403
|
}
|
|
404
|
+
if (this.getInvokeChildWorkflowStepEntry(current.timeline, current.currentStepId)) {
|
|
405
|
+
return current;
|
|
406
|
+
}
|
|
351
407
|
return this.triggerEvent({
|
|
352
408
|
runId,
|
|
353
409
|
resourceId,
|
|
@@ -367,6 +423,9 @@ class WorkflowEngine {
|
|
|
367
423
|
return run;
|
|
368
424
|
}
|
|
369
425
|
const stepId = run.currentStepId;
|
|
426
|
+
if (this.getInvokeChildWorkflowStepEntry(run.timeline, stepId)) {
|
|
427
|
+
return run;
|
|
428
|
+
}
|
|
370
429
|
const waitForStep = this.getWaitForStepEntry(run.timeline, stepId);
|
|
371
430
|
if (!waitForStep) {
|
|
372
431
|
return run;
|
|
@@ -415,6 +474,7 @@ class WorkflowEngine {
|
|
|
415
474
|
expectedStatuses: ["pending" /* PENDING */, "running" /* RUNNING */, "paused" /* PAUSED */]
|
|
416
475
|
});
|
|
417
476
|
this.logger.log(`cancelled workflow run with id ${runId}`);
|
|
477
|
+
await this.notifyParentOfChildTerminalRun(run);
|
|
418
478
|
return run;
|
|
419
479
|
}
|
|
420
480
|
async triggerEvent({
|
|
@@ -652,6 +712,22 @@ class WorkflowEngine {
|
|
|
652
712
|
}
|
|
653
713
|
const timeoutMs = options?.timeout ? parseDuration(options.timeout) : undefined;
|
|
654
714
|
return this.pollStep({ run, stepId, conditionFn, intervalMs, timeoutMs });
|
|
715
|
+
},
|
|
716
|
+
invokeChildWorkflow: async (stepId, refOrParams, inputArg, optionsArg) => {
|
|
717
|
+
if (!run) {
|
|
718
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
719
|
+
}
|
|
720
|
+
const resolvedChildCall = this.resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg);
|
|
721
|
+
const childWorkflowInvocation = {
|
|
722
|
+
run,
|
|
723
|
+
stepId,
|
|
724
|
+
workflowId: resolvedChildCall.workflowId,
|
|
725
|
+
input: resolvedChildCall.input,
|
|
726
|
+
options: resolvedChildCall.options,
|
|
727
|
+
resourceId: resolvedChildCall.resourceId,
|
|
728
|
+
idempotencyKey: resolvedChildCall.idempotencyKey
|
|
729
|
+
};
|
|
730
|
+
return this.invokeChildWorkflowStep(childWorkflowInvocation);
|
|
655
731
|
}
|
|
656
732
|
};
|
|
657
733
|
let step = { ...baseStep };
|
|
@@ -664,7 +740,9 @@ class WorkflowEngine {
|
|
|
664
740
|
input: run.input,
|
|
665
741
|
workflowId: run.workflowId,
|
|
666
742
|
runId: run.id,
|
|
667
|
-
timeline
|
|
743
|
+
get timeline() {
|
|
744
|
+
return run?.timeline ?? {};
|
|
745
|
+
},
|
|
668
746
|
logger: this.logger,
|
|
669
747
|
step
|
|
670
748
|
};
|
|
@@ -676,7 +754,7 @@ class WorkflowEngine {
|
|
|
676
754
|
const shouldComplete = run.status === "running" /* RUNNING */ && (noParsedSteps || isLastParsedStep || hasPluginSteps && result !== undefined);
|
|
677
755
|
if (shouldComplete) {
|
|
678
756
|
const normalizedResult = result === undefined ? {} : result;
|
|
679
|
-
await this.updateRun({
|
|
757
|
+
const completedRun = await this.updateRun({
|
|
680
758
|
runId,
|
|
681
759
|
resourceId: scopedResourceId,
|
|
682
760
|
data: {
|
|
@@ -686,6 +764,7 @@ class WorkflowEngine {
|
|
|
686
764
|
jobId: job?.id
|
|
687
765
|
}
|
|
688
766
|
});
|
|
767
|
+
await this.notifyParentOfChildTerminalRun(completedRun);
|
|
689
768
|
this.logger.log("Workflow run completed.", {
|
|
690
769
|
runId,
|
|
691
770
|
workflowId
|
|
@@ -693,7 +772,7 @@ class WorkflowEngine {
|
|
|
693
772
|
}
|
|
694
773
|
} catch (error) {
|
|
695
774
|
if (runId) {
|
|
696
|
-
await this.updateRun({
|
|
775
|
+
const updatedRun = await this.updateRun({
|
|
697
776
|
runId,
|
|
698
777
|
resourceId: scopedResourceId,
|
|
699
778
|
data: {
|
|
@@ -701,6 +780,9 @@ class WorkflowEngine {
|
|
|
701
780
|
jobId: job?.id
|
|
702
781
|
}
|
|
703
782
|
});
|
|
783
|
+
if (updatedRun.status === "completed" /* COMPLETED */ || updatedRun.status === "failed" /* FAILED */ || updatedRun.status === "cancelled" /* CANCELLED */) {
|
|
784
|
+
await this.notifyParentOfChildTerminalRun(updatedRun);
|
|
785
|
+
}
|
|
704
786
|
}
|
|
705
787
|
throw error;
|
|
706
788
|
}
|
|
@@ -712,7 +794,7 @@ class WorkflowEngine {
|
|
|
712
794
|
const run = await getWorkflowRun({ runId }, { db: this.db });
|
|
713
795
|
if (!run || run.status !== "running" /* RUNNING */)
|
|
714
796
|
return;
|
|
715
|
-
await this.updateRun({
|
|
797
|
+
const failedRun = await this.updateRun({
|
|
716
798
|
runId,
|
|
717
799
|
resourceId: run.resourceId ?? undefined,
|
|
718
800
|
data: {
|
|
@@ -720,6 +802,7 @@ class WorkflowEngine {
|
|
|
720
802
|
error: run.error ?? "Workflow run worker died or job expired before completion"
|
|
721
803
|
}
|
|
722
804
|
});
|
|
805
|
+
await this.notifyParentOfChildTerminalRun(failedRun);
|
|
723
806
|
this.logger.log("Marked stuck workflow run as failed", {
|
|
724
807
|
runId,
|
|
725
808
|
workflowId: run.workflowId
|
|
@@ -730,9 +813,195 @@ class WorkflowEngine {
|
|
|
730
813
|
return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
|
|
731
814
|
}
|
|
732
815
|
getWaitForStepEntry(timeline, stepId) {
|
|
733
|
-
const entry = timeline[
|
|
816
|
+
const entry = timeline[waitForTimelineKey(stepId)];
|
|
734
817
|
return entry && typeof entry === "object" && "waitFor" in entry ? entry : null;
|
|
735
818
|
}
|
|
819
|
+
getInvokeChildWorkflowStepEntry(timeline, stepId) {
|
|
820
|
+
const entry = timeline[invokeChildWorkflowTimelineKey(stepId)];
|
|
821
|
+
return isInvokeChildWorkflowTimelineEntry(entry) ? entry : null;
|
|
822
|
+
}
|
|
823
|
+
getCompletedChildOutput(childRun) {
|
|
824
|
+
return childRun.output === undefined ? {} : childRun.output;
|
|
825
|
+
}
|
|
826
|
+
throwForNonCompletedChild(childRun) {
|
|
827
|
+
throw new WorkflowEngineError(`Child workflow ${childRun.workflowId} ${childRun.status}${childRun.error ? `: ${childRun.error}` : ""}`, childRun.workflowId, childRun.id);
|
|
828
|
+
}
|
|
829
|
+
assertInvokeChildWorkflowStepOwnership({
|
|
830
|
+
childRun,
|
|
831
|
+
parentRun,
|
|
832
|
+
stepId,
|
|
833
|
+
workflowId
|
|
834
|
+
}) {
|
|
835
|
+
const expectedParentResourceId = parentRun.resourceId ?? null;
|
|
836
|
+
const matches = childRun.workflowId === workflowId && childRun.parentRunId === parentRun.id && childRun.parentStepId === stepId && childRun.parentResourceId === expectedParentResourceId;
|
|
837
|
+
if (!matches) {
|
|
838
|
+
throw new WorkflowEngineError(`Idempotency key resolved to workflow run ${childRun.id}, which does not belong to invokeChildWorkflow step '${stepId}'`, workflowId, parentRun.id);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async invokeChildWorkflowStep({
|
|
842
|
+
run,
|
|
843
|
+
stepId,
|
|
844
|
+
workflowId,
|
|
845
|
+
input,
|
|
846
|
+
resourceId,
|
|
847
|
+
idempotencyKey,
|
|
848
|
+
options
|
|
849
|
+
}) {
|
|
850
|
+
let invokeOutput;
|
|
851
|
+
let hasInvokeOutput = false;
|
|
852
|
+
const childResourceId = resourceId ?? run.resourceId ?? undefined;
|
|
853
|
+
const childIdempotencyKey = idempotencyKey;
|
|
854
|
+
await withPostgresTransaction(this.db, async (db) => {
|
|
855
|
+
const lockedRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
|
|
856
|
+
if (lockedRun.status === "cancelled" /* CANCELLED */ || lockedRun.status === "paused" /* PAUSED */ || lockedRun.status === "failed" /* FAILED */) {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
const lockedCached = this.getCachedStepEntry(lockedRun.timeline, stepId);
|
|
860
|
+
if (lockedCached?.output !== undefined) {
|
|
861
|
+
invokeOutput = lockedCached.output;
|
|
862
|
+
hasInvokeOutput = true;
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const lockedInvoke = this.getInvokeChildWorkflowStepEntry(lockedRun.timeline, stepId);
|
|
866
|
+
if (lockedInvoke) {
|
|
867
|
+
const existingChildResourceId = "childResourceId" in lockedInvoke.invokeChildWorkflow ? lockedInvoke.invokeChildWorkflow.childResourceId ?? undefined : childResourceId;
|
|
868
|
+
const existingChildRun = await this.getRun({
|
|
869
|
+
runId: lockedInvoke.invokeChildWorkflow.childRunId,
|
|
870
|
+
resourceId: existingChildResourceId
|
|
871
|
+
});
|
|
872
|
+
if (existingChildRun.status === "completed" /* COMPLETED */) {
|
|
873
|
+
invokeOutput = this.getCompletedChildOutput(existingChildRun);
|
|
874
|
+
hasInvokeOutput = true;
|
|
875
|
+
await this.updateRun({
|
|
876
|
+
runId: run.id,
|
|
877
|
+
resourceId: run.resourceId ?? undefined,
|
|
878
|
+
data: {
|
|
879
|
+
timeline: merge(lockedRun.timeline, {
|
|
880
|
+
[stepId]: {
|
|
881
|
+
output: invokeOutput,
|
|
882
|
+
timestamp: new Date
|
|
883
|
+
}
|
|
884
|
+
})
|
|
885
|
+
}
|
|
886
|
+
}, { db });
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (existingChildRun.status === "failed" /* FAILED */ || existingChildRun.status === "cancelled" /* CANCELLED */) {
|
|
890
|
+
this.throwForNonCompletedChild(existingChildRun);
|
|
891
|
+
}
|
|
892
|
+
await this.pauseRunForWait({
|
|
893
|
+
run: lockedRun,
|
|
894
|
+
stepId,
|
|
895
|
+
eventName: getInvokeChildWorkflowEventName(existingChildRun.id),
|
|
896
|
+
skipOutput: true,
|
|
897
|
+
db
|
|
898
|
+
});
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
const result = await this.createWorkflowRun({
|
|
902
|
+
workflowId,
|
|
903
|
+
input,
|
|
904
|
+
resourceId: childResourceId,
|
|
905
|
+
idempotencyKey: childIdempotencyKey,
|
|
906
|
+
options,
|
|
907
|
+
parentRunId: run.id,
|
|
908
|
+
parentStepId: stepId,
|
|
909
|
+
parentResourceId: run.resourceId ?? undefined,
|
|
910
|
+
enqueue: true,
|
|
911
|
+
db
|
|
912
|
+
});
|
|
913
|
+
const childRun = result.run;
|
|
914
|
+
if (!result.created) {
|
|
915
|
+
this.assertInvokeChildWorkflowStepOwnership({
|
|
916
|
+
childRun,
|
|
917
|
+
parentRun: lockedRun,
|
|
918
|
+
stepId,
|
|
919
|
+
workflowId
|
|
920
|
+
});
|
|
921
|
+
if (childRun.status === "completed" /* COMPLETED */) {
|
|
922
|
+
invokeOutput = this.getCompletedChildOutput(childRun);
|
|
923
|
+
hasInvokeOutput = true;
|
|
924
|
+
await this.updateRun({
|
|
925
|
+
runId: run.id,
|
|
926
|
+
resourceId: run.resourceId ?? undefined,
|
|
927
|
+
data: {
|
|
928
|
+
timeline: merge(lockedRun.timeline, {
|
|
929
|
+
[invokeChildWorkflowTimelineKey(stepId)]: {
|
|
930
|
+
invokeChildWorkflow: {
|
|
931
|
+
childRunId: childRun.id,
|
|
932
|
+
childWorkflowId: childRun.workflowId,
|
|
933
|
+
childResourceId: childRun.resourceId
|
|
934
|
+
},
|
|
935
|
+
timestamp: new Date
|
|
936
|
+
},
|
|
937
|
+
[stepId]: {
|
|
938
|
+
output: invokeOutput,
|
|
939
|
+
timestamp: new Date
|
|
940
|
+
}
|
|
941
|
+
})
|
|
942
|
+
}
|
|
943
|
+
}, { db });
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
if (childRun.status === "failed" /* FAILED */ || childRun.status === "cancelled" /* CANCELLED */) {
|
|
947
|
+
this.throwForNonCompletedChild(childRun);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
await this.pauseRunForWait({
|
|
951
|
+
run: lockedRun,
|
|
952
|
+
stepId,
|
|
953
|
+
eventName: getInvokeChildWorkflowEventName(childRun.id),
|
|
954
|
+
skipOutput: true,
|
|
955
|
+
db,
|
|
956
|
+
timeline: merge(lockedRun.timeline, {
|
|
957
|
+
[invokeChildWorkflowTimelineKey(stepId)]: {
|
|
958
|
+
invokeChildWorkflow: {
|
|
959
|
+
childRunId: childRun.id,
|
|
960
|
+
childWorkflowId: childRun.workflowId,
|
|
961
|
+
childResourceId: childRun.resourceId
|
|
962
|
+
},
|
|
963
|
+
timestamp: new Date
|
|
964
|
+
}
|
|
965
|
+
})
|
|
966
|
+
});
|
|
967
|
+
}, this.pool);
|
|
968
|
+
if (hasInvokeOutput) {
|
|
969
|
+
return invokeOutput;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
async pauseRunForWait({
|
|
973
|
+
run,
|
|
974
|
+
stepId,
|
|
975
|
+
eventName,
|
|
976
|
+
timeoutEvent,
|
|
977
|
+
skipOutput,
|
|
978
|
+
db,
|
|
979
|
+
timeline
|
|
980
|
+
}) {
|
|
981
|
+
const baseTimeline = timeline ?? run.timeline;
|
|
982
|
+
const waitFor = {};
|
|
983
|
+
if (eventName)
|
|
984
|
+
waitFor.eventName = eventName;
|
|
985
|
+
if (timeoutEvent)
|
|
986
|
+
waitFor.timeoutEvent = timeoutEvent;
|
|
987
|
+
if (skipOutput)
|
|
988
|
+
waitFor.skipOutput = true;
|
|
989
|
+
await this.updateRun({
|
|
990
|
+
runId: run.id,
|
|
991
|
+
resourceId: run.resourceId ?? undefined,
|
|
992
|
+
data: {
|
|
993
|
+
status: "paused" /* PAUSED */,
|
|
994
|
+
currentStepId: stepId,
|
|
995
|
+
pausedAt: new Date,
|
|
996
|
+
timeline: merge(baseTimeline, {
|
|
997
|
+
[waitForTimelineKey(stepId)]: {
|
|
998
|
+
waitFor,
|
|
999
|
+
timestamp: new Date
|
|
1000
|
+
}
|
|
1001
|
+
})
|
|
1002
|
+
}
|
|
1003
|
+
}, { db });
|
|
1004
|
+
}
|
|
736
1005
|
async runStep({
|
|
737
1006
|
stepId,
|
|
738
1007
|
run,
|
|
@@ -770,7 +1039,7 @@ class WorkflowEngine {
|
|
|
770
1039
|
if (output === undefined) {
|
|
771
1040
|
output = {};
|
|
772
1041
|
}
|
|
773
|
-
|
|
1042
|
+
const updated = await this.updateRun({
|
|
774
1043
|
runId: run.id,
|
|
775
1044
|
resourceId: run.resourceId ?? undefined,
|
|
776
1045
|
data: {
|
|
@@ -782,6 +1051,7 @@ class WorkflowEngine {
|
|
|
782
1051
|
})
|
|
783
1052
|
}
|
|
784
1053
|
}, { db });
|
|
1054
|
+
Object.assign(run, updated);
|
|
785
1055
|
return output;
|
|
786
1056
|
} catch (error) {
|
|
787
1057
|
this.logger.error(`Step ${stepId} failed:`, error, {
|
|
@@ -821,21 +1091,7 @@ ${error.stack}` : String(error)
|
|
|
821
1091
|
const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
|
|
822
1092
|
await withPostgresTransaction(this.db, async (db) => {
|
|
823
1093
|
const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
|
|
824
|
-
return this.
|
|
825
|
-
runId: run.id,
|
|
826
|
-
resourceId: run.resourceId ?? undefined,
|
|
827
|
-
data: {
|
|
828
|
-
status: "paused" /* PAUSED */,
|
|
829
|
-
currentStepId: stepId,
|
|
830
|
-
pausedAt: new Date,
|
|
831
|
-
timeline: merge(freshRun.timeline, {
|
|
832
|
-
[`${stepId}-wait-for`]: {
|
|
833
|
-
waitFor: { eventName, timeoutEvent },
|
|
834
|
-
timestamp: new Date
|
|
835
|
-
}
|
|
836
|
-
})
|
|
837
|
-
}
|
|
838
|
-
}, { db });
|
|
1094
|
+
return this.pauseRunForWait({ run: freshRun, stepId, eventName, timeoutEvent, db });
|
|
839
1095
|
}, this.pool);
|
|
840
1096
|
if (timeoutDate && timeoutEvent) {
|
|
841
1097
|
try {
|
|
@@ -949,7 +1205,7 @@ ${error.stack}` : String(error)
|
|
|
949
1205
|
pausedAt: new Date,
|
|
950
1206
|
timeline: merge(freshRun.timeline, {
|
|
951
1207
|
[`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
|
|
952
|
-
[
|
|
1208
|
+
[waitForTimelineKey(stepId)]: {
|
|
953
1209
|
waitFor: { timeoutEvent: pollEvent, skipOutput: true },
|
|
954
1210
|
timestamp: new Date
|
|
955
1211
|
}
|
|
@@ -1025,17 +1281,13 @@ ${error.stack}` : String(error)
|
|
|
1025
1281
|
}
|
|
1026
1282
|
export {
|
|
1027
1283
|
workflow,
|
|
1028
|
-
validateWorkflowId,
|
|
1029
|
-
validateResourceId,
|
|
1030
|
-
parseDuration,
|
|
1031
1284
|
createWorkflowRef,
|
|
1032
1285
|
WorkflowStatus,
|
|
1033
1286
|
WorkflowRunNotFoundError,
|
|
1034
1287
|
WorkflowEngineError,
|
|
1035
1288
|
WorkflowEngine,
|
|
1036
|
-
WorkflowClient
|
|
1037
|
-
StepType
|
|
1289
|
+
WorkflowClient
|
|
1038
1290
|
};
|
|
1039
1291
|
|
|
1040
|
-
//# debugId=
|
|
1292
|
+
//# debugId=F9D6596295A7020B64756E2164756E21
|
|
1041
1293
|
//# sourceMappingURL=index.js.map
|