pg-workflows 0.2.0 → 0.3.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/README.md +67 -2
- package/dist/index.cjs +329 -189
- package/dist/index.d.cts +105 -41
- package/dist/index.d.ts +105 -41
- package/dist/index.js +299 -180
- package/dist/index.js.map +10 -9
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,18 +1,98 @@
|
|
|
1
1
|
// src/definition.ts
|
|
2
|
-
function
|
|
3
|
-
|
|
2
|
+
function createWorkflowFactory(plugins = []) {
|
|
3
|
+
const factory = (id, handler, { inputSchema, timeout, retries } = {}) => ({
|
|
4
4
|
id,
|
|
5
5
|
handler,
|
|
6
6
|
inputSchema,
|
|
7
7
|
timeout,
|
|
8
|
-
retries
|
|
9
|
-
|
|
8
|
+
retries,
|
|
9
|
+
plugins: plugins.length > 0 ? plugins : undefined
|
|
10
|
+
});
|
|
11
|
+
factory.use = (plugin) => createWorkflowFactory([
|
|
12
|
+
...plugins,
|
|
13
|
+
plugin
|
|
14
|
+
]);
|
|
15
|
+
return factory;
|
|
16
|
+
}
|
|
17
|
+
var workflow = createWorkflowFactory();
|
|
18
|
+
// src/duration.ts
|
|
19
|
+
import parse from "parse-duration";
|
|
20
|
+
|
|
21
|
+
// src/error.ts
|
|
22
|
+
class WorkflowEngineError extends Error {
|
|
23
|
+
workflowId;
|
|
24
|
+
runId;
|
|
25
|
+
cause;
|
|
26
|
+
constructor(message, workflowId, runId, cause = undefined) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.workflowId = workflowId;
|
|
29
|
+
this.runId = runId;
|
|
30
|
+
this.cause = cause;
|
|
31
|
+
this.name = "WorkflowEngineError";
|
|
32
|
+
if (Error.captureStackTrace) {
|
|
33
|
+
Error.captureStackTrace(this, WorkflowEngineError);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
39
|
+
constructor(runId, workflowId) {
|
|
40
|
+
super("Workflow run not found", workflowId, runId);
|
|
41
|
+
this.name = "WorkflowRunNotFoundError";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/duration.ts
|
|
46
|
+
var MS_PER_SECOND = 1000;
|
|
47
|
+
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
48
|
+
var MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
49
|
+
var MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
50
|
+
var MS_PER_WEEK = 7 * MS_PER_DAY;
|
|
51
|
+
function parseDuration(duration) {
|
|
52
|
+
if (typeof duration === "string") {
|
|
53
|
+
if (duration.trim() === "") {
|
|
54
|
+
throw new WorkflowEngineError("Invalid duration: empty string");
|
|
55
|
+
}
|
|
56
|
+
const ms2 = parse(duration);
|
|
57
|
+
if (ms2 == null || ms2 <= 0) {
|
|
58
|
+
throw new WorkflowEngineError(`Invalid duration: "${duration}"`);
|
|
59
|
+
}
|
|
60
|
+
return ms2;
|
|
61
|
+
}
|
|
62
|
+
const { weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0 } = duration;
|
|
63
|
+
const ms = weeks * MS_PER_WEEK + days * MS_PER_DAY + hours * MS_PER_HOUR + minutes * MS_PER_MINUTE + seconds * MS_PER_SECOND;
|
|
64
|
+
if (ms <= 0) {
|
|
65
|
+
throw new WorkflowEngineError("Invalid duration: must be a positive value");
|
|
66
|
+
}
|
|
67
|
+
return ms;
|
|
10
68
|
}
|
|
11
69
|
// src/engine.ts
|
|
12
70
|
import { merge } from "es-toolkit";
|
|
13
71
|
|
|
14
72
|
// src/ast-parser.ts
|
|
15
73
|
import * as ts from "typescript";
|
|
74
|
+
|
|
75
|
+
// src/types.ts
|
|
76
|
+
var WorkflowStatus;
|
|
77
|
+
((WorkflowStatus2) => {
|
|
78
|
+
WorkflowStatus2["PENDING"] = "pending";
|
|
79
|
+
WorkflowStatus2["RUNNING"] = "running";
|
|
80
|
+
WorkflowStatus2["PAUSED"] = "paused";
|
|
81
|
+
WorkflowStatus2["COMPLETED"] = "completed";
|
|
82
|
+
WorkflowStatus2["FAILED"] = "failed";
|
|
83
|
+
WorkflowStatus2["CANCELLED"] = "cancelled";
|
|
84
|
+
})(WorkflowStatus ||= {});
|
|
85
|
+
var StepType;
|
|
86
|
+
((StepType2) => {
|
|
87
|
+
StepType2["PAUSE"] = "pause";
|
|
88
|
+
StepType2["RUN"] = "run";
|
|
89
|
+
StepType2["WAIT_FOR"] = "waitFor";
|
|
90
|
+
StepType2["WAIT_UNTIL"] = "waitUntil";
|
|
91
|
+
StepType2["DELAY"] = "delay";
|
|
92
|
+
StepType2["POLL"] = "poll";
|
|
93
|
+
})(StepType ||= {});
|
|
94
|
+
|
|
95
|
+
// src/ast-parser.ts
|
|
16
96
|
function parseWorkflowHandler(handler) {
|
|
17
97
|
const handlerSource = handler.toString();
|
|
18
98
|
const sourceFile = ts.createSourceFile("handler.ts", handlerSource, ts.ScriptTarget.Latest, true);
|
|
@@ -56,13 +136,14 @@ function parseWorkflowHandler(handler) {
|
|
|
56
136
|
const propertyAccess = node.expression;
|
|
57
137
|
const objectName = propertyAccess.expression.getText(sourceFile);
|
|
58
138
|
const methodName = propertyAccess.name.text;
|
|
59
|
-
if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil")) {
|
|
139
|
+
if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll")) {
|
|
60
140
|
const firstArg = node.arguments[0];
|
|
61
141
|
if (firstArg) {
|
|
62
142
|
const { id, isDynamic } = extractStepId(firstArg);
|
|
143
|
+
const stepType = methodName === "sleep" ? "delay" /* DELAY */ : methodName;
|
|
63
144
|
const stepDefinition = {
|
|
64
145
|
id,
|
|
65
|
-
type:
|
|
146
|
+
type: stepType,
|
|
66
147
|
conditional: isInConditional(node),
|
|
67
148
|
loop: isInLoop(node),
|
|
68
149
|
isDynamic
|
|
@@ -363,48 +444,6 @@ async function withPostgresTransaction(db, callback) {
|
|
|
363
444
|
}
|
|
364
445
|
}
|
|
365
446
|
|
|
366
|
-
// src/error.ts
|
|
367
|
-
class WorkflowEngineError extends Error {
|
|
368
|
-
workflowId;
|
|
369
|
-
runId;
|
|
370
|
-
cause;
|
|
371
|
-
constructor(message, workflowId, runId, cause = undefined) {
|
|
372
|
-
super(message);
|
|
373
|
-
this.workflowId = workflowId;
|
|
374
|
-
this.runId = runId;
|
|
375
|
-
this.cause = cause;
|
|
376
|
-
this.name = "WorkflowEngineError";
|
|
377
|
-
if (Error.captureStackTrace) {
|
|
378
|
-
Error.captureStackTrace(this, WorkflowEngineError);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
384
|
-
constructor(runId, workflowId) {
|
|
385
|
-
super("Workflow run not found", workflowId, runId);
|
|
386
|
-
this.name = "WorkflowRunNotFoundError";
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// src/types.ts
|
|
391
|
-
var WorkflowStatus;
|
|
392
|
-
((WorkflowStatus2) => {
|
|
393
|
-
WorkflowStatus2["PENDING"] = "pending";
|
|
394
|
-
WorkflowStatus2["RUNNING"] = "running";
|
|
395
|
-
WorkflowStatus2["PAUSED"] = "paused";
|
|
396
|
-
WorkflowStatus2["COMPLETED"] = "completed";
|
|
397
|
-
WorkflowStatus2["FAILED"] = "failed";
|
|
398
|
-
WorkflowStatus2["CANCELLED"] = "cancelled";
|
|
399
|
-
})(WorkflowStatus ||= {});
|
|
400
|
-
var StepType;
|
|
401
|
-
((StepType2) => {
|
|
402
|
-
StepType2["PAUSE"] = "pause";
|
|
403
|
-
StepType2["RUN"] = "run";
|
|
404
|
-
StepType2["WAIT_FOR"] = "waitFor";
|
|
405
|
-
StepType2["WAIT_UNTIL"] = "waitUntil";
|
|
406
|
-
})(StepType ||= {});
|
|
407
|
-
|
|
408
447
|
// src/engine.ts
|
|
409
448
|
var PAUSE_EVENT_NAME = "__internal_pause";
|
|
410
449
|
var WORKFLOW_RUN_QUEUE_NAME = "workflow-run";
|
|
@@ -413,7 +452,9 @@ var StepTypeToIcon = {
|
|
|
413
452
|
["run" /* RUN */]: "λ",
|
|
414
453
|
["waitFor" /* WAIT_FOR */]: "○",
|
|
415
454
|
["pause" /* PAUSE */]: "⏸",
|
|
416
|
-
["waitUntil" /* WAIT_UNTIL */]: "⏲"
|
|
455
|
+
["waitUntil" /* WAIT_UNTIL */]: "⏲",
|
|
456
|
+
["delay" /* DELAY */]: "⏱",
|
|
457
|
+
["poll" /* POLL */]: "↻"
|
|
417
458
|
};
|
|
418
459
|
var defaultLogger = {
|
|
419
460
|
log: (_message) => console.warn(_message),
|
|
@@ -513,7 +554,9 @@ class WorkflowEngine {
|
|
|
513
554
|
if (!workflow2) {
|
|
514
555
|
throw new WorkflowEngineError(`Unknown workflow ${workflowId}`);
|
|
515
556
|
}
|
|
516
|
-
|
|
557
|
+
const hasSteps = workflow2.steps.length > 0 && workflow2.steps[0];
|
|
558
|
+
const hasPlugins = (workflow2.plugins?.length ?? 0) > 0;
|
|
559
|
+
if (!hasSteps && !hasPlugins) {
|
|
517
560
|
throw new WorkflowEngineError(`Workflow ${workflowId} has no steps`, workflowId);
|
|
518
561
|
}
|
|
519
562
|
if (workflow2.inputSchema) {
|
|
@@ -522,7 +565,7 @@ class WorkflowEngine {
|
|
|
522
565
|
throw new WorkflowEngineError(result.error.message, workflowId);
|
|
523
566
|
}
|
|
524
567
|
}
|
|
525
|
-
const initialStepId = workflow2.steps[0]?.id;
|
|
568
|
+
const initialStepId = workflow2.steps[0]?.id ?? "__start__";
|
|
526
569
|
const run = await withPostgresTransaction(this.boss.getDb(), async (_db) => {
|
|
527
570
|
const timeoutAt = options?.timeout ? new Date(Date.now() + options.timeout) : workflow2.timeout ? new Date(Date.now() + workflow2.timeout) : null;
|
|
528
571
|
const insertedRun = await insertWorkflowRun({
|
|
@@ -709,11 +752,13 @@ class WorkflowEngine {
|
|
|
709
752
|
if (run.status === "paused" /* PAUSED */) {
|
|
710
753
|
const waitForStepEntry = run.timeline[`${run.currentStepId}-wait-for`];
|
|
711
754
|
const waitForStep = waitForStepEntry && typeof waitForStepEntry === "object" && "waitFor" in waitForStepEntry ? waitForStepEntry : null;
|
|
712
|
-
const
|
|
713
|
-
const currentStep = currentStepEntry && typeof currentStepEntry === "object" && "output" in currentStepEntry ? currentStepEntry : null;
|
|
755
|
+
const currentStep = this.getCachedStepEntry(run.timeline, run.currentStepId);
|
|
714
756
|
const waitFor = waitForStep?.waitFor;
|
|
715
757
|
const hasCurrentStepOutput = currentStep?.output !== undefined;
|
|
716
|
-
|
|
758
|
+
const eventMatches = waitFor && event?.name && (event.name === waitFor.eventName || event.name === waitFor.timeoutEvent) && !hasCurrentStepOutput;
|
|
759
|
+
if (eventMatches) {
|
|
760
|
+
const isTimeout = event?.name === waitFor?.timeoutEvent;
|
|
761
|
+
const skipOutput = waitFor?.skipOutput;
|
|
717
762
|
run = await this.updateRun({
|
|
718
763
|
runId,
|
|
719
764
|
resourceId,
|
|
@@ -721,13 +766,16 @@ class WorkflowEngine {
|
|
|
721
766
|
status: "running" /* RUNNING */,
|
|
722
767
|
pausedAt: null,
|
|
723
768
|
resumedAt: new Date,
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
769
|
+
jobId: job?.id,
|
|
770
|
+
...skipOutput ? {} : {
|
|
771
|
+
timeline: merge(run.timeline, {
|
|
772
|
+
[run.currentStepId]: {
|
|
773
|
+
output: event?.data ?? {},
|
|
774
|
+
...isTimeout ? { timedOut: true } : {},
|
|
775
|
+
timestamp: new Date
|
|
776
|
+
}
|
|
777
|
+
})
|
|
778
|
+
}
|
|
731
779
|
}
|
|
732
780
|
});
|
|
733
781
|
} else {
|
|
@@ -743,58 +791,79 @@ class WorkflowEngine {
|
|
|
743
791
|
});
|
|
744
792
|
}
|
|
745
793
|
}
|
|
794
|
+
const baseStep = {
|
|
795
|
+
run: async (stepId, handler) => {
|
|
796
|
+
if (!run) {
|
|
797
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
798
|
+
}
|
|
799
|
+
return this.runStep({ stepId, run, handler });
|
|
800
|
+
},
|
|
801
|
+
waitFor: async (stepId, { eventName, timeout }) => {
|
|
802
|
+
if (!run) {
|
|
803
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
804
|
+
}
|
|
805
|
+
const timeoutDate = timeout ? new Date(Date.now() + timeout) : undefined;
|
|
806
|
+
return this.waitStep({ run, stepId, eventName, timeoutDate });
|
|
807
|
+
},
|
|
808
|
+
waitUntil: async (stepId, dateOrOptions) => {
|
|
809
|
+
if (!run) {
|
|
810
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
811
|
+
}
|
|
812
|
+
const date = dateOrOptions instanceof Date ? dateOrOptions : typeof dateOrOptions === "string" ? new Date(dateOrOptions) : dateOrOptions.date instanceof Date ? dateOrOptions.date : new Date(dateOrOptions.date);
|
|
813
|
+
await this.waitStep({ run, stepId, timeoutDate: date });
|
|
814
|
+
},
|
|
815
|
+
pause: async (stepId) => {
|
|
816
|
+
if (!run) {
|
|
817
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
818
|
+
}
|
|
819
|
+
await this.waitStep({ run, stepId, eventName: PAUSE_EVENT_NAME });
|
|
820
|
+
},
|
|
821
|
+
delay: async (stepId, duration) => {
|
|
822
|
+
if (!run) {
|
|
823
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
824
|
+
}
|
|
825
|
+
await this.waitStep({
|
|
826
|
+
run,
|
|
827
|
+
stepId,
|
|
828
|
+
timeoutDate: new Date(Date.now() + parseDuration(duration))
|
|
829
|
+
});
|
|
830
|
+
},
|
|
831
|
+
get sleep() {
|
|
832
|
+
return this.delay;
|
|
833
|
+
},
|
|
834
|
+
poll: async (stepId, conditionFn, options) => {
|
|
835
|
+
if (!run) {
|
|
836
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
837
|
+
}
|
|
838
|
+
const intervalMs = parseDuration(options?.interval ?? "30s");
|
|
839
|
+
if (intervalMs < 30000) {
|
|
840
|
+
throw new WorkflowEngineError(`step.poll interval must be at least 30s (got ${intervalMs}ms)`, workflowId, runId);
|
|
841
|
+
}
|
|
842
|
+
const timeoutMs = options?.timeout ? parseDuration(options.timeout) : undefined;
|
|
843
|
+
return this.pollStep({ run, stepId, conditionFn, intervalMs, timeoutMs });
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
let step = { ...baseStep };
|
|
847
|
+
const plugins = workflow2.plugins ?? [];
|
|
848
|
+
for (const plugin of plugins) {
|
|
849
|
+
const extra = plugin.methods(step);
|
|
850
|
+
step = { ...step, ...extra };
|
|
851
|
+
}
|
|
746
852
|
const context = {
|
|
747
853
|
input: run.input,
|
|
748
854
|
workflowId: run.workflowId,
|
|
749
855
|
runId: run.id,
|
|
750
856
|
timeline: run.timeline,
|
|
751
857
|
logger: this.logger,
|
|
752
|
-
step
|
|
753
|
-
run: async (stepId, handler) => {
|
|
754
|
-
if (!run) {
|
|
755
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
756
|
-
}
|
|
757
|
-
return this.runStep({
|
|
758
|
-
stepId,
|
|
759
|
-
run,
|
|
760
|
-
handler
|
|
761
|
-
});
|
|
762
|
-
},
|
|
763
|
-
waitFor: async (stepId, { eventName, timeout }) => {
|
|
764
|
-
if (!run) {
|
|
765
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
766
|
-
}
|
|
767
|
-
return this.waitForEvent({
|
|
768
|
-
run,
|
|
769
|
-
stepId,
|
|
770
|
-
eventName,
|
|
771
|
-
timeout
|
|
772
|
-
});
|
|
773
|
-
},
|
|
774
|
-
waitUntil: async (stepId, { date }) => {
|
|
775
|
-
if (!run) {
|
|
776
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
777
|
-
}
|
|
778
|
-
await this.waitUntilDate({
|
|
779
|
-
run,
|
|
780
|
-
stepId,
|
|
781
|
-
date
|
|
782
|
-
});
|
|
783
|
-
},
|
|
784
|
-
pause: async (stepId) => {
|
|
785
|
-
if (!run) {
|
|
786
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
787
|
-
}
|
|
788
|
-
return this.pauseStep({
|
|
789
|
-
stepId,
|
|
790
|
-
run
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
}
|
|
858
|
+
step
|
|
794
859
|
};
|
|
795
860
|
const result = await workflow2.handler(context);
|
|
796
861
|
run = await this.getRun({ runId, resourceId });
|
|
797
|
-
|
|
862
|
+
const isLastParsedStep = run.currentStepId === workflow2.steps[workflow2.steps.length - 1]?.id;
|
|
863
|
+
const hasPluginSteps = (workflow2.plugins?.length ?? 0) > 0;
|
|
864
|
+
const noParsedSteps = workflow2.steps.length === 0;
|
|
865
|
+
const shouldComplete = run.status === "running" /* RUNNING */ && (noParsedSteps || isLastParsedStep || hasPluginSteps && result !== undefined);
|
|
866
|
+
if (shouldComplete) {
|
|
798
867
|
const normalizedResult = result === undefined ? {} : result;
|
|
799
868
|
await this.updateRun({
|
|
800
869
|
runId,
|
|
@@ -846,6 +915,10 @@ class WorkflowEngine {
|
|
|
846
915
|
throw error;
|
|
847
916
|
}
|
|
848
917
|
}
|
|
918
|
+
getCachedStepEntry(timeline, stepId) {
|
|
919
|
+
const stepEntry = timeline[stepId];
|
|
920
|
+
return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
|
|
921
|
+
}
|
|
849
922
|
async runStep({
|
|
850
923
|
stepId,
|
|
851
924
|
run,
|
|
@@ -864,39 +937,38 @@ class WorkflowEngine {
|
|
|
864
937
|
return;
|
|
865
938
|
}
|
|
866
939
|
try {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
if (timelineStep?.output !== undefined) {
|
|
871
|
-
result = timelineStep.output;
|
|
872
|
-
} else {
|
|
873
|
-
await this.updateRun({
|
|
874
|
-
runId: run.id,
|
|
875
|
-
resourceId: run.resourceId ?? undefined,
|
|
876
|
-
data: {
|
|
877
|
-
currentStepId: stepId
|
|
878
|
-
}
|
|
879
|
-
}, { db });
|
|
880
|
-
this.logger.log(`Running step ${stepId}...`, {
|
|
881
|
-
runId: run.id,
|
|
882
|
-
workflowId: run.workflowId
|
|
883
|
-
});
|
|
884
|
-
result = await handler();
|
|
885
|
-
run = await this.updateRun({
|
|
886
|
-
runId: run.id,
|
|
887
|
-
resourceId: run.resourceId ?? undefined,
|
|
888
|
-
data: {
|
|
889
|
-
timeline: merge(run.timeline, {
|
|
890
|
-
[stepId]: {
|
|
891
|
-
output: result === undefined ? {} : result,
|
|
892
|
-
timestamp: new Date
|
|
893
|
-
}
|
|
894
|
-
})
|
|
895
|
-
}
|
|
896
|
-
}, { db });
|
|
940
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
941
|
+
if (cached?.output !== undefined) {
|
|
942
|
+
return cached.output;
|
|
897
943
|
}
|
|
898
|
-
|
|
899
|
-
|
|
944
|
+
await this.updateRun({
|
|
945
|
+
runId: run.id,
|
|
946
|
+
resourceId: run.resourceId ?? undefined,
|
|
947
|
+
data: {
|
|
948
|
+
currentStepId: stepId
|
|
949
|
+
}
|
|
950
|
+
}, { db });
|
|
951
|
+
this.logger.log(`Running step ${stepId}...`, {
|
|
952
|
+
runId: run.id,
|
|
953
|
+
workflowId: run.workflowId
|
|
954
|
+
});
|
|
955
|
+
let output = await handler();
|
|
956
|
+
if (output === undefined) {
|
|
957
|
+
output = {};
|
|
958
|
+
}
|
|
959
|
+
run = await this.updateRun({
|
|
960
|
+
runId: run.id,
|
|
961
|
+
resourceId: run.resourceId ?? undefined,
|
|
962
|
+
data: {
|
|
963
|
+
timeline: merge(run.timeline, {
|
|
964
|
+
[stepId]: {
|
|
965
|
+
output,
|
|
966
|
+
timestamp: new Date
|
|
967
|
+
}
|
|
968
|
+
})
|
|
969
|
+
}
|
|
970
|
+
}, { db });
|
|
971
|
+
return output;
|
|
900
972
|
} catch (error) {
|
|
901
973
|
this.logger.error(`Step ${stepId} failed:`, error, {
|
|
902
974
|
runId: run.id,
|
|
@@ -915,87 +987,133 @@ ${error.stack}` : String(error)
|
|
|
915
987
|
}
|
|
916
988
|
});
|
|
917
989
|
}
|
|
918
|
-
async
|
|
990
|
+
async waitStep({
|
|
919
991
|
run,
|
|
920
992
|
stepId,
|
|
921
993
|
eventName,
|
|
922
|
-
|
|
994
|
+
timeoutDate
|
|
923
995
|
}) {
|
|
924
996
|
const persistedRun = await this.getRun({
|
|
925
997
|
runId: run.id,
|
|
926
998
|
resourceId: run.resourceId ?? undefined
|
|
927
999
|
});
|
|
928
1000
|
if (persistedRun.status === "cancelled" /* CANCELLED */ || persistedRun.status === "paused" /* PAUSED */ || persistedRun.status === "failed" /* FAILED */) {
|
|
929
|
-
this.logger.log(`Step ${stepId} skipped, workflow run is ${persistedRun.status}`, {
|
|
930
|
-
runId: run.id,
|
|
931
|
-
workflowId: run.workflowId
|
|
932
|
-
});
|
|
933
1001
|
return;
|
|
934
1002
|
}
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
return timelineStepCheck.output;
|
|
1003
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
1004
|
+
if (cached?.output !== undefined) {
|
|
1005
|
+
return cached.timedOut ? undefined : cached.output;
|
|
939
1006
|
}
|
|
1007
|
+
const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
|
|
940
1008
|
await this.updateRun({
|
|
941
1009
|
runId: run.id,
|
|
942
1010
|
resourceId: run.resourceId ?? undefined,
|
|
943
1011
|
data: {
|
|
944
1012
|
status: "paused" /* PAUSED */,
|
|
945
1013
|
currentStepId: stepId,
|
|
1014
|
+
pausedAt: new Date,
|
|
946
1015
|
timeline: merge(run.timeline, {
|
|
947
1016
|
[`${stepId}-wait-for`]: {
|
|
948
|
-
waitFor: {
|
|
949
|
-
eventName,
|
|
950
|
-
timeout
|
|
951
|
-
},
|
|
1017
|
+
waitFor: { eventName, timeoutEvent },
|
|
952
1018
|
timestamp: new Date
|
|
953
1019
|
}
|
|
954
|
-
})
|
|
955
|
-
pausedAt: new Date
|
|
1020
|
+
})
|
|
956
1021
|
}
|
|
957
1022
|
});
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1023
|
+
if (timeoutDate && timeoutEvent) {
|
|
1024
|
+
const job = {
|
|
1025
|
+
runId: run.id,
|
|
1026
|
+
resourceId: run.resourceId ?? undefined,
|
|
1027
|
+
workflowId: run.workflowId,
|
|
1028
|
+
input: run.input,
|
|
1029
|
+
event: { name: timeoutEvent, data: { date: timeoutDate.toISOString() } }
|
|
1030
|
+
};
|
|
1031
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
1032
|
+
startAfter: timeoutDate.getTime() <= Date.now() ? new Date : timeoutDate,
|
|
1033
|
+
expireInSeconds: defaultExpireInSeconds
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
this.logger.log(`Step ${stepId} waiting${eventName ? ` for event "${eventName}"` : ""}${timeoutDate ? ` until ${timeoutDate.toISOString()}` : ""}`, { runId: run.id, workflowId: run.workflowId });
|
|
969
1037
|
}
|
|
970
|
-
async
|
|
1038
|
+
async pollStep({
|
|
971
1039
|
run,
|
|
972
1040
|
stepId,
|
|
973
|
-
|
|
1041
|
+
conditionFn,
|
|
1042
|
+
intervalMs,
|
|
1043
|
+
timeoutMs
|
|
974
1044
|
}) {
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
run
|
|
978
|
-
stepId,
|
|
979
|
-
eventName
|
|
1045
|
+
const persistedRun = await this.getRun({
|
|
1046
|
+
runId: run.id,
|
|
1047
|
+
resourceId: run.resourceId ?? undefined
|
|
980
1048
|
});
|
|
981
|
-
|
|
1049
|
+
if (persistedRun.status === "cancelled" /* CANCELLED */ || persistedRun.status === "paused" /* PAUSED */ || persistedRun.status === "failed" /* FAILED */) {
|
|
1050
|
+
return { timedOut: true };
|
|
1051
|
+
}
|
|
1052
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
1053
|
+
if (cached?.output !== undefined) {
|
|
1054
|
+
return cached.timedOut ? { timedOut: true } : { timedOut: false, data: cached.output };
|
|
1055
|
+
}
|
|
1056
|
+
const pollStateEntry = persistedRun.timeline[`${stepId}-poll`];
|
|
1057
|
+
const startedAt = pollStateEntry && typeof pollStateEntry === "object" && "startedAt" in pollStateEntry ? new Date(pollStateEntry.startedAt) : new Date;
|
|
1058
|
+
if (timeoutMs !== undefined && Date.now() >= startedAt.getTime() + timeoutMs) {
|
|
1059
|
+
await this.updateRun({
|
|
1060
|
+
runId: run.id,
|
|
1061
|
+
resourceId: run.resourceId ?? undefined,
|
|
1062
|
+
data: {
|
|
1063
|
+
currentStepId: stepId,
|
|
1064
|
+
timeline: merge(persistedRun.timeline, {
|
|
1065
|
+
[stepId]: { output: {}, timedOut: true, timestamp: new Date }
|
|
1066
|
+
})
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
return { timedOut: true };
|
|
1070
|
+
}
|
|
1071
|
+
const result = await conditionFn();
|
|
1072
|
+
if (result !== false) {
|
|
1073
|
+
await this.updateRun({
|
|
1074
|
+
runId: run.id,
|
|
1075
|
+
resourceId: run.resourceId ?? undefined,
|
|
1076
|
+
data: {
|
|
1077
|
+
currentStepId: stepId,
|
|
1078
|
+
timeline: merge(persistedRun.timeline, {
|
|
1079
|
+
[stepId]: { output: result, timestamp: new Date }
|
|
1080
|
+
})
|
|
1081
|
+
}
|
|
1082
|
+
});
|
|
1083
|
+
return { timedOut: false, data: result };
|
|
1084
|
+
}
|
|
1085
|
+
const pollEvent = `__poll_${stepId}`;
|
|
1086
|
+
await this.updateRun({
|
|
1087
|
+
runId: run.id,
|
|
1088
|
+
resourceId: run.resourceId ?? undefined,
|
|
1089
|
+
data: {
|
|
1090
|
+
status: "paused" /* PAUSED */,
|
|
1091
|
+
currentStepId: stepId,
|
|
1092
|
+
pausedAt: new Date,
|
|
1093
|
+
timeline: merge(persistedRun.timeline, {
|
|
1094
|
+
[`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
|
|
1095
|
+
[`${stepId}-wait-for`]: {
|
|
1096
|
+
waitFor: { timeoutEvent: pollEvent, skipOutput: true },
|
|
1097
|
+
timestamp: new Date
|
|
1098
|
+
}
|
|
1099
|
+
})
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, {
|
|
982
1103
|
runId: run.id,
|
|
983
1104
|
resourceId: run.resourceId ?? undefined,
|
|
984
1105
|
workflowId: run.workflowId,
|
|
985
1106
|
input: run.input,
|
|
986
|
-
event: {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
}
|
|
990
|
-
};
|
|
991
|
-
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
992
|
-
startAfter: date,
|
|
1107
|
+
event: { name: pollEvent, data: {} }
|
|
1108
|
+
}, {
|
|
1109
|
+
startAfter: new Date(Date.now() + intervalMs),
|
|
993
1110
|
expireInSeconds: defaultExpireInSeconds
|
|
994
1111
|
});
|
|
995
|
-
this.logger.log(`
|
|
1112
|
+
this.logger.log(`Step ${stepId} polling every ${intervalMs}ms...`, {
|
|
996
1113
|
runId: run.id,
|
|
997
1114
|
workflowId: run.workflowId
|
|
998
1115
|
});
|
|
1116
|
+
return { timedOut: false, data: undefined };
|
|
999
1117
|
}
|
|
1000
1118
|
async checkIfHasStarted() {
|
|
1001
1119
|
if (!this._started) {
|
|
@@ -1036,6 +1154,7 @@ ${error.stack}` : String(error)
|
|
|
1036
1154
|
}
|
|
1037
1155
|
export {
|
|
1038
1156
|
workflow,
|
|
1157
|
+
parseDuration,
|
|
1039
1158
|
WorkflowStatus,
|
|
1040
1159
|
WorkflowRunNotFoundError,
|
|
1041
1160
|
WorkflowEngineError,
|
|
@@ -1043,5 +1162,5 @@ export {
|
|
|
1043
1162
|
StepType
|
|
1044
1163
|
};
|
|
1045
1164
|
|
|
1046
|
-
//# debugId=
|
|
1165
|
+
//# debugId=4ADA0797255A878364756E2164756E21
|
|
1047
1166
|
//# sourceMappingURL=index.js.map
|