pg-workflows 0.1.3 → 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 +340 -166
- package/dist/index.d.cts +105 -41
- package/dist/index.d.ts +105 -41
- package/dist/index.js +310 -157
- 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")) {
|
|
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,51 +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 ({ date }) => {
|
|
775
|
-
return this.waitUntil(runId, date);
|
|
776
|
-
},
|
|
777
|
-
pause: async (stepId) => {
|
|
778
|
-
if (!run) {
|
|
779
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
780
|
-
}
|
|
781
|
-
return this.pauseStep({
|
|
782
|
-
stepId,
|
|
783
|
-
run
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
}
|
|
858
|
+
step
|
|
787
859
|
};
|
|
788
860
|
const result = await workflow2.handler(context);
|
|
789
861
|
run = await this.getRun({ runId, resourceId });
|
|
790
|
-
|
|
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) {
|
|
791
867
|
const normalizedResult = result === undefined ? {} : result;
|
|
792
868
|
await this.updateRun({
|
|
793
869
|
runId,
|
|
@@ -839,6 +915,10 @@ class WorkflowEngine {
|
|
|
839
915
|
throw error;
|
|
840
916
|
}
|
|
841
917
|
}
|
|
918
|
+
getCachedStepEntry(timeline, stepId) {
|
|
919
|
+
const stepEntry = timeline[stepId];
|
|
920
|
+
return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
|
|
921
|
+
}
|
|
842
922
|
async runStep({
|
|
843
923
|
stepId,
|
|
844
924
|
run,
|
|
@@ -857,39 +937,38 @@ class WorkflowEngine {
|
|
|
857
937
|
return;
|
|
858
938
|
}
|
|
859
939
|
try {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
run = await this.updateRun({
|
|
879
|
-
runId: run.id,
|
|
880
|
-
resourceId: run.resourceId ?? undefined,
|
|
881
|
-
data: {
|
|
882
|
-
timeline: merge(run.timeline, {
|
|
883
|
-
[stepId]: {
|
|
884
|
-
output: result === undefined ? {} : result,
|
|
885
|
-
timestamp: new Date
|
|
886
|
-
}
|
|
887
|
-
})
|
|
888
|
-
}
|
|
889
|
-
}, { db });
|
|
940
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
941
|
+
if (cached?.output !== undefined) {
|
|
942
|
+
return cached.output;
|
|
943
|
+
}
|
|
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 = {};
|
|
890
958
|
}
|
|
891
|
-
|
|
892
|
-
|
|
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;
|
|
893
972
|
} catch (error) {
|
|
894
973
|
this.logger.error(`Step ${stepId} failed:`, error, {
|
|
895
974
|
runId: run.id,
|
|
@@ -908,60 +987,133 @@ ${error.stack}` : String(error)
|
|
|
908
987
|
}
|
|
909
988
|
});
|
|
910
989
|
}
|
|
911
|
-
async
|
|
990
|
+
async waitStep({
|
|
912
991
|
run,
|
|
913
992
|
stepId,
|
|
914
993
|
eventName,
|
|
915
|
-
|
|
994
|
+
timeoutDate
|
|
916
995
|
}) {
|
|
917
996
|
const persistedRun = await this.getRun({
|
|
918
997
|
runId: run.id,
|
|
919
998
|
resourceId: run.resourceId ?? undefined
|
|
920
999
|
});
|
|
921
1000
|
if (persistedRun.status === "cancelled" /* CANCELLED */ || persistedRun.status === "paused" /* PAUSED */ || persistedRun.status === "failed" /* FAILED */) {
|
|
922
|
-
this.logger.log(`Step ${stepId} skipped, workflow run is ${persistedRun.status}`, {
|
|
923
|
-
runId: run.id,
|
|
924
|
-
workflowId: run.workflowId
|
|
925
|
-
});
|
|
926
1001
|
return;
|
|
927
1002
|
}
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
return timelineStepCheck.output;
|
|
1003
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
1004
|
+
if (cached?.output !== undefined) {
|
|
1005
|
+
return cached.timedOut ? undefined : cached.output;
|
|
932
1006
|
}
|
|
1007
|
+
const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
|
|
933
1008
|
await this.updateRun({
|
|
934
1009
|
runId: run.id,
|
|
935
1010
|
resourceId: run.resourceId ?? undefined,
|
|
936
1011
|
data: {
|
|
937
1012
|
status: "paused" /* PAUSED */,
|
|
938
1013
|
currentStepId: stepId,
|
|
1014
|
+
pausedAt: new Date,
|
|
939
1015
|
timeline: merge(run.timeline, {
|
|
940
1016
|
[`${stepId}-wait-for`]: {
|
|
941
|
-
waitFor: {
|
|
942
|
-
eventName,
|
|
943
|
-
timeout
|
|
944
|
-
},
|
|
1017
|
+
waitFor: { eventName, timeoutEvent },
|
|
945
1018
|
timestamp: new Date
|
|
946
1019
|
}
|
|
947
|
-
})
|
|
948
|
-
|
|
1020
|
+
})
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
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 });
|
|
1037
|
+
}
|
|
1038
|
+
async pollStep({
|
|
1039
|
+
run,
|
|
1040
|
+
stepId,
|
|
1041
|
+
conditionFn,
|
|
1042
|
+
intervalMs,
|
|
1043
|
+
timeoutMs
|
|
1044
|
+
}) {
|
|
1045
|
+
const persistedRun = await this.getRun({
|
|
1046
|
+
runId: run.id,
|
|
1047
|
+
resourceId: run.resourceId ?? undefined
|
|
1048
|
+
});
|
|
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
|
+
})
|
|
949
1100
|
}
|
|
950
1101
|
});
|
|
951
|
-
this.
|
|
1102
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, {
|
|
952
1103
|
runId: run.id,
|
|
953
|
-
|
|
1104
|
+
resourceId: run.resourceId ?? undefined,
|
|
1105
|
+
workflowId: run.workflowId,
|
|
1106
|
+
input: run.input,
|
|
1107
|
+
event: { name: pollEvent, data: {} }
|
|
1108
|
+
}, {
|
|
1109
|
+
startAfter: new Date(Date.now() + intervalMs),
|
|
1110
|
+
expireInSeconds: defaultExpireInSeconds
|
|
954
1111
|
});
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
run,
|
|
959
|
-
stepId,
|
|
960
|
-
eventName: PAUSE_EVENT_NAME
|
|
1112
|
+
this.logger.log(`Step ${stepId} polling every ${intervalMs}ms...`, {
|
|
1113
|
+
runId: run.id,
|
|
1114
|
+
workflowId: run.workflowId
|
|
961
1115
|
});
|
|
962
|
-
|
|
963
|
-
async waitUntil(runId, _date) {
|
|
964
|
-
throw new WorkflowEngineError("Not implemented yet", undefined, runId);
|
|
1116
|
+
return { timedOut: false, data: undefined };
|
|
965
1117
|
}
|
|
966
1118
|
async checkIfHasStarted() {
|
|
967
1119
|
if (!this._started) {
|
|
@@ -1002,6 +1154,7 @@ ${error.stack}` : String(error)
|
|
|
1002
1154
|
}
|
|
1003
1155
|
export {
|
|
1004
1156
|
workflow,
|
|
1157
|
+
parseDuration,
|
|
1005
1158
|
WorkflowStatus,
|
|
1006
1159
|
WorkflowRunNotFoundError,
|
|
1007
1160
|
WorkflowEngineError,
|
|
@@ -1009,5 +1162,5 @@ export {
|
|
|
1009
1162
|
StepType
|
|
1010
1163
|
};
|
|
1011
1164
|
|
|
1012
|
-
//# debugId=
|
|
1165
|
+
//# debugId=4ADA0797255A878364756E2164756E21
|
|
1013
1166
|
//# sourceMappingURL=index.js.map
|