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.cjs
CHANGED
|
@@ -5,38 +5,59 @@ var __defProp = Object.defineProperty;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
|
-
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
20
33
|
var __toCommonJS = (from) => {
|
|
21
|
-
var entry = __moduleCache.get(from), desc;
|
|
34
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
22
35
|
if (entry)
|
|
23
36
|
return entry;
|
|
24
37
|
entry = __defProp({}, "__esModule", { value: true });
|
|
25
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
39
|
+
for (var key of __getOwnPropNames(from))
|
|
40
|
+
if (!__hasOwnProp.call(entry, key))
|
|
41
|
+
__defProp(entry, key, {
|
|
42
|
+
get: __accessProp.bind(from, key),
|
|
43
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
44
|
+
});
|
|
45
|
+
}
|
|
30
46
|
__moduleCache.set(from, entry);
|
|
31
47
|
return entry;
|
|
32
48
|
};
|
|
49
|
+
var __moduleCache;
|
|
50
|
+
var __returnValue = (v) => v;
|
|
51
|
+
function __exportSetter(name, newValue) {
|
|
52
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
53
|
+
}
|
|
33
54
|
var __export = (target, all) => {
|
|
34
55
|
for (var name in all)
|
|
35
56
|
__defProp(target, name, {
|
|
36
57
|
get: all[name],
|
|
37
58
|
enumerable: true,
|
|
38
59
|
configurable: true,
|
|
39
|
-
set: (
|
|
60
|
+
set: __exportSetter.bind(all, name)
|
|
40
61
|
});
|
|
41
62
|
};
|
|
42
63
|
|
|
@@ -44,6 +65,7 @@ var __export = (target, all) => {
|
|
|
44
65
|
var exports_src = {};
|
|
45
66
|
__export(exports_src, {
|
|
46
67
|
workflow: () => workflow,
|
|
68
|
+
parseDuration: () => parseDuration,
|
|
47
69
|
WorkflowStatus: () => WorkflowStatus,
|
|
48
70
|
WorkflowRunNotFoundError: () => WorkflowRunNotFoundError,
|
|
49
71
|
WorkflowEngineError: () => WorkflowEngineError,
|
|
@@ -53,20 +75,100 @@ __export(exports_src, {
|
|
|
53
75
|
module.exports = __toCommonJS(exports_src);
|
|
54
76
|
|
|
55
77
|
// src/definition.ts
|
|
56
|
-
function
|
|
57
|
-
|
|
78
|
+
function createWorkflowFactory(plugins = []) {
|
|
79
|
+
const factory = (id, handler, { inputSchema, timeout, retries } = {}) => ({
|
|
58
80
|
id,
|
|
59
81
|
handler,
|
|
60
82
|
inputSchema,
|
|
61
83
|
timeout,
|
|
62
|
-
retries
|
|
63
|
-
|
|
84
|
+
retries,
|
|
85
|
+
plugins: plugins.length > 0 ? plugins : undefined
|
|
86
|
+
});
|
|
87
|
+
factory.use = (plugin) => createWorkflowFactory([
|
|
88
|
+
...plugins,
|
|
89
|
+
plugin
|
|
90
|
+
]);
|
|
91
|
+
return factory;
|
|
92
|
+
}
|
|
93
|
+
var workflow = createWorkflowFactory();
|
|
94
|
+
// src/duration.ts
|
|
95
|
+
var import_parse_duration = __toESM(require("parse-duration"));
|
|
96
|
+
|
|
97
|
+
// src/error.ts
|
|
98
|
+
class WorkflowEngineError extends Error {
|
|
99
|
+
workflowId;
|
|
100
|
+
runId;
|
|
101
|
+
cause;
|
|
102
|
+
constructor(message, workflowId, runId, cause = undefined) {
|
|
103
|
+
super(message);
|
|
104
|
+
this.workflowId = workflowId;
|
|
105
|
+
this.runId = runId;
|
|
106
|
+
this.cause = cause;
|
|
107
|
+
this.name = "WorkflowEngineError";
|
|
108
|
+
if (Error.captureStackTrace) {
|
|
109
|
+
Error.captureStackTrace(this, WorkflowEngineError);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
115
|
+
constructor(runId, workflowId) {
|
|
116
|
+
super("Workflow run not found", workflowId, runId);
|
|
117
|
+
this.name = "WorkflowRunNotFoundError";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/duration.ts
|
|
122
|
+
var MS_PER_SECOND = 1000;
|
|
123
|
+
var MS_PER_MINUTE = 60 * MS_PER_SECOND;
|
|
124
|
+
var MS_PER_HOUR = 60 * MS_PER_MINUTE;
|
|
125
|
+
var MS_PER_DAY = 24 * MS_PER_HOUR;
|
|
126
|
+
var MS_PER_WEEK = 7 * MS_PER_DAY;
|
|
127
|
+
function parseDuration(duration) {
|
|
128
|
+
if (typeof duration === "string") {
|
|
129
|
+
if (duration.trim() === "") {
|
|
130
|
+
throw new WorkflowEngineError("Invalid duration: empty string");
|
|
131
|
+
}
|
|
132
|
+
const ms2 = import_parse_duration.default(duration);
|
|
133
|
+
if (ms2 == null || ms2 <= 0) {
|
|
134
|
+
throw new WorkflowEngineError(`Invalid duration: "${duration}"`);
|
|
135
|
+
}
|
|
136
|
+
return ms2;
|
|
137
|
+
}
|
|
138
|
+
const { weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0 } = duration;
|
|
139
|
+
const ms = weeks * MS_PER_WEEK + days * MS_PER_DAY + hours * MS_PER_HOUR + minutes * MS_PER_MINUTE + seconds * MS_PER_SECOND;
|
|
140
|
+
if (ms <= 0) {
|
|
141
|
+
throw new WorkflowEngineError("Invalid duration: must be a positive value");
|
|
142
|
+
}
|
|
143
|
+
return ms;
|
|
64
144
|
}
|
|
65
145
|
// src/engine.ts
|
|
66
146
|
var import_es_toolkit = require("es-toolkit");
|
|
67
147
|
|
|
68
148
|
// src/ast-parser.ts
|
|
69
149
|
var ts = __toESM(require("typescript"));
|
|
150
|
+
|
|
151
|
+
// src/types.ts
|
|
152
|
+
var WorkflowStatus;
|
|
153
|
+
((WorkflowStatus2) => {
|
|
154
|
+
WorkflowStatus2["PENDING"] = "pending";
|
|
155
|
+
WorkflowStatus2["RUNNING"] = "running";
|
|
156
|
+
WorkflowStatus2["PAUSED"] = "paused";
|
|
157
|
+
WorkflowStatus2["COMPLETED"] = "completed";
|
|
158
|
+
WorkflowStatus2["FAILED"] = "failed";
|
|
159
|
+
WorkflowStatus2["CANCELLED"] = "cancelled";
|
|
160
|
+
})(WorkflowStatus ||= {});
|
|
161
|
+
var StepType;
|
|
162
|
+
((StepType2) => {
|
|
163
|
+
StepType2["PAUSE"] = "pause";
|
|
164
|
+
StepType2["RUN"] = "run";
|
|
165
|
+
StepType2["WAIT_FOR"] = "waitFor";
|
|
166
|
+
StepType2["WAIT_UNTIL"] = "waitUntil";
|
|
167
|
+
StepType2["DELAY"] = "delay";
|
|
168
|
+
StepType2["POLL"] = "poll";
|
|
169
|
+
})(StepType ||= {});
|
|
170
|
+
|
|
171
|
+
// src/ast-parser.ts
|
|
70
172
|
function parseWorkflowHandler(handler) {
|
|
71
173
|
const handlerSource = handler.toString();
|
|
72
174
|
const sourceFile = ts.createSourceFile("handler.ts", handlerSource, ts.ScriptTarget.Latest, true);
|
|
@@ -110,13 +212,14 @@ function parseWorkflowHandler(handler) {
|
|
|
110
212
|
const propertyAccess = node.expression;
|
|
111
213
|
const objectName = propertyAccess.expression.getText(sourceFile);
|
|
112
214
|
const methodName = propertyAccess.name.text;
|
|
113
|
-
if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause")) {
|
|
215
|
+
if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll")) {
|
|
114
216
|
const firstArg = node.arguments[0];
|
|
115
217
|
if (firstArg) {
|
|
116
218
|
const { id, isDynamic } = extractStepId(firstArg);
|
|
219
|
+
const stepType = methodName === "sleep" ? "delay" /* DELAY */ : methodName;
|
|
117
220
|
const stepDefinition = {
|
|
118
221
|
id,
|
|
119
|
-
type:
|
|
222
|
+
type: stepType,
|
|
120
223
|
conditional: isInConditional(node),
|
|
121
224
|
loop: isInLoop(node),
|
|
122
225
|
isDynamic
|
|
@@ -417,48 +520,6 @@ async function withPostgresTransaction(db, callback) {
|
|
|
417
520
|
}
|
|
418
521
|
}
|
|
419
522
|
|
|
420
|
-
// src/error.ts
|
|
421
|
-
class WorkflowEngineError extends Error {
|
|
422
|
-
workflowId;
|
|
423
|
-
runId;
|
|
424
|
-
cause;
|
|
425
|
-
constructor(message, workflowId, runId, cause = undefined) {
|
|
426
|
-
super(message);
|
|
427
|
-
this.workflowId = workflowId;
|
|
428
|
-
this.runId = runId;
|
|
429
|
-
this.cause = cause;
|
|
430
|
-
this.name = "WorkflowEngineError";
|
|
431
|
-
if (Error.captureStackTrace) {
|
|
432
|
-
Error.captureStackTrace(this, WorkflowEngineError);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
class WorkflowRunNotFoundError extends WorkflowEngineError {
|
|
438
|
-
constructor(runId, workflowId) {
|
|
439
|
-
super("Workflow run not found", workflowId, runId);
|
|
440
|
-
this.name = "WorkflowRunNotFoundError";
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// src/types.ts
|
|
445
|
-
var WorkflowStatus;
|
|
446
|
-
((WorkflowStatus2) => {
|
|
447
|
-
WorkflowStatus2["PENDING"] = "pending";
|
|
448
|
-
WorkflowStatus2["RUNNING"] = "running";
|
|
449
|
-
WorkflowStatus2["PAUSED"] = "paused";
|
|
450
|
-
WorkflowStatus2["COMPLETED"] = "completed";
|
|
451
|
-
WorkflowStatus2["FAILED"] = "failed";
|
|
452
|
-
WorkflowStatus2["CANCELLED"] = "cancelled";
|
|
453
|
-
})(WorkflowStatus ||= {});
|
|
454
|
-
var StepType;
|
|
455
|
-
((StepType2) => {
|
|
456
|
-
StepType2["PAUSE"] = "pause";
|
|
457
|
-
StepType2["RUN"] = "run";
|
|
458
|
-
StepType2["WAIT_FOR"] = "waitFor";
|
|
459
|
-
StepType2["WAIT_UNTIL"] = "waitUntil";
|
|
460
|
-
})(StepType ||= {});
|
|
461
|
-
|
|
462
523
|
// src/engine.ts
|
|
463
524
|
var PAUSE_EVENT_NAME = "__internal_pause";
|
|
464
525
|
var WORKFLOW_RUN_QUEUE_NAME = "workflow-run";
|
|
@@ -467,7 +528,9 @@ var StepTypeToIcon = {
|
|
|
467
528
|
["run" /* RUN */]: "λ",
|
|
468
529
|
["waitFor" /* WAIT_FOR */]: "○",
|
|
469
530
|
["pause" /* PAUSE */]: "⏸",
|
|
470
|
-
["waitUntil" /* WAIT_UNTIL */]: "⏲"
|
|
531
|
+
["waitUntil" /* WAIT_UNTIL */]: "⏲",
|
|
532
|
+
["delay" /* DELAY */]: "⏱",
|
|
533
|
+
["poll" /* POLL */]: "↻"
|
|
471
534
|
};
|
|
472
535
|
var defaultLogger = {
|
|
473
536
|
log: (_message) => console.warn(_message),
|
|
@@ -567,7 +630,9 @@ class WorkflowEngine {
|
|
|
567
630
|
if (!workflow2) {
|
|
568
631
|
throw new WorkflowEngineError(`Unknown workflow ${workflowId}`);
|
|
569
632
|
}
|
|
570
|
-
|
|
633
|
+
const hasSteps = workflow2.steps.length > 0 && workflow2.steps[0];
|
|
634
|
+
const hasPlugins = (workflow2.plugins?.length ?? 0) > 0;
|
|
635
|
+
if (!hasSteps && !hasPlugins) {
|
|
571
636
|
throw new WorkflowEngineError(`Workflow ${workflowId} has no steps`, workflowId);
|
|
572
637
|
}
|
|
573
638
|
if (workflow2.inputSchema) {
|
|
@@ -576,7 +641,7 @@ class WorkflowEngine {
|
|
|
576
641
|
throw new WorkflowEngineError(result.error.message, workflowId);
|
|
577
642
|
}
|
|
578
643
|
}
|
|
579
|
-
const initialStepId = workflow2.steps[0]?.id;
|
|
644
|
+
const initialStepId = workflow2.steps[0]?.id ?? "__start__";
|
|
580
645
|
const run = await withPostgresTransaction(this.boss.getDb(), async (_db) => {
|
|
581
646
|
const timeoutAt = options?.timeout ? new Date(Date.now() + options.timeout) : workflow2.timeout ? new Date(Date.now() + workflow2.timeout) : null;
|
|
582
647
|
const insertedRun = await insertWorkflowRun({
|
|
@@ -763,11 +828,13 @@ class WorkflowEngine {
|
|
|
763
828
|
if (run.status === "paused" /* PAUSED */) {
|
|
764
829
|
const waitForStepEntry = run.timeline[`${run.currentStepId}-wait-for`];
|
|
765
830
|
const waitForStep = waitForStepEntry && typeof waitForStepEntry === "object" && "waitFor" in waitForStepEntry ? waitForStepEntry : null;
|
|
766
|
-
const
|
|
767
|
-
const currentStep = currentStepEntry && typeof currentStepEntry === "object" && "output" in currentStepEntry ? currentStepEntry : null;
|
|
831
|
+
const currentStep = this.getCachedStepEntry(run.timeline, run.currentStepId);
|
|
768
832
|
const waitFor = waitForStep?.waitFor;
|
|
769
833
|
const hasCurrentStepOutput = currentStep?.output !== undefined;
|
|
770
|
-
|
|
834
|
+
const eventMatches = waitFor && event?.name && (event.name === waitFor.eventName || event.name === waitFor.timeoutEvent) && !hasCurrentStepOutput;
|
|
835
|
+
if (eventMatches) {
|
|
836
|
+
const isTimeout = event?.name === waitFor?.timeoutEvent;
|
|
837
|
+
const skipOutput = waitFor?.skipOutput;
|
|
771
838
|
run = await this.updateRun({
|
|
772
839
|
runId,
|
|
773
840
|
resourceId,
|
|
@@ -775,13 +842,16 @@ class WorkflowEngine {
|
|
|
775
842
|
status: "running" /* RUNNING */,
|
|
776
843
|
pausedAt: null,
|
|
777
844
|
resumedAt: new Date,
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
845
|
+
jobId: job?.id,
|
|
846
|
+
...skipOutput ? {} : {
|
|
847
|
+
timeline: import_es_toolkit.merge(run.timeline, {
|
|
848
|
+
[run.currentStepId]: {
|
|
849
|
+
output: event?.data ?? {},
|
|
850
|
+
...isTimeout ? { timedOut: true } : {},
|
|
851
|
+
timestamp: new Date
|
|
852
|
+
}
|
|
853
|
+
})
|
|
854
|
+
}
|
|
785
855
|
}
|
|
786
856
|
});
|
|
787
857
|
} else {
|
|
@@ -797,51 +867,79 @@ class WorkflowEngine {
|
|
|
797
867
|
});
|
|
798
868
|
}
|
|
799
869
|
}
|
|
870
|
+
const baseStep = {
|
|
871
|
+
run: async (stepId, handler) => {
|
|
872
|
+
if (!run) {
|
|
873
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
874
|
+
}
|
|
875
|
+
return this.runStep({ stepId, run, handler });
|
|
876
|
+
},
|
|
877
|
+
waitFor: async (stepId, { eventName, timeout }) => {
|
|
878
|
+
if (!run) {
|
|
879
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
880
|
+
}
|
|
881
|
+
const timeoutDate = timeout ? new Date(Date.now() + timeout) : undefined;
|
|
882
|
+
return this.waitStep({ run, stepId, eventName, timeoutDate });
|
|
883
|
+
},
|
|
884
|
+
waitUntil: async (stepId, dateOrOptions) => {
|
|
885
|
+
if (!run) {
|
|
886
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
887
|
+
}
|
|
888
|
+
const date = dateOrOptions instanceof Date ? dateOrOptions : typeof dateOrOptions === "string" ? new Date(dateOrOptions) : dateOrOptions.date instanceof Date ? dateOrOptions.date : new Date(dateOrOptions.date);
|
|
889
|
+
await this.waitStep({ run, stepId, timeoutDate: date });
|
|
890
|
+
},
|
|
891
|
+
pause: async (stepId) => {
|
|
892
|
+
if (!run) {
|
|
893
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
894
|
+
}
|
|
895
|
+
await this.waitStep({ run, stepId, eventName: PAUSE_EVENT_NAME });
|
|
896
|
+
},
|
|
897
|
+
delay: async (stepId, duration) => {
|
|
898
|
+
if (!run) {
|
|
899
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
900
|
+
}
|
|
901
|
+
await this.waitStep({
|
|
902
|
+
run,
|
|
903
|
+
stepId,
|
|
904
|
+
timeoutDate: new Date(Date.now() + parseDuration(duration))
|
|
905
|
+
});
|
|
906
|
+
},
|
|
907
|
+
get sleep() {
|
|
908
|
+
return this.delay;
|
|
909
|
+
},
|
|
910
|
+
poll: async (stepId, conditionFn, options) => {
|
|
911
|
+
if (!run) {
|
|
912
|
+
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
913
|
+
}
|
|
914
|
+
const intervalMs = parseDuration(options?.interval ?? "30s");
|
|
915
|
+
if (intervalMs < 30000) {
|
|
916
|
+
throw new WorkflowEngineError(`step.poll interval must be at least 30s (got ${intervalMs}ms)`, workflowId, runId);
|
|
917
|
+
}
|
|
918
|
+
const timeoutMs = options?.timeout ? parseDuration(options.timeout) : undefined;
|
|
919
|
+
return this.pollStep({ run, stepId, conditionFn, intervalMs, timeoutMs });
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
let step = { ...baseStep };
|
|
923
|
+
const plugins = workflow2.plugins ?? [];
|
|
924
|
+
for (const plugin of plugins) {
|
|
925
|
+
const extra = plugin.methods(step);
|
|
926
|
+
step = { ...step, ...extra };
|
|
927
|
+
}
|
|
800
928
|
const context = {
|
|
801
929
|
input: run.input,
|
|
802
930
|
workflowId: run.workflowId,
|
|
803
931
|
runId: run.id,
|
|
804
932
|
timeline: run.timeline,
|
|
805
933
|
logger: this.logger,
|
|
806
|
-
step
|
|
807
|
-
run: async (stepId, handler) => {
|
|
808
|
-
if (!run) {
|
|
809
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
810
|
-
}
|
|
811
|
-
return this.runStep({
|
|
812
|
-
stepId,
|
|
813
|
-
run,
|
|
814
|
-
handler
|
|
815
|
-
});
|
|
816
|
-
},
|
|
817
|
-
waitFor: async (stepId, { eventName, timeout }) => {
|
|
818
|
-
if (!run) {
|
|
819
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
820
|
-
}
|
|
821
|
-
return this.waitForEvent({
|
|
822
|
-
run,
|
|
823
|
-
stepId,
|
|
824
|
-
eventName,
|
|
825
|
-
timeout
|
|
826
|
-
});
|
|
827
|
-
},
|
|
828
|
-
waitUntil: async ({ date }) => {
|
|
829
|
-
return this.waitUntil(runId, date);
|
|
830
|
-
},
|
|
831
|
-
pause: async (stepId) => {
|
|
832
|
-
if (!run) {
|
|
833
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
834
|
-
}
|
|
835
|
-
return this.pauseStep({
|
|
836
|
-
stepId,
|
|
837
|
-
run
|
|
838
|
-
});
|
|
839
|
-
}
|
|
840
|
-
}
|
|
934
|
+
step
|
|
841
935
|
};
|
|
842
936
|
const result = await workflow2.handler(context);
|
|
843
937
|
run = await this.getRun({ runId, resourceId });
|
|
844
|
-
|
|
938
|
+
const isLastParsedStep = run.currentStepId === workflow2.steps[workflow2.steps.length - 1]?.id;
|
|
939
|
+
const hasPluginSteps = (workflow2.plugins?.length ?? 0) > 0;
|
|
940
|
+
const noParsedSteps = workflow2.steps.length === 0;
|
|
941
|
+
const shouldComplete = run.status === "running" /* RUNNING */ && (noParsedSteps || isLastParsedStep || hasPluginSteps && result !== undefined);
|
|
942
|
+
if (shouldComplete) {
|
|
845
943
|
const normalizedResult = result === undefined ? {} : result;
|
|
846
944
|
await this.updateRun({
|
|
847
945
|
runId,
|
|
@@ -893,6 +991,10 @@ class WorkflowEngine {
|
|
|
893
991
|
throw error;
|
|
894
992
|
}
|
|
895
993
|
}
|
|
994
|
+
getCachedStepEntry(timeline, stepId) {
|
|
995
|
+
const stepEntry = timeline[stepId];
|
|
996
|
+
return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
|
|
997
|
+
}
|
|
896
998
|
async runStep({
|
|
897
999
|
stepId,
|
|
898
1000
|
run,
|
|
@@ -911,39 +1013,38 @@ class WorkflowEngine {
|
|
|
911
1013
|
return;
|
|
912
1014
|
}
|
|
913
1015
|
try {
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
run = await this.updateRun({
|
|
933
|
-
runId: run.id,
|
|
934
|
-
resourceId: run.resourceId ?? undefined,
|
|
935
|
-
data: {
|
|
936
|
-
timeline: import_es_toolkit.merge(run.timeline, {
|
|
937
|
-
[stepId]: {
|
|
938
|
-
output: result === undefined ? {} : result,
|
|
939
|
-
timestamp: new Date
|
|
940
|
-
}
|
|
941
|
-
})
|
|
942
|
-
}
|
|
943
|
-
}, { db });
|
|
1016
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
1017
|
+
if (cached?.output !== undefined) {
|
|
1018
|
+
return cached.output;
|
|
1019
|
+
}
|
|
1020
|
+
await this.updateRun({
|
|
1021
|
+
runId: run.id,
|
|
1022
|
+
resourceId: run.resourceId ?? undefined,
|
|
1023
|
+
data: {
|
|
1024
|
+
currentStepId: stepId
|
|
1025
|
+
}
|
|
1026
|
+
}, { db });
|
|
1027
|
+
this.logger.log(`Running step ${stepId}...`, {
|
|
1028
|
+
runId: run.id,
|
|
1029
|
+
workflowId: run.workflowId
|
|
1030
|
+
});
|
|
1031
|
+
let output = await handler();
|
|
1032
|
+
if (output === undefined) {
|
|
1033
|
+
output = {};
|
|
944
1034
|
}
|
|
945
|
-
|
|
946
|
-
|
|
1035
|
+
run = await this.updateRun({
|
|
1036
|
+
runId: run.id,
|
|
1037
|
+
resourceId: run.resourceId ?? undefined,
|
|
1038
|
+
data: {
|
|
1039
|
+
timeline: import_es_toolkit.merge(run.timeline, {
|
|
1040
|
+
[stepId]: {
|
|
1041
|
+
output,
|
|
1042
|
+
timestamp: new Date
|
|
1043
|
+
}
|
|
1044
|
+
})
|
|
1045
|
+
}
|
|
1046
|
+
}, { db });
|
|
1047
|
+
return output;
|
|
947
1048
|
} catch (error) {
|
|
948
1049
|
this.logger.error(`Step ${stepId} failed:`, error, {
|
|
949
1050
|
runId: run.id,
|
|
@@ -962,60 +1063,133 @@ ${error.stack}` : String(error)
|
|
|
962
1063
|
}
|
|
963
1064
|
});
|
|
964
1065
|
}
|
|
965
|
-
async
|
|
1066
|
+
async waitStep({
|
|
966
1067
|
run,
|
|
967
1068
|
stepId,
|
|
968
1069
|
eventName,
|
|
969
|
-
|
|
1070
|
+
timeoutDate
|
|
970
1071
|
}) {
|
|
971
1072
|
const persistedRun = await this.getRun({
|
|
972
1073
|
runId: run.id,
|
|
973
1074
|
resourceId: run.resourceId ?? undefined
|
|
974
1075
|
});
|
|
975
1076
|
if (persistedRun.status === "cancelled" /* CANCELLED */ || persistedRun.status === "paused" /* PAUSED */ || persistedRun.status === "failed" /* FAILED */) {
|
|
976
|
-
this.logger.log(`Step ${stepId} skipped, workflow run is ${persistedRun.status}`, {
|
|
977
|
-
runId: run.id,
|
|
978
|
-
workflowId: run.workflowId
|
|
979
|
-
});
|
|
980
1077
|
return;
|
|
981
1078
|
}
|
|
982
|
-
const
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
return timelineStepCheck.output;
|
|
1079
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
1080
|
+
if (cached?.output !== undefined) {
|
|
1081
|
+
return cached.timedOut ? undefined : cached.output;
|
|
986
1082
|
}
|
|
1083
|
+
const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
|
|
987
1084
|
await this.updateRun({
|
|
988
1085
|
runId: run.id,
|
|
989
1086
|
resourceId: run.resourceId ?? undefined,
|
|
990
1087
|
data: {
|
|
991
1088
|
status: "paused" /* PAUSED */,
|
|
992
1089
|
currentStepId: stepId,
|
|
1090
|
+
pausedAt: new Date,
|
|
993
1091
|
timeline: import_es_toolkit.merge(run.timeline, {
|
|
994
1092
|
[`${stepId}-wait-for`]: {
|
|
995
|
-
waitFor: {
|
|
996
|
-
eventName,
|
|
997
|
-
timeout
|
|
998
|
-
},
|
|
1093
|
+
waitFor: { eventName, timeoutEvent },
|
|
999
1094
|
timestamp: new Date
|
|
1000
1095
|
}
|
|
1001
|
-
})
|
|
1002
|
-
|
|
1096
|
+
})
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
if (timeoutDate && timeoutEvent) {
|
|
1100
|
+
const job = {
|
|
1101
|
+
runId: run.id,
|
|
1102
|
+
resourceId: run.resourceId ?? undefined,
|
|
1103
|
+
workflowId: run.workflowId,
|
|
1104
|
+
input: run.input,
|
|
1105
|
+
event: { name: timeoutEvent, data: { date: timeoutDate.toISOString() } }
|
|
1106
|
+
};
|
|
1107
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
1108
|
+
startAfter: timeoutDate.getTime() <= Date.now() ? new Date : timeoutDate,
|
|
1109
|
+
expireInSeconds: defaultExpireInSeconds
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
this.logger.log(`Step ${stepId} waiting${eventName ? ` for event "${eventName}"` : ""}${timeoutDate ? ` until ${timeoutDate.toISOString()}` : ""}`, { runId: run.id, workflowId: run.workflowId });
|
|
1113
|
+
}
|
|
1114
|
+
async pollStep({
|
|
1115
|
+
run,
|
|
1116
|
+
stepId,
|
|
1117
|
+
conditionFn,
|
|
1118
|
+
intervalMs,
|
|
1119
|
+
timeoutMs
|
|
1120
|
+
}) {
|
|
1121
|
+
const persistedRun = await this.getRun({
|
|
1122
|
+
runId: run.id,
|
|
1123
|
+
resourceId: run.resourceId ?? undefined
|
|
1124
|
+
});
|
|
1125
|
+
if (persistedRun.status === "cancelled" /* CANCELLED */ || persistedRun.status === "paused" /* PAUSED */ || persistedRun.status === "failed" /* FAILED */) {
|
|
1126
|
+
return { timedOut: true };
|
|
1127
|
+
}
|
|
1128
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
1129
|
+
if (cached?.output !== undefined) {
|
|
1130
|
+
return cached.timedOut ? { timedOut: true } : { timedOut: false, data: cached.output };
|
|
1131
|
+
}
|
|
1132
|
+
const pollStateEntry = persistedRun.timeline[`${stepId}-poll`];
|
|
1133
|
+
const startedAt = pollStateEntry && typeof pollStateEntry === "object" && "startedAt" in pollStateEntry ? new Date(pollStateEntry.startedAt) : new Date;
|
|
1134
|
+
if (timeoutMs !== undefined && Date.now() >= startedAt.getTime() + timeoutMs) {
|
|
1135
|
+
await this.updateRun({
|
|
1136
|
+
runId: run.id,
|
|
1137
|
+
resourceId: run.resourceId ?? undefined,
|
|
1138
|
+
data: {
|
|
1139
|
+
currentStepId: stepId,
|
|
1140
|
+
timeline: import_es_toolkit.merge(persistedRun.timeline, {
|
|
1141
|
+
[stepId]: { output: {}, timedOut: true, timestamp: new Date }
|
|
1142
|
+
})
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
return { timedOut: true };
|
|
1146
|
+
}
|
|
1147
|
+
const result = await conditionFn();
|
|
1148
|
+
if (result !== false) {
|
|
1149
|
+
await this.updateRun({
|
|
1150
|
+
runId: run.id,
|
|
1151
|
+
resourceId: run.resourceId ?? undefined,
|
|
1152
|
+
data: {
|
|
1153
|
+
currentStepId: stepId,
|
|
1154
|
+
timeline: import_es_toolkit.merge(persistedRun.timeline, {
|
|
1155
|
+
[stepId]: { output: result, timestamp: new Date }
|
|
1156
|
+
})
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
return { timedOut: false, data: result };
|
|
1160
|
+
}
|
|
1161
|
+
const pollEvent = `__poll_${stepId}`;
|
|
1162
|
+
await this.updateRun({
|
|
1163
|
+
runId: run.id,
|
|
1164
|
+
resourceId: run.resourceId ?? undefined,
|
|
1165
|
+
data: {
|
|
1166
|
+
status: "paused" /* PAUSED */,
|
|
1167
|
+
currentStepId: stepId,
|
|
1168
|
+
pausedAt: new Date,
|
|
1169
|
+
timeline: import_es_toolkit.merge(persistedRun.timeline, {
|
|
1170
|
+
[`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
|
|
1171
|
+
[`${stepId}-wait-for`]: {
|
|
1172
|
+
waitFor: { timeoutEvent: pollEvent, skipOutput: true },
|
|
1173
|
+
timestamp: new Date
|
|
1174
|
+
}
|
|
1175
|
+
})
|
|
1003
1176
|
}
|
|
1004
1177
|
});
|
|
1005
|
-
this.
|
|
1178
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, {
|
|
1006
1179
|
runId: run.id,
|
|
1007
|
-
|
|
1180
|
+
resourceId: run.resourceId ?? undefined,
|
|
1181
|
+
workflowId: run.workflowId,
|
|
1182
|
+
input: run.input,
|
|
1183
|
+
event: { name: pollEvent, data: {} }
|
|
1184
|
+
}, {
|
|
1185
|
+
startAfter: new Date(Date.now() + intervalMs),
|
|
1186
|
+
expireInSeconds: defaultExpireInSeconds
|
|
1008
1187
|
});
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
run,
|
|
1013
|
-
stepId,
|
|
1014
|
-
eventName: PAUSE_EVENT_NAME
|
|
1188
|
+
this.logger.log(`Step ${stepId} polling every ${intervalMs}ms...`, {
|
|
1189
|
+
runId: run.id,
|
|
1190
|
+
workflowId: run.workflowId
|
|
1015
1191
|
});
|
|
1016
|
-
|
|
1017
|
-
async waitUntil(runId, _date) {
|
|
1018
|
-
throw new WorkflowEngineError("Not implemented yet", undefined, runId);
|
|
1192
|
+
return { timedOut: false, data: undefined };
|
|
1019
1193
|
}
|
|
1020
1194
|
async checkIfHasStarted() {
|
|
1021
1195
|
if (!this._started) {
|
|
@@ -1055,5 +1229,5 @@ ${error.stack}` : String(error)
|
|
|
1055
1229
|
}
|
|
1056
1230
|
}
|
|
1057
1231
|
|
|
1058
|
-
//# debugId=
|
|
1232
|
+
//# debugId=11B08B71DE539A4764756E2164756E21
|
|
1059
1233
|
//# sourceMappingURL=index.js.map
|