pg-workflows 0.9.0 → 0.11.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.js CHANGED
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  DEFAULT_PGBOSS_SCHEMA,
3
3
  PAUSE_EVENT_NAME,
4
- StepType,
5
4
  WORKFLOW_RUN_DLQ_QUEUE_NAME,
6
5
  WORKFLOW_RUN_QUEUE_NAME,
7
6
  WorkflowClient,
@@ -12,38 +11,16 @@ import {
12
11
  getWorkflowRun,
13
12
  getWorkflowRuns,
14
13
  insertWorkflowRun,
14
+ invokeChildWorkflowTimelineKey,
15
+ isInvokeChildWorkflowTimelineEntry,
15
16
  runMigrations,
16
17
  updateWorkflowRun,
17
18
  validateResourceId,
18
19
  validateWorkflowId,
20
+ waitForTimelineKey,
19
21
  withPostgresTransaction,
20
22
  workflow
21
- } from "./shared/chunk-fr76gdwj.js";
22
- // src/duration.ts
23
- import parse from "parse-duration";
24
- var MS_PER_SECOND = 1000;
25
- var MS_PER_MINUTE = 60 * MS_PER_SECOND;
26
- var MS_PER_HOUR = 60 * MS_PER_MINUTE;
27
- var MS_PER_DAY = 24 * MS_PER_HOUR;
28
- var MS_PER_WEEK = 7 * MS_PER_DAY;
29
- function parseDuration(duration) {
30
- if (typeof duration === "string") {
31
- if (duration.trim() === "") {
32
- throw new WorkflowEngineError("Invalid duration: empty string");
33
- }
34
- const ms2 = parse(duration);
35
- if (ms2 == null || ms2 <= 0) {
36
- throw new WorkflowEngineError(`Invalid duration: "${duration}"`);
37
- }
38
- return ms2;
39
- }
40
- const { weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0 } = duration;
41
- const ms = weeks * MS_PER_WEEK + days * MS_PER_DAY + hours * MS_PER_HOUR + minutes * MS_PER_MINUTE + seconds * MS_PER_SECOND;
42
- if (ms <= 0) {
43
- throw new WorkflowEngineError("Invalid duration: must be a positive value");
44
- }
45
- return ms;
46
- }
23
+ } from "./shared/chunk-ahxqsytt.js";
47
24
  // src/engine.ts
48
25
  import { merge } from "es-toolkit";
49
26
  import pg from "pg";
@@ -94,7 +71,7 @@ function parseWorkflowHandler(handler) {
94
71
  const propertyAccess = node.expression;
95
72
  const objectName = propertyAccess.expression.getText(sourceFile);
96
73
  const methodName = propertyAccess.name.text;
97
- if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll")) {
74
+ if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll" || methodName === "invokeChildWorkflow")) {
98
75
  const firstArg = node.arguments[0];
99
76
  if (firstArg) {
100
77
  const { id, isDynamic } = extractStepId(firstArg);
@@ -119,6 +96,32 @@ function parseWorkflowHandler(handler) {
119
96
  return { steps: Array.from(steps.values()) };
120
97
  }
121
98
 
99
+ // src/duration.ts
100
+ import parse from "parse-duration";
101
+ var MS_PER_SECOND = 1000;
102
+ var MS_PER_MINUTE = 60 * MS_PER_SECOND;
103
+ var MS_PER_HOUR = 60 * MS_PER_MINUTE;
104
+ var MS_PER_DAY = 24 * MS_PER_HOUR;
105
+ var MS_PER_WEEK = 7 * MS_PER_DAY;
106
+ function parseDuration(duration) {
107
+ if (typeof duration === "string") {
108
+ if (duration.trim() === "") {
109
+ throw new WorkflowEngineError("Invalid duration: empty string");
110
+ }
111
+ const ms2 = parse(duration);
112
+ if (ms2 == null || ms2 <= 0) {
113
+ throw new WorkflowEngineError(`Invalid duration: "${duration}"`);
114
+ }
115
+ return ms2;
116
+ }
117
+ const { weeks = 0, days = 0, hours = 0, minutes = 0, seconds = 0 } = duration;
118
+ const ms = weeks * MS_PER_WEEK + days * MS_PER_DAY + hours * MS_PER_HOUR + minutes * MS_PER_MINUTE + seconds * MS_PER_SECOND;
119
+ if (ms <= 0) {
120
+ throw new WorkflowEngineError("Invalid duration: must be a positive value");
121
+ }
122
+ return ms;
123
+ }
124
+
122
125
  // src/engine.ts
123
126
  var LOG_PREFIX = "[WorkflowEngine]";
124
127
  var StepTypeToIcon = {
@@ -127,7 +130,8 @@ var StepTypeToIcon = {
127
130
  ["pause" /* PAUSE */]: "⏸",
128
131
  ["waitUntil" /* WAIT_UNTIL */]: "⏲",
129
132
  ["delay" /* DELAY */]: "⏱",
130
- ["poll" /* POLL */]: "↻"
133
+ ["poll" /* POLL */]: "↻",
134
+ ["invokeChildWorkflow" /* INVOKE_CHILD_WORKFLOW */]: "↪"
131
135
  };
132
136
  var defaultLogger = {
133
137
  log: (_message) => console.warn(_message),
@@ -139,6 +143,7 @@ var retrySendOptions = (maxRetries) => ({
139
143
  retryBackoff: true,
140
144
  retryDelay: 1
141
145
  });
146
+ var getInvokeChildWorkflowEventName = (childRunId) => `__invoke_child_workflow_completed:${childRunId}`;
142
147
  var defaultHeartbeatSeconds = process.env.WORKFLOW_RUN_HEARTBEAT_SECONDS ? Number.parseInt(process.env.WORKFLOW_RUN_HEARTBEAT_SECONDS, 10) : 30;
143
148
 
144
149
  class WorkflowEngine {
@@ -244,31 +249,57 @@ class WorkflowEngine {
244
249
  this.workflows.clear();
245
250
  return this;
246
251
  }
247
- async startWorkflow(refOrParams, inputArg, optionsArg) {
248
- let workflowId;
249
- let input;
250
- let resourceId;
251
- let idempotencyKey;
252
- let options;
252
+ resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg) {
253
253
  if (typeof refOrParams === "function" && "id" in refOrParams) {
254
- workflowId = refOrParams.id;
255
- input = inputArg;
256
- options = optionsArg;
257
- resourceId = optionsArg?.resourceId;
258
- idempotencyKey = optionsArg?.idempotencyKey;
259
- } else {
260
- const params = refOrParams;
261
- workflowId = params.workflowId;
262
- input = params.input;
263
- resourceId = params.resourceId;
264
- idempotencyKey = params.idempotencyKey;
265
- options = params.options;
254
+ return {
255
+ workflowId: refOrParams.id,
256
+ input: inputArg,
257
+ options: optionsArg,
258
+ resourceId: optionsArg?.resourceId,
259
+ idempotencyKey: optionsArg?.idempotencyKey
260
+ };
266
261
  }
267
- validateWorkflowId(workflowId);
268
- validateResourceId(resourceId);
262
+ const params = refOrParams;
263
+ return {
264
+ workflowId: params.workflowId,
265
+ input: params.input,
266
+ resourceId: params.resourceId ?? params.options?.resourceId,
267
+ idempotencyKey: params.idempotencyKey ?? params.options?.idempotencyKey,
268
+ options: params.options
269
+ };
270
+ }
271
+ async startWorkflow(refOrParams, inputArg, optionsArg) {
272
+ const { workflowId, input, resourceId, idempotencyKey, options } = this.resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg);
269
273
  if (!this._started) {
270
- await this.start(false, { batchSize: options?.batchSize ?? 1 });
274
+ await this.start(false);
271
275
  }
276
+ const { run } = await this.createWorkflowRun({
277
+ workflowId,
278
+ input,
279
+ resourceId,
280
+ idempotencyKey,
281
+ options
282
+ });
283
+ this.logger.log("Started workflow run", {
284
+ runId: run.id,
285
+ workflowId
286
+ });
287
+ return run;
288
+ }
289
+ async createWorkflowRun({
290
+ workflowId,
291
+ input,
292
+ resourceId,
293
+ idempotencyKey,
294
+ options,
295
+ parentRunId,
296
+ parentStepId,
297
+ parentResourceId,
298
+ enqueue = true,
299
+ db
300
+ }) {
301
+ validateWorkflowId(workflowId);
302
+ validateResourceId(resourceId);
272
303
  const workflow2 = this.workflows.get(workflowId);
273
304
  if (!workflow2) {
274
305
  throw new WorkflowEngineError(`Unknown workflow ${workflowId}`);
@@ -285,38 +316,60 @@ class WorkflowEngine {
285
316
  }
286
317
  }
287
318
  const initialStepId = workflow2.steps[0]?.id ?? "__start__";
288
- const run = await withPostgresTransaction(this.boss.getDb(), async (_db) => {
289
- const timeoutAt = options?.timeout ? new Date(Date.now() + options.timeout) : workflow2.timeout ? new Date(Date.now() + workflow2.timeout) : null;
290
- const { run: insertedRun, created } = await insertWorkflowRun({
291
- resourceId,
292
- workflowId,
293
- currentStepId: initialStepId,
294
- status: "running" /* RUNNING */,
295
- input,
296
- maxRetries: options?.retries ?? workflow2.retries ?? 0,
297
- timeoutAt,
298
- idempotencyKey
299
- }, _db);
300
- if (created) {
301
- const job = {
302
- runId: insertedRun.id,
303
- resourceId,
304
- workflowId,
305
- input
306
- };
307
- await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
308
- startAfter: new Date,
309
- expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
310
- ...retrySendOptions(insertedRun.maxRetries)
311
- });
319
+ const timeoutAt = options?.timeout ? new Date(Date.now() + options.timeout) : workflow2.timeout ? new Date(Date.now() + workflow2.timeout) : null;
320
+ const insertRun = async (targetDb) => await insertWorkflowRun({
321
+ resourceId,
322
+ workflowId,
323
+ currentStepId: initialStepId,
324
+ status: "running" /* RUNNING */,
325
+ input,
326
+ maxRetries: options?.retries ?? workflow2.retries ?? 0,
327
+ timeoutAt,
328
+ idempotencyKey,
329
+ parentRunId,
330
+ parentStepId,
331
+ parentResourceId
332
+ }, targetDb);
333
+ const insertAndEnqueue = async (targetDb) => {
334
+ const result = await insertRun(targetDb);
335
+ if (enqueue && result.created) {
336
+ await this.enqueueWorkflowRun(result.run, options, targetDb);
312
337
  }
313
- return insertedRun;
314
- }, this.pool);
315
- this.logger.log("Started workflow run", {
338
+ return result;
339
+ };
340
+ const { run, created } = db ? await insertAndEnqueue(db) : await withPostgresTransaction(this.boss.getDb(), insertAndEnqueue, this.pool);
341
+ return { run, created };
342
+ }
343
+ async enqueueWorkflowRun(run, options, db) {
344
+ const job = {
316
345
  runId: run.id,
317
- workflowId
346
+ resourceId: run.resourceId ?? undefined,
347
+ workflowId: run.workflowId,
348
+ input: run.input
349
+ };
350
+ await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
351
+ startAfter: new Date,
352
+ expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
353
+ ...retrySendOptions(run.maxRetries),
354
+ ...db ? { db } : {}
355
+ });
356
+ }
357
+ async notifyParentOfChildTerminalRun(childRun) {
358
+ if (!childRun.parentRunId || !childRun.parentStepId) {
359
+ return;
360
+ }
361
+ const parentRun = await getWorkflowRun({
362
+ runId: childRun.parentRunId,
363
+ resourceId: childRun.parentResourceId ?? undefined
364
+ }, { db: this.db });
365
+ if (!parentRun || parentRun.status === "completed" /* COMPLETED */ || parentRun.status === "failed" /* FAILED */ || parentRun.status === "cancelled" /* CANCELLED */) {
366
+ return;
367
+ }
368
+ await this.triggerEvent({
369
+ runId: parentRun.id,
370
+ resourceId: parentRun.resourceId ?? undefined,
371
+ eventName: getInvokeChildWorkflowEventName(childRun.id)
318
372
  });
319
- return run;
320
373
  }
321
374
  async pauseWorkflow({
322
375
  runId,
@@ -348,6 +401,9 @@ class WorkflowEngine {
348
401
  if (current.status !== "paused" /* PAUSED */) {
349
402
  throw new WorkflowEngineError(`Cannot resume workflow run in '${current.status}' status, must be 'paused'`, current.workflowId, runId);
350
403
  }
404
+ if (this.getInvokeChildWorkflowStepEntry(current.timeline, current.currentStepId)) {
405
+ return current;
406
+ }
351
407
  return this.triggerEvent({
352
408
  runId,
353
409
  resourceId,
@@ -367,6 +423,9 @@ class WorkflowEngine {
367
423
  return run;
368
424
  }
369
425
  const stepId = run.currentStepId;
426
+ if (this.getInvokeChildWorkflowStepEntry(run.timeline, stepId)) {
427
+ return run;
428
+ }
370
429
  const waitForStep = this.getWaitForStepEntry(run.timeline, stepId);
371
430
  if (!waitForStep) {
372
431
  return run;
@@ -415,6 +474,7 @@ class WorkflowEngine {
415
474
  expectedStatuses: ["pending" /* PENDING */, "running" /* RUNNING */, "paused" /* PAUSED */]
416
475
  });
417
476
  this.logger.log(`cancelled workflow run with id ${runId}`);
477
+ await this.notifyParentOfChildTerminalRun(run);
418
478
  return run;
419
479
  }
420
480
  async triggerEvent({
@@ -652,6 +712,22 @@ class WorkflowEngine {
652
712
  }
653
713
  const timeoutMs = options?.timeout ? parseDuration(options.timeout) : undefined;
654
714
  return this.pollStep({ run, stepId, conditionFn, intervalMs, timeoutMs });
715
+ },
716
+ invokeChildWorkflow: async (stepId, refOrParams, inputArg, optionsArg) => {
717
+ if (!run) {
718
+ throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
719
+ }
720
+ const resolvedChildCall = this.resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg);
721
+ const childWorkflowInvocation = {
722
+ run,
723
+ stepId,
724
+ workflowId: resolvedChildCall.workflowId,
725
+ input: resolvedChildCall.input,
726
+ options: resolvedChildCall.options,
727
+ resourceId: resolvedChildCall.resourceId,
728
+ idempotencyKey: resolvedChildCall.idempotencyKey
729
+ };
730
+ return this.invokeChildWorkflowStep(childWorkflowInvocation);
655
731
  }
656
732
  };
657
733
  let step = { ...baseStep };
@@ -664,7 +740,9 @@ class WorkflowEngine {
664
740
  input: run.input,
665
741
  workflowId: run.workflowId,
666
742
  runId: run.id,
667
- timeline: run.timeline,
743
+ get timeline() {
744
+ return run?.timeline ?? {};
745
+ },
668
746
  logger: this.logger,
669
747
  step
670
748
  };
@@ -676,7 +754,7 @@ class WorkflowEngine {
676
754
  const shouldComplete = run.status === "running" /* RUNNING */ && (noParsedSteps || isLastParsedStep || hasPluginSteps && result !== undefined);
677
755
  if (shouldComplete) {
678
756
  const normalizedResult = result === undefined ? {} : result;
679
- await this.updateRun({
757
+ const completedRun = await this.updateRun({
680
758
  runId,
681
759
  resourceId: scopedResourceId,
682
760
  data: {
@@ -686,6 +764,7 @@ class WorkflowEngine {
686
764
  jobId: job?.id
687
765
  }
688
766
  });
767
+ await this.notifyParentOfChildTerminalRun(completedRun);
689
768
  this.logger.log("Workflow run completed.", {
690
769
  runId,
691
770
  workflowId
@@ -693,7 +772,7 @@ class WorkflowEngine {
693
772
  }
694
773
  } catch (error) {
695
774
  if (runId) {
696
- await this.updateRun({
775
+ const updatedRun = await this.updateRun({
697
776
  runId,
698
777
  resourceId: scopedResourceId,
699
778
  data: {
@@ -701,6 +780,9 @@ class WorkflowEngine {
701
780
  jobId: job?.id
702
781
  }
703
782
  });
783
+ if (updatedRun.status === "completed" /* COMPLETED */ || updatedRun.status === "failed" /* FAILED */ || updatedRun.status === "cancelled" /* CANCELLED */) {
784
+ await this.notifyParentOfChildTerminalRun(updatedRun);
785
+ }
704
786
  }
705
787
  throw error;
706
788
  }
@@ -712,7 +794,7 @@ class WorkflowEngine {
712
794
  const run = await getWorkflowRun({ runId }, { db: this.db });
713
795
  if (!run || run.status !== "running" /* RUNNING */)
714
796
  return;
715
- await this.updateRun({
797
+ const failedRun = await this.updateRun({
716
798
  runId,
717
799
  resourceId: run.resourceId ?? undefined,
718
800
  data: {
@@ -720,6 +802,7 @@ class WorkflowEngine {
720
802
  error: run.error ?? "Workflow run worker died or job expired before completion"
721
803
  }
722
804
  });
805
+ await this.notifyParentOfChildTerminalRun(failedRun);
723
806
  this.logger.log("Marked stuck workflow run as failed", {
724
807
  runId,
725
808
  workflowId: run.workflowId
@@ -730,9 +813,195 @@ class WorkflowEngine {
730
813
  return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
731
814
  }
732
815
  getWaitForStepEntry(timeline, stepId) {
733
- const entry = timeline[`${stepId}-wait-for`];
816
+ const entry = timeline[waitForTimelineKey(stepId)];
734
817
  return entry && typeof entry === "object" && "waitFor" in entry ? entry : null;
735
818
  }
819
+ getInvokeChildWorkflowStepEntry(timeline, stepId) {
820
+ const entry = timeline[invokeChildWorkflowTimelineKey(stepId)];
821
+ return isInvokeChildWorkflowTimelineEntry(entry) ? entry : null;
822
+ }
823
+ getCompletedChildOutput(childRun) {
824
+ return childRun.output === undefined ? {} : childRun.output;
825
+ }
826
+ throwForNonCompletedChild(childRun) {
827
+ throw new WorkflowEngineError(`Child workflow ${childRun.workflowId} ${childRun.status}${childRun.error ? `: ${childRun.error}` : ""}`, childRun.workflowId, childRun.id);
828
+ }
829
+ assertInvokeChildWorkflowStepOwnership({
830
+ childRun,
831
+ parentRun,
832
+ stepId,
833
+ workflowId
834
+ }) {
835
+ const expectedParentResourceId = parentRun.resourceId ?? null;
836
+ const matches = childRun.workflowId === workflowId && childRun.parentRunId === parentRun.id && childRun.parentStepId === stepId && childRun.parentResourceId === expectedParentResourceId;
837
+ if (!matches) {
838
+ throw new WorkflowEngineError(`Idempotency key resolved to workflow run ${childRun.id}, which does not belong to invokeChildWorkflow step '${stepId}'`, workflowId, parentRun.id);
839
+ }
840
+ }
841
+ async invokeChildWorkflowStep({
842
+ run,
843
+ stepId,
844
+ workflowId,
845
+ input,
846
+ resourceId,
847
+ idempotencyKey,
848
+ options
849
+ }) {
850
+ let invokeOutput;
851
+ let hasInvokeOutput = false;
852
+ const childResourceId = resourceId ?? run.resourceId ?? undefined;
853
+ const childIdempotencyKey = idempotencyKey;
854
+ await withPostgresTransaction(this.db, async (db) => {
855
+ const lockedRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
856
+ if (lockedRun.status === "cancelled" /* CANCELLED */ || lockedRun.status === "paused" /* PAUSED */ || lockedRun.status === "failed" /* FAILED */) {
857
+ return;
858
+ }
859
+ const lockedCached = this.getCachedStepEntry(lockedRun.timeline, stepId);
860
+ if (lockedCached?.output !== undefined) {
861
+ invokeOutput = lockedCached.output;
862
+ hasInvokeOutput = true;
863
+ return;
864
+ }
865
+ const lockedInvoke = this.getInvokeChildWorkflowStepEntry(lockedRun.timeline, stepId);
866
+ if (lockedInvoke) {
867
+ const existingChildResourceId = "childResourceId" in lockedInvoke.invokeChildWorkflow ? lockedInvoke.invokeChildWorkflow.childResourceId ?? undefined : childResourceId;
868
+ const existingChildRun = await this.getRun({
869
+ runId: lockedInvoke.invokeChildWorkflow.childRunId,
870
+ resourceId: existingChildResourceId
871
+ });
872
+ if (existingChildRun.status === "completed" /* COMPLETED */) {
873
+ invokeOutput = this.getCompletedChildOutput(existingChildRun);
874
+ hasInvokeOutput = true;
875
+ await this.updateRun({
876
+ runId: run.id,
877
+ resourceId: run.resourceId ?? undefined,
878
+ data: {
879
+ timeline: merge(lockedRun.timeline, {
880
+ [stepId]: {
881
+ output: invokeOutput,
882
+ timestamp: new Date
883
+ }
884
+ })
885
+ }
886
+ }, { db });
887
+ return;
888
+ }
889
+ if (existingChildRun.status === "failed" /* FAILED */ || existingChildRun.status === "cancelled" /* CANCELLED */) {
890
+ this.throwForNonCompletedChild(existingChildRun);
891
+ }
892
+ await this.pauseRunForWait({
893
+ run: lockedRun,
894
+ stepId,
895
+ eventName: getInvokeChildWorkflowEventName(existingChildRun.id),
896
+ skipOutput: true,
897
+ db
898
+ });
899
+ return;
900
+ }
901
+ const result = await this.createWorkflowRun({
902
+ workflowId,
903
+ input,
904
+ resourceId: childResourceId,
905
+ idempotencyKey: childIdempotencyKey,
906
+ options,
907
+ parentRunId: run.id,
908
+ parentStepId: stepId,
909
+ parentResourceId: run.resourceId ?? undefined,
910
+ enqueue: true,
911
+ db
912
+ });
913
+ const childRun = result.run;
914
+ if (!result.created) {
915
+ this.assertInvokeChildWorkflowStepOwnership({
916
+ childRun,
917
+ parentRun: lockedRun,
918
+ stepId,
919
+ workflowId
920
+ });
921
+ if (childRun.status === "completed" /* COMPLETED */) {
922
+ invokeOutput = this.getCompletedChildOutput(childRun);
923
+ hasInvokeOutput = true;
924
+ await this.updateRun({
925
+ runId: run.id,
926
+ resourceId: run.resourceId ?? undefined,
927
+ data: {
928
+ timeline: merge(lockedRun.timeline, {
929
+ [invokeChildWorkflowTimelineKey(stepId)]: {
930
+ invokeChildWorkflow: {
931
+ childRunId: childRun.id,
932
+ childWorkflowId: childRun.workflowId,
933
+ childResourceId: childRun.resourceId
934
+ },
935
+ timestamp: new Date
936
+ },
937
+ [stepId]: {
938
+ output: invokeOutput,
939
+ timestamp: new Date
940
+ }
941
+ })
942
+ }
943
+ }, { db });
944
+ return;
945
+ }
946
+ if (childRun.status === "failed" /* FAILED */ || childRun.status === "cancelled" /* CANCELLED */) {
947
+ this.throwForNonCompletedChild(childRun);
948
+ }
949
+ }
950
+ await this.pauseRunForWait({
951
+ run: lockedRun,
952
+ stepId,
953
+ eventName: getInvokeChildWorkflowEventName(childRun.id),
954
+ skipOutput: true,
955
+ db,
956
+ timeline: merge(lockedRun.timeline, {
957
+ [invokeChildWorkflowTimelineKey(stepId)]: {
958
+ invokeChildWorkflow: {
959
+ childRunId: childRun.id,
960
+ childWorkflowId: childRun.workflowId,
961
+ childResourceId: childRun.resourceId
962
+ },
963
+ timestamp: new Date
964
+ }
965
+ })
966
+ });
967
+ }, this.pool);
968
+ if (hasInvokeOutput) {
969
+ return invokeOutput;
970
+ }
971
+ }
972
+ async pauseRunForWait({
973
+ run,
974
+ stepId,
975
+ eventName,
976
+ timeoutEvent,
977
+ skipOutput,
978
+ db,
979
+ timeline
980
+ }) {
981
+ const baseTimeline = timeline ?? run.timeline;
982
+ const waitFor = {};
983
+ if (eventName)
984
+ waitFor.eventName = eventName;
985
+ if (timeoutEvent)
986
+ waitFor.timeoutEvent = timeoutEvent;
987
+ if (skipOutput)
988
+ waitFor.skipOutput = true;
989
+ await this.updateRun({
990
+ runId: run.id,
991
+ resourceId: run.resourceId ?? undefined,
992
+ data: {
993
+ status: "paused" /* PAUSED */,
994
+ currentStepId: stepId,
995
+ pausedAt: new Date,
996
+ timeline: merge(baseTimeline, {
997
+ [waitForTimelineKey(stepId)]: {
998
+ waitFor,
999
+ timestamp: new Date
1000
+ }
1001
+ })
1002
+ }
1003
+ }, { db });
1004
+ }
736
1005
  async runStep({
737
1006
  stepId,
738
1007
  run,
@@ -770,7 +1039,7 @@ class WorkflowEngine {
770
1039
  if (output === undefined) {
771
1040
  output = {};
772
1041
  }
773
- run = await this.updateRun({
1042
+ const updated = await this.updateRun({
774
1043
  runId: run.id,
775
1044
  resourceId: run.resourceId ?? undefined,
776
1045
  data: {
@@ -782,6 +1051,7 @@ class WorkflowEngine {
782
1051
  })
783
1052
  }
784
1053
  }, { db });
1054
+ Object.assign(run, updated);
785
1055
  return output;
786
1056
  } catch (error) {
787
1057
  this.logger.error(`Step ${stepId} failed:`, error, {
@@ -821,21 +1091,7 @@ ${error.stack}` : String(error)
821
1091
  const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
822
1092
  await withPostgresTransaction(this.db, async (db) => {
823
1093
  const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
824
- return this.updateRun({
825
- runId: run.id,
826
- resourceId: run.resourceId ?? undefined,
827
- data: {
828
- status: "paused" /* PAUSED */,
829
- currentStepId: stepId,
830
- pausedAt: new Date,
831
- timeline: merge(freshRun.timeline, {
832
- [`${stepId}-wait-for`]: {
833
- waitFor: { eventName, timeoutEvent },
834
- timestamp: new Date
835
- }
836
- })
837
- }
838
- }, { db });
1094
+ return this.pauseRunForWait({ run: freshRun, stepId, eventName, timeoutEvent, db });
839
1095
  }, this.pool);
840
1096
  if (timeoutDate && timeoutEvent) {
841
1097
  try {
@@ -949,7 +1205,7 @@ ${error.stack}` : String(error)
949
1205
  pausedAt: new Date,
950
1206
  timeline: merge(freshRun.timeline, {
951
1207
  [`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
952
- [`${stepId}-wait-for`]: {
1208
+ [waitForTimelineKey(stepId)]: {
953
1209
  waitFor: { timeoutEvent: pollEvent, skipOutput: true },
954
1210
  timestamp: new Date
955
1211
  }
@@ -1025,17 +1281,13 @@ ${error.stack}` : String(error)
1025
1281
  }
1026
1282
  export {
1027
1283
  workflow,
1028
- validateWorkflowId,
1029
- validateResourceId,
1030
- parseDuration,
1031
1284
  createWorkflowRef,
1032
1285
  WorkflowStatus,
1033
1286
  WorkflowRunNotFoundError,
1034
1287
  WorkflowEngineError,
1035
1288
  WorkflowEngine,
1036
- WorkflowClient,
1037
- StepType
1289
+ WorkflowClient
1038
1290
  };
1039
1291
 
1040
- //# debugId=FC991C83D3B1165A64756E2164756E21
1292
+ //# debugId=F9D6596295A7020B64756E2164756E21
1041
1293
  //# sourceMappingURL=index.js.map