pg-workflows 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/dist/index.cjs +285 -135
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +285 -135
- package/dist/index.js.map +5 -5
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -211,6 +211,11 @@ declare class WorkflowEngine {
|
|
|
211
211
|
expireInSeconds?: number;
|
|
212
212
|
};
|
|
213
213
|
}): Promise<WorkflowRun>;
|
|
214
|
+
fastForwardWorkflow({ runId, resourceId, data }: {
|
|
215
|
+
runId: string;
|
|
216
|
+
resourceId?: string;
|
|
217
|
+
data?: Record<string, unknown>;
|
|
218
|
+
}): Promise<WorkflowRun>;
|
|
214
219
|
cancelWorkflow({ runId, resourceId }: {
|
|
215
220
|
runId: string;
|
|
216
221
|
resourceId?: string;
|
|
@@ -231,10 +236,11 @@ declare class WorkflowEngine {
|
|
|
231
236
|
exclusiveLock?: boolean;
|
|
232
237
|
db?: Db;
|
|
233
238
|
}): Promise<WorkflowRun>;
|
|
234
|
-
updateRun({ runId, resourceId, data }: {
|
|
239
|
+
updateRun({ runId, resourceId, data, expectedStatuses }: {
|
|
235
240
|
runId: string;
|
|
236
241
|
resourceId?: string;
|
|
237
242
|
data: Partial<WorkflowRun>;
|
|
243
|
+
expectedStatuses?: string[];
|
|
238
244
|
}, { db }?: {
|
|
239
245
|
db?: Db;
|
|
240
246
|
}): Promise<WorkflowRun>;
|
|
@@ -251,6 +257,7 @@ declare class WorkflowEngine {
|
|
|
251
257
|
private resolveScopedResourceId;
|
|
252
258
|
private handleWorkflowRun;
|
|
253
259
|
private getCachedStepEntry;
|
|
260
|
+
private getWaitForStepEntry;
|
|
254
261
|
private runStep;
|
|
255
262
|
private waitStep;
|
|
256
263
|
private pollStep;
|
package/dist/index.js
CHANGED
|
@@ -195,15 +195,23 @@ async function runMigrations(db) {
|
|
|
195
195
|
job_id varchar(256)
|
|
196
196
|
);
|
|
197
197
|
`, []);
|
|
198
|
-
await db.executeSql(`
|
|
199
|
-
CREATE INDEX workflow_runs_workflow_id_idx ON workflow_runs USING btree (workflow_id);
|
|
200
|
-
`, []);
|
|
201
198
|
await db.executeSql(`
|
|
202
199
|
CREATE INDEX workflow_runs_created_at_idx ON workflow_runs USING btree (created_at);
|
|
200
|
+
CREATE INDEX workflow_runs_resource_id_created_at_idx ON workflow_runs USING btree (resource_id, created_at DESC);
|
|
201
|
+
CREATE INDEX workflow_runs_status_created_at_idx ON workflow_runs USING btree (status, created_at DESC);
|
|
202
|
+
CREATE INDEX workflow_runs_workflow_id_created_at_idx ON workflow_runs USING btree (workflow_id, created_at DESC);
|
|
203
|
+
CREATE INDEX workflow_runs_resource_id_workflow_id_created_at_idx ON workflow_runs USING btree (resource_id, workflow_id, created_at DESC);
|
|
203
204
|
`, []);
|
|
205
|
+
} else {
|
|
204
206
|
await db.executeSql(`
|
|
205
|
-
|
|
206
|
-
|
|
207
|
+
DROP INDEX IF EXISTS workflow_runs_workflow_id_idx;
|
|
208
|
+
DROP INDEX IF EXISTS workflow_runs_resource_id_idx;
|
|
209
|
+
CREATE INDEX IF NOT EXISTS workflow_runs_created_at_idx ON workflow_runs USING btree (created_at);
|
|
210
|
+
CREATE INDEX IF NOT EXISTS workflow_runs_resource_id_created_at_idx ON workflow_runs USING btree (resource_id, created_at DESC);
|
|
211
|
+
CREATE INDEX IF NOT EXISTS workflow_runs_status_created_at_idx ON workflow_runs USING btree (status, created_at DESC);
|
|
212
|
+
CREATE INDEX IF NOT EXISTS workflow_runs_workflow_id_created_at_idx ON workflow_runs USING btree (workflow_id, created_at DESC);
|
|
213
|
+
CREATE INDEX IF NOT EXISTS workflow_runs_resource_id_workflow_id_created_at_idx ON workflow_runs USING btree (resource_id, workflow_id, created_at DESC);
|
|
214
|
+
`, []);
|
|
207
215
|
}
|
|
208
216
|
}
|
|
209
217
|
|
|
@@ -246,13 +254,13 @@ async function insertWorkflowRun({
|
|
|
246
254
|
const runId = generateKSUID("run");
|
|
247
255
|
const now = new Date;
|
|
248
256
|
const result = await db.executeSql(`INSERT INTO workflow_runs (
|
|
249
|
-
id,
|
|
250
|
-
resource_id,
|
|
251
|
-
workflow_id,
|
|
252
|
-
current_step_id,
|
|
253
|
-
status,
|
|
254
|
-
input,
|
|
255
|
-
max_retries,
|
|
257
|
+
id,
|
|
258
|
+
resource_id,
|
|
259
|
+
workflow_id,
|
|
260
|
+
current_step_id,
|
|
261
|
+
status,
|
|
262
|
+
input,
|
|
263
|
+
max_retries,
|
|
256
264
|
timeout_at,
|
|
257
265
|
created_at,
|
|
258
266
|
updated_at,
|
|
@@ -299,7 +307,8 @@ async function getWorkflowRun({
|
|
|
299
307
|
async function updateWorkflowRun({
|
|
300
308
|
runId,
|
|
301
309
|
resourceId,
|
|
302
|
-
data
|
|
310
|
+
data,
|
|
311
|
+
expectedStatuses
|
|
303
312
|
}, db) {
|
|
304
313
|
const now = new Date;
|
|
305
314
|
const updates = ["updated_at = $1"];
|
|
@@ -355,10 +364,20 @@ async function updateWorkflowRun({
|
|
|
355
364
|
values.push(data.jobId);
|
|
356
365
|
paramIndex++;
|
|
357
366
|
}
|
|
358
|
-
const whereClause = resourceId ? `WHERE id = $${paramIndex} AND resource_id = $${paramIndex + 1}` : `WHERE id = $${paramIndex}`;
|
|
359
367
|
values.push(runId);
|
|
368
|
+
const idParam = paramIndex;
|
|
369
|
+
paramIndex++;
|
|
360
370
|
if (resourceId) {
|
|
361
371
|
values.push(resourceId);
|
|
372
|
+
paramIndex++;
|
|
373
|
+
}
|
|
374
|
+
if (expectedStatuses && expectedStatuses.length > 0) {
|
|
375
|
+
values.push(expectedStatuses);
|
|
376
|
+
paramIndex++;
|
|
377
|
+
}
|
|
378
|
+
let whereClause = resourceId ? `WHERE id = $${idParam} AND resource_id = $${idParam + 1}` : `WHERE id = $${idParam}`;
|
|
379
|
+
if (expectedStatuses && expectedStatuses.length > 0) {
|
|
380
|
+
whereClause += ` AND status = ANY($${paramIndex - 1})`;
|
|
362
381
|
}
|
|
363
382
|
const query = `
|
|
364
383
|
UPDATE workflow_runs
|
|
@@ -399,37 +418,50 @@ async function getWorkflowRuns({
|
|
|
399
418
|
values.push(workflowId);
|
|
400
419
|
paramIndex++;
|
|
401
420
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
421
|
+
const cursorIds = [startingAfter, endingBefore].filter(Boolean);
|
|
422
|
+
if (cursorIds.length > 0) {
|
|
423
|
+
const cursorResult = await db.executeSql("SELECT id, created_at FROM workflow_runs WHERE id = ANY($1)", [cursorIds]);
|
|
424
|
+
const cursorMap = new Map;
|
|
425
|
+
for (const row of cursorResult.rows) {
|
|
426
|
+
cursorMap.set(row.id, typeof row.created_at === "string" ? new Date(row.created_at) : row.created_at);
|
|
408
427
|
}
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
428
|
+
if (startingAfter) {
|
|
429
|
+
const cursor = cursorMap.get(startingAfter);
|
|
430
|
+
if (cursor) {
|
|
431
|
+
conditions.push(`created_at < $${paramIndex}`);
|
|
432
|
+
values.push(cursor);
|
|
433
|
+
paramIndex++;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (endingBefore) {
|
|
437
|
+
const cursor = cursorMap.get(endingBefore);
|
|
438
|
+
if (cursor) {
|
|
439
|
+
conditions.push(`created_at > $${paramIndex}`);
|
|
440
|
+
values.push(cursor);
|
|
441
|
+
paramIndex++;
|
|
442
|
+
}
|
|
416
443
|
}
|
|
417
444
|
}
|
|
418
445
|
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
419
446
|
const actualLimit = Math.min(Math.max(limit, 1), 100) + 1;
|
|
447
|
+
const isBackward = !!endingBefore && !startingAfter;
|
|
420
448
|
const query = `
|
|
421
449
|
SELECT * FROM workflow_runs
|
|
422
450
|
${whereClause}
|
|
423
|
-
ORDER BY created_at DESC
|
|
451
|
+
ORDER BY created_at ${isBackward ? "ASC" : "DESC"}
|
|
424
452
|
LIMIT $${paramIndex}
|
|
425
453
|
`;
|
|
426
454
|
values.push(actualLimit);
|
|
427
455
|
const result = await db.executeSql(query, values);
|
|
428
456
|
const rows = result.rows;
|
|
429
|
-
const
|
|
430
|
-
const rawItems =
|
|
457
|
+
const hasExtraRow = rows.length > (limit ?? 20);
|
|
458
|
+
const rawItems = hasExtraRow ? rows.slice(0, limit) : rows;
|
|
459
|
+
if (isBackward) {
|
|
460
|
+
rawItems.reverse();
|
|
461
|
+
}
|
|
431
462
|
const items = rawItems.map((row) => mapRowToWorkflowRun(row));
|
|
432
|
-
const
|
|
463
|
+
const hasMore = isBackward ? items.length > 0 : hasExtraRow;
|
|
464
|
+
const hasPrev = isBackward ? hasExtraRow : !!startingAfter && items.length > 0;
|
|
433
465
|
const nextCursor = hasMore && items.length > 0 ? items[items.length - 1]?.id ?? null : null;
|
|
434
466
|
const prevCursor = hasPrev && items.length > 0 ? items[0]?.id ?? null : null;
|
|
435
467
|
return { items, nextCursor, prevCursor, hasMore, hasPrev };
|
|
@@ -635,7 +667,8 @@ class WorkflowEngine {
|
|
|
635
667
|
data: {
|
|
636
668
|
status: "paused" /* PAUSED */,
|
|
637
669
|
pausedAt: new Date
|
|
638
|
-
}
|
|
670
|
+
},
|
|
671
|
+
expectedStatuses: ["running" /* RUNNING */, "pending" /* PENDING */]
|
|
639
672
|
});
|
|
640
673
|
this.logger.log("Paused workflow run", {
|
|
641
674
|
runId,
|
|
@@ -649,6 +682,10 @@ class WorkflowEngine {
|
|
|
649
682
|
options
|
|
650
683
|
}) {
|
|
651
684
|
await this.checkIfHasStarted();
|
|
685
|
+
const current = await this.getRun({ runId, resourceId });
|
|
686
|
+
if (current.status !== "paused" /* PAUSED */) {
|
|
687
|
+
throw new WorkflowEngineError(`Cannot resume workflow run in '${current.status}' status, must be 'paused'`, current.workflowId, runId);
|
|
688
|
+
}
|
|
652
689
|
return this.triggerEvent({
|
|
653
690
|
runId,
|
|
654
691
|
resourceId,
|
|
@@ -657,6 +694,51 @@ class WorkflowEngine {
|
|
|
657
694
|
options
|
|
658
695
|
});
|
|
659
696
|
}
|
|
697
|
+
async fastForwardWorkflow({
|
|
698
|
+
runId,
|
|
699
|
+
resourceId,
|
|
700
|
+
data
|
|
701
|
+
}) {
|
|
702
|
+
await this.checkIfHasStarted();
|
|
703
|
+
const run = await this.getRun({ runId, resourceId });
|
|
704
|
+
if (run.status !== "paused" /* PAUSED */) {
|
|
705
|
+
return run;
|
|
706
|
+
}
|
|
707
|
+
const stepId = run.currentStepId;
|
|
708
|
+
const waitForStep = this.getWaitForStepEntry(run.timeline, stepId);
|
|
709
|
+
if (!waitForStep) {
|
|
710
|
+
return run;
|
|
711
|
+
}
|
|
712
|
+
const { eventName, timeoutEvent, skipOutput } = waitForStep.waitFor;
|
|
713
|
+
if (eventName === PAUSE_EVENT_NAME) {
|
|
714
|
+
return this.resumeWorkflow({ runId, resourceId });
|
|
715
|
+
}
|
|
716
|
+
if (skipOutput && timeoutEvent) {
|
|
717
|
+
await withPostgresTransaction(this.db, async (db) => {
|
|
718
|
+
const freshRun = await this.getRun({ runId, resourceId }, { exclusiveLock: true, db });
|
|
719
|
+
return this.updateRun({
|
|
720
|
+
runId,
|
|
721
|
+
resourceId,
|
|
722
|
+
data: {
|
|
723
|
+
timeline: merge(freshRun.timeline, {
|
|
724
|
+
[stepId]: {
|
|
725
|
+
output: data ?? {},
|
|
726
|
+
timestamp: new Date
|
|
727
|
+
}
|
|
728
|
+
})
|
|
729
|
+
}
|
|
730
|
+
}, { db });
|
|
731
|
+
}, this.pool);
|
|
732
|
+
return this.triggerEvent({ runId, resourceId, eventName: timeoutEvent });
|
|
733
|
+
}
|
|
734
|
+
if (eventName) {
|
|
735
|
+
return this.triggerEvent({ runId, resourceId, eventName, data: data ?? {} });
|
|
736
|
+
}
|
|
737
|
+
if (timeoutEvent) {
|
|
738
|
+
return this.triggerEvent({ runId, resourceId, eventName: timeoutEvent, data: data ?? {} });
|
|
739
|
+
}
|
|
740
|
+
return run;
|
|
741
|
+
}
|
|
660
742
|
async cancelWorkflow({
|
|
661
743
|
runId,
|
|
662
744
|
resourceId
|
|
@@ -667,7 +749,8 @@ class WorkflowEngine {
|
|
|
667
749
|
resourceId,
|
|
668
750
|
data: {
|
|
669
751
|
status: "cancelled" /* CANCELLED */
|
|
670
|
-
}
|
|
752
|
+
},
|
|
753
|
+
expectedStatuses: ["pending" /* PENDING */, "running" /* RUNNING */, "paused" /* PAUSED */]
|
|
671
754
|
});
|
|
672
755
|
this.logger.log(`cancelled workflow run with id ${runId}`);
|
|
673
756
|
return run;
|
|
@@ -692,7 +775,7 @@ class WorkflowEngine {
|
|
|
692
775
|
data
|
|
693
776
|
}
|
|
694
777
|
};
|
|
695
|
-
this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
778
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
696
779
|
expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds
|
|
697
780
|
});
|
|
698
781
|
this.logger.log(`event ${eventName} sent for workflow run with id ${runId}`);
|
|
@@ -708,10 +791,17 @@ class WorkflowEngine {
|
|
|
708
791
|
async updateRun({
|
|
709
792
|
runId,
|
|
710
793
|
resourceId,
|
|
711
|
-
data
|
|
794
|
+
data,
|
|
795
|
+
expectedStatuses
|
|
712
796
|
}, { db } = {}) {
|
|
713
|
-
const run = await updateWorkflowRun({ runId, resourceId, data }, db ?? this.db);
|
|
797
|
+
const run = await updateWorkflowRun({ runId, resourceId, data, expectedStatuses }, db ?? this.db);
|
|
714
798
|
if (!run) {
|
|
799
|
+
if (expectedStatuses) {
|
|
800
|
+
const current = await getWorkflowRun({ runId, resourceId }, { db: db ?? this.db });
|
|
801
|
+
if (current) {
|
|
802
|
+
throw new WorkflowEngineError(`Cannot update workflow run in '${current.status}' status, expected: ${expectedStatuses.join(", ")}`, current.workflowId, runId);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
715
805
|
throw new WorkflowRunNotFoundError(runId);
|
|
716
806
|
}
|
|
717
807
|
return run;
|
|
@@ -794,36 +884,40 @@ class WorkflowEngine {
|
|
|
794
884
|
throw new WorkflowEngineError("Missing current step id", workflowId, runId);
|
|
795
885
|
}
|
|
796
886
|
if (run.status === "paused" /* PAUSED */) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
const
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
887
|
+
run = await withPostgresTransaction(this.db, async (db) => {
|
|
888
|
+
const lockedRun = await this.getRun({ runId, resourceId: scopedResourceId }, { exclusiveLock: true, db });
|
|
889
|
+
if (lockedRun.status !== "paused" /* PAUSED */) {
|
|
890
|
+
return lockedRun;
|
|
891
|
+
}
|
|
892
|
+
const waitForStep = this.getWaitForStepEntry(lockedRun.timeline, lockedRun.currentStepId);
|
|
893
|
+
const currentStep = this.getCachedStepEntry(lockedRun.timeline, lockedRun.currentStepId);
|
|
894
|
+
const waitFor = waitForStep?.waitFor;
|
|
895
|
+
const hasCurrentStepOutput = currentStep?.output !== undefined;
|
|
896
|
+
const eventMatches = waitFor && event?.name && (event.name === waitFor.eventName || event.name === waitFor.timeoutEvent) && !hasCurrentStepOutput;
|
|
897
|
+
if (eventMatches) {
|
|
898
|
+
const isTimeout = event?.name === waitFor?.timeoutEvent;
|
|
899
|
+
const skipOutput = waitFor?.skipOutput;
|
|
900
|
+
return this.updateRun({
|
|
901
|
+
runId,
|
|
902
|
+
resourceId: scopedResourceId,
|
|
903
|
+
data: {
|
|
904
|
+
status: "running" /* RUNNING */,
|
|
905
|
+
pausedAt: null,
|
|
906
|
+
resumedAt: new Date,
|
|
907
|
+
jobId: job?.id,
|
|
908
|
+
...skipOutput ? {} : {
|
|
909
|
+
timeline: merge(lockedRun.timeline, {
|
|
910
|
+
[lockedRun.currentStepId]: {
|
|
911
|
+
output: event?.data ?? {},
|
|
912
|
+
...isTimeout ? { timedOut: true } : {},
|
|
913
|
+
timestamp: new Date
|
|
914
|
+
}
|
|
915
|
+
})
|
|
916
|
+
}
|
|
822
917
|
}
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
run = await this.updateRun({
|
|
918
|
+
}, { db });
|
|
919
|
+
}
|
|
920
|
+
return this.updateRun({
|
|
827
921
|
runId,
|
|
828
922
|
resourceId: scopedResourceId,
|
|
829
923
|
data: {
|
|
@@ -832,8 +926,8 @@ class WorkflowEngine {
|
|
|
832
926
|
resumedAt: new Date,
|
|
833
927
|
jobId: job?.id
|
|
834
928
|
}
|
|
835
|
-
});
|
|
836
|
-
}
|
|
929
|
+
}, { db });
|
|
930
|
+
}, this.pool);
|
|
837
931
|
}
|
|
838
932
|
const baseStep = {
|
|
839
933
|
run: async (stepId, handler) => {
|
|
@@ -963,6 +1057,10 @@ class WorkflowEngine {
|
|
|
963
1057
|
const stepEntry = timeline[stepId];
|
|
964
1058
|
return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
|
|
965
1059
|
}
|
|
1060
|
+
getWaitForStepEntry(timeline, stepId) {
|
|
1061
|
+
const entry = timeline[`${stepId}-wait-for`];
|
|
1062
|
+
return entry && typeof entry === "object" && "waitFor" in entry ? entry : null;
|
|
1063
|
+
}
|
|
966
1064
|
async runStep({
|
|
967
1065
|
stepId,
|
|
968
1066
|
run,
|
|
@@ -1004,7 +1102,7 @@ class WorkflowEngine {
|
|
|
1004
1102
|
runId: run.id,
|
|
1005
1103
|
resourceId: run.resourceId ?? undefined,
|
|
1006
1104
|
data: {
|
|
1007
|
-
timeline: merge(
|
|
1105
|
+
timeline: merge(persistedRun.timeline, {
|
|
1008
1106
|
[stepId]: {
|
|
1009
1107
|
output,
|
|
1010
1108
|
timestamp: new Date
|
|
@@ -1049,33 +1147,45 @@ ${error.stack}` : String(error)
|
|
|
1049
1147
|
return cached.timedOut ? undefined : cached.output;
|
|
1050
1148
|
}
|
|
1051
1149
|
const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
|
|
1052
|
-
await this.
|
|
1053
|
-
runId: run.id,
|
|
1054
|
-
|
|
1055
|
-
data: {
|
|
1056
|
-
status: "paused" /* PAUSED */,
|
|
1057
|
-
currentStepId: stepId,
|
|
1058
|
-
pausedAt: new Date,
|
|
1059
|
-
timeline: merge(run.timeline, {
|
|
1060
|
-
[`${stepId}-wait-for`]: {
|
|
1061
|
-
waitFor: { eventName, timeoutEvent },
|
|
1062
|
-
timestamp: new Date
|
|
1063
|
-
}
|
|
1064
|
-
})
|
|
1065
|
-
}
|
|
1066
|
-
});
|
|
1067
|
-
if (timeoutDate && timeoutEvent) {
|
|
1068
|
-
const job = {
|
|
1150
|
+
await withPostgresTransaction(this.db, async (db) => {
|
|
1151
|
+
const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
|
|
1152
|
+
return this.updateRun({
|
|
1069
1153
|
runId: run.id,
|
|
1070
1154
|
resourceId: run.resourceId ?? undefined,
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1155
|
+
data: {
|
|
1156
|
+
status: "paused" /* PAUSED */,
|
|
1157
|
+
currentStepId: stepId,
|
|
1158
|
+
pausedAt: new Date,
|
|
1159
|
+
timeline: merge(freshRun.timeline, {
|
|
1160
|
+
[`${stepId}-wait-for`]: {
|
|
1161
|
+
waitFor: { eventName, timeoutEvent },
|
|
1162
|
+
timestamp: new Date
|
|
1163
|
+
}
|
|
1164
|
+
})
|
|
1165
|
+
}
|
|
1166
|
+
}, { db });
|
|
1167
|
+
}, this.pool);
|
|
1168
|
+
if (timeoutDate && timeoutEvent) {
|
|
1169
|
+
try {
|
|
1170
|
+
const job = {
|
|
1171
|
+
runId: run.id,
|
|
1172
|
+
resourceId: run.resourceId ?? undefined,
|
|
1173
|
+
workflowId: run.workflowId,
|
|
1174
|
+
input: run.input,
|
|
1175
|
+
event: { name: timeoutEvent, data: { date: timeoutDate.toISOString() } }
|
|
1176
|
+
};
|
|
1177
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
1178
|
+
startAfter: timeoutDate.getTime() <= Date.now() ? new Date : timeoutDate,
|
|
1179
|
+
expireInSeconds: defaultExpireInSeconds
|
|
1180
|
+
});
|
|
1181
|
+
} catch (error) {
|
|
1182
|
+
await this.updateRun({
|
|
1183
|
+
runId: run.id,
|
|
1184
|
+
resourceId: run.resourceId ?? undefined,
|
|
1185
|
+
data: { status: "running" /* RUNNING */, pausedAt: null }
|
|
1186
|
+
});
|
|
1187
|
+
throw error;
|
|
1188
|
+
}
|
|
1079
1189
|
}
|
|
1080
1190
|
this.logger.log(`Step ${stepId} waiting${eventName ? ` for event "${eventName}"` : ""}${timeoutDate ? ` until ${timeoutDate.toISOString()}` : ""}`, { runId: run.id, workflowId: run.workflowId });
|
|
1081
1191
|
}
|
|
@@ -1100,59 +1210,99 @@ ${error.stack}` : String(error)
|
|
|
1100
1210
|
const pollStateEntry = persistedRun.timeline[`${stepId}-poll`];
|
|
1101
1211
|
const startedAt = pollStateEntry && typeof pollStateEntry === "object" && "startedAt" in pollStateEntry ? new Date(pollStateEntry.startedAt) : new Date;
|
|
1102
1212
|
if (timeoutMs !== undefined && Date.now() >= startedAt.getTime() + timeoutMs) {
|
|
1103
|
-
await this.
|
|
1213
|
+
await withPostgresTransaction(this.db, async (db) => {
|
|
1214
|
+
const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
|
|
1215
|
+
return this.updateRun({
|
|
1216
|
+
runId: run.id,
|
|
1217
|
+
resourceId: run.resourceId ?? undefined,
|
|
1218
|
+
data: {
|
|
1219
|
+
currentStepId: stepId,
|
|
1220
|
+
timeline: merge(freshRun.timeline, {
|
|
1221
|
+
[stepId]: { output: {}, timedOut: true, timestamp: new Date }
|
|
1222
|
+
})
|
|
1223
|
+
}
|
|
1224
|
+
}, { db });
|
|
1225
|
+
}, this.pool);
|
|
1226
|
+
return { timedOut: true };
|
|
1227
|
+
}
|
|
1228
|
+
let result;
|
|
1229
|
+
try {
|
|
1230
|
+
result = await conditionFn();
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
this.logger.error(`Poll conditionFn for step ${stepId} threw an error, treating as non-match and continuing to poll`, error, { runId: run.id, workflowId: run.workflowId });
|
|
1233
|
+
if (timeoutMs !== undefined && Date.now() >= startedAt.getTime() + timeoutMs) {
|
|
1234
|
+
await withPostgresTransaction(this.db, async (db) => {
|
|
1235
|
+
const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
|
|
1236
|
+
return this.updateRun({
|
|
1237
|
+
runId: run.id,
|
|
1238
|
+
resourceId: run.resourceId ?? undefined,
|
|
1239
|
+
data: {
|
|
1240
|
+
currentStepId: stepId,
|
|
1241
|
+
timeline: merge(freshRun.timeline, {
|
|
1242
|
+
[stepId]: { output: {}, timedOut: true, timestamp: new Date }
|
|
1243
|
+
})
|
|
1244
|
+
}
|
|
1245
|
+
}, { db });
|
|
1246
|
+
}, this.pool);
|
|
1247
|
+
return { timedOut: true };
|
|
1248
|
+
}
|
|
1249
|
+
result = false;
|
|
1250
|
+
}
|
|
1251
|
+
if (result !== false) {
|
|
1252
|
+
await withPostgresTransaction(this.db, async (db) => {
|
|
1253
|
+
const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
|
|
1254
|
+
return this.updateRun({
|
|
1255
|
+
runId: run.id,
|
|
1256
|
+
resourceId: run.resourceId ?? undefined,
|
|
1257
|
+
data: {
|
|
1258
|
+
currentStepId: stepId,
|
|
1259
|
+
timeline: merge(freshRun.timeline, {
|
|
1260
|
+
[stepId]: { output: result, timestamp: new Date }
|
|
1261
|
+
})
|
|
1262
|
+
}
|
|
1263
|
+
}, { db });
|
|
1264
|
+
}, this.pool);
|
|
1265
|
+
return { timedOut: false, data: result };
|
|
1266
|
+
}
|
|
1267
|
+
const pollEvent = `__poll_${stepId}`;
|
|
1268
|
+
await withPostgresTransaction(this.db, async (db) => {
|
|
1269
|
+
const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
|
|
1270
|
+
return this.updateRun({
|
|
1104
1271
|
runId: run.id,
|
|
1105
1272
|
resourceId: run.resourceId ?? undefined,
|
|
1106
1273
|
data: {
|
|
1274
|
+
status: "paused" /* PAUSED */,
|
|
1107
1275
|
currentStepId: stepId,
|
|
1108
|
-
|
|
1109
|
-
|
|
1276
|
+
pausedAt: new Date,
|
|
1277
|
+
timeline: merge(freshRun.timeline, {
|
|
1278
|
+
[`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
|
|
1279
|
+
[`${stepId}-wait-for`]: {
|
|
1280
|
+
waitFor: { timeoutEvent: pollEvent, skipOutput: true },
|
|
1281
|
+
timestamp: new Date
|
|
1282
|
+
}
|
|
1110
1283
|
})
|
|
1111
1284
|
}
|
|
1285
|
+
}, { db });
|
|
1286
|
+
}, this.pool);
|
|
1287
|
+
try {
|
|
1288
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, {
|
|
1289
|
+
runId: run.id,
|
|
1290
|
+
resourceId: run.resourceId ?? undefined,
|
|
1291
|
+
workflowId: run.workflowId,
|
|
1292
|
+
input: run.input,
|
|
1293
|
+
event: { name: pollEvent, data: {} }
|
|
1294
|
+
}, {
|
|
1295
|
+
startAfter: new Date(Date.now() + intervalMs),
|
|
1296
|
+
expireInSeconds: defaultExpireInSeconds
|
|
1112
1297
|
});
|
|
1113
|
-
|
|
1114
|
-
}
|
|
1115
|
-
const result = await conditionFn();
|
|
1116
|
-
if (result !== false) {
|
|
1298
|
+
} catch (error) {
|
|
1117
1299
|
await this.updateRun({
|
|
1118
1300
|
runId: run.id,
|
|
1119
1301
|
resourceId: run.resourceId ?? undefined,
|
|
1120
|
-
data: {
|
|
1121
|
-
currentStepId: stepId,
|
|
1122
|
-
timeline: merge(persistedRun.timeline, {
|
|
1123
|
-
[stepId]: { output: result, timestamp: new Date }
|
|
1124
|
-
})
|
|
1125
|
-
}
|
|
1302
|
+
data: { status: "running" /* RUNNING */, pausedAt: null }
|
|
1126
1303
|
});
|
|
1127
|
-
|
|
1304
|
+
throw error;
|
|
1128
1305
|
}
|
|
1129
|
-
const pollEvent = `__poll_${stepId}`;
|
|
1130
|
-
await this.updateRun({
|
|
1131
|
-
runId: run.id,
|
|
1132
|
-
resourceId: run.resourceId ?? undefined,
|
|
1133
|
-
data: {
|
|
1134
|
-
status: "paused" /* PAUSED */,
|
|
1135
|
-
currentStepId: stepId,
|
|
1136
|
-
pausedAt: new Date,
|
|
1137
|
-
timeline: merge(persistedRun.timeline, {
|
|
1138
|
-
[`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
|
|
1139
|
-
[`${stepId}-wait-for`]: {
|
|
1140
|
-
waitFor: { timeoutEvent: pollEvent, skipOutput: true },
|
|
1141
|
-
timestamp: new Date
|
|
1142
|
-
}
|
|
1143
|
-
})
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
|
-
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, {
|
|
1147
|
-
runId: run.id,
|
|
1148
|
-
resourceId: run.resourceId ?? undefined,
|
|
1149
|
-
workflowId: run.workflowId,
|
|
1150
|
-
input: run.input,
|
|
1151
|
-
event: { name: pollEvent, data: {} }
|
|
1152
|
-
}, {
|
|
1153
|
-
startAfter: new Date(Date.now() + intervalMs),
|
|
1154
|
-
expireInSeconds: defaultExpireInSeconds
|
|
1155
|
-
});
|
|
1156
1306
|
this.logger.log(`Step ${stepId} polling every ${intervalMs}ms...`, {
|
|
1157
1307
|
runId: run.id,
|
|
1158
1308
|
workflowId: run.workflowId
|
|
@@ -1206,5 +1356,5 @@ export {
|
|
|
1206
1356
|
StepType
|
|
1207
1357
|
};
|
|
1208
1358
|
|
|
1209
|
-
//# debugId=
|
|
1359
|
+
//# debugId=0EF998205E7ED0B464756E2164756E21
|
|
1210
1360
|
//# sourceMappingURL=index.js.map
|