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/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: () => mod[key],
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
- __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
27
- get: () => from[key],
28
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
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: (newValue) => all[name] = () => newValue
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 workflow(id, handler, { inputSchema, timeout, retries } = {}) {
57
- return {
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: methodName,
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
- if (workflow2.steps.length === 0 || !workflow2.steps[0]) {
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 currentStepEntry = run.timeline[run.currentStepId];
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
- if (waitFor && waitFor.eventName === event?.name && !hasCurrentStepOutput) {
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
- timeline: import_es_toolkit.merge(run.timeline, {
779
- [run.currentStepId]: {
780
- output: event?.data ?? {},
781
- timestamp: new Date
782
- }
783
- }),
784
- jobId: job?.id
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
- if (run.status === "running" /* RUNNING */ && run.currentStepId === workflow2.steps[workflow2.steps.length - 1]?.id) {
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
- let result;
922
- const timelineStepEntry = persistedRun.timeline[stepId];
923
- const timelineStep = timelineStepEntry && typeof timelineStepEntry === "object" && "output" in timelineStepEntry ? timelineStepEntry : null;
924
- if (timelineStep?.output !== undefined) {
925
- result = timelineStep.output;
926
- } else {
927
- await this.updateRun({
928
- runId: run.id,
929
- resourceId: run.resourceId ?? undefined,
930
- data: {
931
- currentStepId: stepId
932
- }
933
- }, { db });
934
- this.logger.log(`Running step ${stepId}...`, {
935
- runId: run.id,
936
- workflowId: run.workflowId
937
- });
938
- result = await handler();
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
- const finalResult = result === undefined ? {} : result;
953
- return finalResult;
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 waitForEvent({
1066
+ async waitStep({
973
1067
  run,
974
1068
  stepId,
975
1069
  eventName,
976
- timeout
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 timelineStepCheckEntry = persistedRun.timeline[stepId];
990
- const timelineStepCheck = timelineStepCheckEntry && typeof timelineStepCheckEntry === "object" && "output" in timelineStepCheckEntry ? timelineStepCheckEntry : null;
991
- if (timelineStepCheck?.output !== undefined) {
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
- this.logger.log(`Running step ${stepId}, waiting for event ${eventName}...`, {
1013
- runId: run.id,
1014
- workflowId: run.workflowId
1015
- });
1016
- }
1017
- async pauseStep({ stepId, run }) {
1018
- await this.waitForEvent({
1019
- run,
1020
- stepId,
1021
- eventName: PAUSE_EVENT_NAME
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 waitUntilDate({
1114
+ async pollStep({
1025
1115
  run,
1026
1116
  stepId,
1027
- date
1117
+ conditionFn,
1118
+ intervalMs,
1119
+ timeoutMs
1028
1120
  }) {
1029
- const eventName = `__wait_until_${stepId}`;
1030
- await this.waitForEvent({
1031
- run,
1032
- stepId,
1033
- eventName
1121
+ const persistedRun = await this.getRun({
1122
+ runId: run.id,
1123
+ resourceId: run.resourceId ?? undefined
1034
1124
  });
1035
- const job = {
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
- name: eventName,
1042
- data: { date: date.toISOString() }
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(`Running step ${stepId}, waiting until ${date.toISOString()}...`, {
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=EB7CB5376BB0770264756E2164756E21
1232
+ //# debugId=11B08B71DE539A4764756E2164756E21
1093
1233
  //# sourceMappingURL=index.js.map