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.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" || methodName === "waitUntil")) {
|
|
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,58 +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 (stepId, { date }) => {
|
|
829
|
-
if (!run) {
|
|
830
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
831
|
-
}
|
|
832
|
-
await this.waitUntilDate({
|
|
833
|
-
run,
|
|
834
|
-
stepId,
|
|
835
|
-
date
|
|
836
|
-
});
|
|
837
|
-
},
|
|
838
|
-
pause: async (stepId) => {
|
|
839
|
-
if (!run) {
|
|
840
|
-
throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
|
|
841
|
-
}
|
|
842
|
-
return this.pauseStep({
|
|
843
|
-
stepId,
|
|
844
|
-
run
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
}
|
|
934
|
+
step
|
|
848
935
|
};
|
|
849
936
|
const result = await workflow2.handler(context);
|
|
850
937
|
run = await this.getRun({ runId, resourceId });
|
|
851
|
-
|
|
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) {
|
|
852
943
|
const normalizedResult = result === undefined ? {} : result;
|
|
853
944
|
await this.updateRun({
|
|
854
945
|
runId,
|
|
@@ -900,6 +991,10 @@ class WorkflowEngine {
|
|
|
900
991
|
throw error;
|
|
901
992
|
}
|
|
902
993
|
}
|
|
994
|
+
getCachedStepEntry(timeline, stepId) {
|
|
995
|
+
const stepEntry = timeline[stepId];
|
|
996
|
+
return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
|
|
997
|
+
}
|
|
903
998
|
async runStep({
|
|
904
999
|
stepId,
|
|
905
1000
|
run,
|
|
@@ -918,39 +1013,38 @@ class WorkflowEngine {
|
|
|
918
1013
|
return;
|
|
919
1014
|
}
|
|
920
1015
|
try {
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
run = await this.updateRun({
|
|
940
|
-
runId: run.id,
|
|
941
|
-
resourceId: run.resourceId ?? undefined,
|
|
942
|
-
data: {
|
|
943
|
-
timeline: import_es_toolkit.merge(run.timeline, {
|
|
944
|
-
[stepId]: {
|
|
945
|
-
output: result === undefined ? {} : result,
|
|
946
|
-
timestamp: new Date
|
|
947
|
-
}
|
|
948
|
-
})
|
|
949
|
-
}
|
|
950
|
-
}, { 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 = {};
|
|
951
1034
|
}
|
|
952
|
-
|
|
953
|
-
|
|
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;
|
|
954
1048
|
} catch (error) {
|
|
955
1049
|
this.logger.error(`Step ${stepId} failed:`, error, {
|
|
956
1050
|
runId: run.id,
|
|
@@ -969,87 +1063,133 @@ ${error.stack}` : String(error)
|
|
|
969
1063
|
}
|
|
970
1064
|
});
|
|
971
1065
|
}
|
|
972
|
-
async
|
|
1066
|
+
async waitStep({
|
|
973
1067
|
run,
|
|
974
1068
|
stepId,
|
|
975
1069
|
eventName,
|
|
976
|
-
|
|
1070
|
+
timeoutDate
|
|
977
1071
|
}) {
|
|
978
1072
|
const persistedRun = await this.getRun({
|
|
979
1073
|
runId: run.id,
|
|
980
1074
|
resourceId: run.resourceId ?? undefined
|
|
981
1075
|
});
|
|
982
1076
|
if (persistedRun.status === "cancelled" /* CANCELLED */ || persistedRun.status === "paused" /* PAUSED */ || persistedRun.status === "failed" /* FAILED */) {
|
|
983
|
-
this.logger.log(`Step ${stepId} skipped, workflow run is ${persistedRun.status}`, {
|
|
984
|
-
runId: run.id,
|
|
985
|
-
workflowId: run.workflowId
|
|
986
|
-
});
|
|
987
1077
|
return;
|
|
988
1078
|
}
|
|
989
|
-
const
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
return timelineStepCheck.output;
|
|
1079
|
+
const cached = this.getCachedStepEntry(persistedRun.timeline, stepId);
|
|
1080
|
+
if (cached?.output !== undefined) {
|
|
1081
|
+
return cached.timedOut ? undefined : cached.output;
|
|
993
1082
|
}
|
|
1083
|
+
const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
|
|
994
1084
|
await this.updateRun({
|
|
995
1085
|
runId: run.id,
|
|
996
1086
|
resourceId: run.resourceId ?? undefined,
|
|
997
1087
|
data: {
|
|
998
1088
|
status: "paused" /* PAUSED */,
|
|
999
1089
|
currentStepId: stepId,
|
|
1090
|
+
pausedAt: new Date,
|
|
1000
1091
|
timeline: import_es_toolkit.merge(run.timeline, {
|
|
1001
1092
|
[`${stepId}-wait-for`]: {
|
|
1002
|
-
waitFor: {
|
|
1003
|
-
eventName,
|
|
1004
|
-
timeout
|
|
1005
|
-
},
|
|
1093
|
+
waitFor: { eventName, timeoutEvent },
|
|
1006
1094
|
timestamp: new Date
|
|
1007
1095
|
}
|
|
1008
|
-
})
|
|
1009
|
-
pausedAt: new Date
|
|
1096
|
+
})
|
|
1010
1097
|
}
|
|
1011
1098
|
});
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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 });
|
|
1023
1113
|
}
|
|
1024
|
-
async
|
|
1114
|
+
async pollStep({
|
|
1025
1115
|
run,
|
|
1026
1116
|
stepId,
|
|
1027
|
-
|
|
1117
|
+
conditionFn,
|
|
1118
|
+
intervalMs,
|
|
1119
|
+
timeoutMs
|
|
1028
1120
|
}) {
|
|
1029
|
-
const
|
|
1030
|
-
|
|
1031
|
-
run
|
|
1032
|
-
stepId,
|
|
1033
|
-
eventName
|
|
1121
|
+
const persistedRun = await this.getRun({
|
|
1122
|
+
runId: run.id,
|
|
1123
|
+
resourceId: run.resourceId ?? undefined
|
|
1034
1124
|
});
|
|
1035
|
-
|
|
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
|
+
})
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, {
|
|
1036
1179
|
runId: run.id,
|
|
1037
1180
|
resourceId: run.resourceId ?? undefined,
|
|
1038
1181
|
workflowId: run.workflowId,
|
|
1039
1182
|
input: run.input,
|
|
1040
|
-
event: {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1044
|
-
};
|
|
1045
|
-
await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
|
|
1046
|
-
startAfter: date,
|
|
1183
|
+
event: { name: pollEvent, data: {} }
|
|
1184
|
+
}, {
|
|
1185
|
+
startAfter: new Date(Date.now() + intervalMs),
|
|
1047
1186
|
expireInSeconds: defaultExpireInSeconds
|
|
1048
1187
|
});
|
|
1049
|
-
this.logger.log(`
|
|
1188
|
+
this.logger.log(`Step ${stepId} polling every ${intervalMs}ms...`, {
|
|
1050
1189
|
runId: run.id,
|
|
1051
1190
|
workflowId: run.workflowId
|
|
1052
1191
|
});
|
|
1192
|
+
return { timedOut: false, data: undefined };
|
|
1053
1193
|
}
|
|
1054
1194
|
async checkIfHasStarted() {
|
|
1055
1195
|
if (!this._started) {
|
|
@@ -1089,5 +1229,5 @@ ${error.stack}` : String(error)
|
|
|
1089
1229
|
}
|
|
1090
1230
|
}
|
|
1091
1231
|
|
|
1092
|
-
//# debugId=
|
|
1232
|
+
//# debugId=11B08B71DE539A4764756E2164756E21
|
|
1093
1233
|
//# sourceMappingURL=index.js.map
|