pg-workflows 0.8.3 → 0.10.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
@@ -2,6 +2,7 @@ import {
2
2
  DEFAULT_PGBOSS_SCHEMA,
3
3
  PAUSE_EVENT_NAME,
4
4
  StepType,
5
+ WORKFLOW_RUN_DLQ_QUEUE_NAME,
5
6
  WORKFLOW_RUN_QUEUE_NAME,
6
7
  WorkflowClient,
7
8
  WorkflowEngineError,
@@ -11,11 +12,16 @@ import {
11
12
  getWorkflowRun,
12
13
  getWorkflowRuns,
13
14
  insertWorkflowRun,
15
+ invokeChildWorkflowTimelineKey,
16
+ isInvokeChildWorkflowTimelineEntry,
14
17
  runMigrations,
15
18
  updateWorkflowRun,
19
+ validateResourceId,
20
+ validateWorkflowId,
21
+ waitForTimelineKey,
16
22
  withPostgresTransaction,
17
23
  workflow
18
- } from "./shared/chunk-2xy8z3xp.js";
24
+ } from "./shared/chunk-nygamc7b.js";
19
25
  // src/duration.ts
20
26
  import parse from "parse-duration";
21
27
  var MS_PER_SECOND = 1000;
@@ -91,7 +97,7 @@ function parseWorkflowHandler(handler) {
91
97
  const propertyAccess = node.expression;
92
98
  const objectName = propertyAccess.expression.getText(sourceFile);
93
99
  const methodName = propertyAccess.name.text;
94
- if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll")) {
100
+ if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll" || methodName === "invokeChildWorkflow")) {
95
101
  const firstArg = node.arguments[0];
96
102
  if (firstArg) {
97
103
  const { id, isDynamic } = extractStepId(firstArg);
@@ -124,13 +130,21 @@ var StepTypeToIcon = {
124
130
  ["pause" /* PAUSE */]: "⏸",
125
131
  ["waitUntil" /* WAIT_UNTIL */]: "⏲",
126
132
  ["delay" /* DELAY */]: "⏱",
127
- ["poll" /* POLL */]: "↻"
133
+ ["poll" /* POLL */]: "↻",
134
+ ["invokeChildWorkflow" /* INVOKE_CHILD_WORKFLOW */]: "↪"
128
135
  };
129
136
  var defaultLogger = {
130
137
  log: (_message) => console.warn(_message),
131
138
  error: (message, error) => console.error(message, error)
132
139
  };
133
140
  var defaultExpireInSeconds = process.env.WORKFLOW_RUN_EXPIRE_IN_SECONDS ? Number.parseInt(process.env.WORKFLOW_RUN_EXPIRE_IN_SECONDS, 10) : 5 * 60;
141
+ var retrySendOptions = (maxRetries) => ({
142
+ retryLimit: maxRetries,
143
+ retryBackoff: true,
144
+ retryDelay: 1
145
+ });
146
+ var getInvokeChildWorkflowEventName = (childRunId) => `__invoke_child_workflow_completed:${childRunId}`;
147
+ var defaultHeartbeatSeconds = process.env.WORKFLOW_RUN_HEARTBEAT_SECONDS ? Number.parseInt(process.env.WORKFLOW_RUN_HEARTBEAT_SECONDS, 10) : 30;
134
148
 
135
149
  class WorkflowEngine {
136
150
  boss;
@@ -164,7 +178,10 @@ class WorkflowEngine {
164
178
  }
165
179
  this.db = this.boss.getDb();
166
180
  }
167
- async start(asEngine = true, { batchSize } = { batchSize: 1 }) {
181
+ async start(asEngine = true, {
182
+ batchSize = 1,
183
+ heartbeatSeconds = defaultHeartbeatSeconds
184
+ } = {}) {
168
185
  if (this._started) {
169
186
  return;
170
187
  }
@@ -175,13 +192,21 @@ class WorkflowEngine {
175
192
  await this.registerWorkflow(workflow2);
176
193
  }
177
194
  }
178
- await this.boss.createQueue(WORKFLOW_RUN_QUEUE_NAME);
195
+ const mainQueueOptions = {
196
+ retryLimit: 0,
197
+ deadLetter: WORKFLOW_RUN_DLQ_QUEUE_NAME,
198
+ heartbeatSeconds
199
+ };
200
+ await this.boss.createQueue(WORKFLOW_RUN_DLQ_QUEUE_NAME, { retryLimit: 0 });
201
+ await this.boss.createQueue(WORKFLOW_RUN_QUEUE_NAME, mainQueueOptions);
202
+ await this.boss.updateQueue(WORKFLOW_RUN_QUEUE_NAME, mainQueueOptions);
179
203
  const numWorkers = +(process.env.WORKFLOW_RUN_WORKERS ?? 3);
180
204
  if (asEngine) {
181
- for (let i = 0;i < numWorkers; i++) {
182
- await this.boss.work(WORKFLOW_RUN_QUEUE_NAME, { pollingIntervalSeconds: 0.5, batchSize }, (job) => this.handleWorkflowRun(job));
205
+ await Promise.all(Array.from({ length: numWorkers }, (_, i) => this.boss.work(WORKFLOW_RUN_QUEUE_NAME, { pollingIntervalSeconds: 0.5, batchSize, includeMetadata: true }, (jobs) => this.handleWorkflowRun(jobs)).then(() => {
183
206
  this.logger.log(`Worker ${i + 1}/${numWorkers} started for queue ${WORKFLOW_RUN_QUEUE_NAME}`);
184
- }
207
+ })));
208
+ await this.boss.work(WORKFLOW_RUN_DLQ_QUEUE_NAME, { pollingIntervalSeconds: 0.5, batchSize: 1 }, (jobs) => this.handleWorkflowRunDlq(jobs));
209
+ this.logger.log(`Worker started for queue ${WORKFLOW_RUN_DLQ_QUEUE_NAME}`);
185
210
  }
186
211
  this._started = true;
187
212
  this.logger.log("Workflow engine started!");
@@ -224,29 +249,57 @@ class WorkflowEngine {
224
249
  this.workflows.clear();
225
250
  return this;
226
251
  }
227
- async startWorkflow(refOrParams, inputArg, optionsArg) {
228
- let workflowId;
229
- let input;
230
- let resourceId;
231
- let idempotencyKey;
232
- let options;
252
+ resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg) {
233
253
  if (typeof refOrParams === "function" && "id" in refOrParams) {
234
- workflowId = refOrParams.id;
235
- input = inputArg;
236
- options = optionsArg;
237
- resourceId = optionsArg?.resourceId;
238
- idempotencyKey = optionsArg?.idempotencyKey;
239
- } else {
240
- const params = refOrParams;
241
- workflowId = params.workflowId;
242
- input = params.input;
243
- resourceId = params.resourceId;
244
- idempotencyKey = params.idempotencyKey;
245
- options = params.options;
254
+ return {
255
+ workflowId: refOrParams.id,
256
+ input: inputArg,
257
+ options: optionsArg,
258
+ resourceId: optionsArg?.resourceId,
259
+ idempotencyKey: optionsArg?.idempotencyKey
260
+ };
246
261
  }
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);
247
273
  if (!this._started) {
248
274
  await this.start(false, { batchSize: options?.batchSize ?? 1 });
249
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);
250
303
  const workflow2 = this.workflows.get(workflowId);
251
304
  if (!workflow2) {
252
305
  throw new WorkflowEngineError(`Unknown workflow ${workflowId}`);
@@ -263,37 +316,60 @@ class WorkflowEngine {
263
316
  }
264
317
  }
265
318
  const initialStepId = workflow2.steps[0]?.id ?? "__start__";
266
- const run = await withPostgresTransaction(this.boss.getDb(), async (_db) => {
267
- const timeoutAt = options?.timeout ? new Date(Date.now() + options.timeout) : workflow2.timeout ? new Date(Date.now() + workflow2.timeout) : null;
268
- const { run: insertedRun, created } = await insertWorkflowRun({
269
- resourceId,
270
- workflowId,
271
- currentStepId: initialStepId,
272
- status: "running" /* RUNNING */,
273
- input,
274
- maxRetries: options?.retries ?? workflow2.retries ?? 0,
275
- timeoutAt,
276
- idempotencyKey
277
- }, _db);
278
- if (created) {
279
- const job = {
280
- runId: insertedRun.id,
281
- resourceId,
282
- workflowId,
283
- input
284
- };
285
- await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
286
- startAfter: new Date,
287
- expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds
288
- });
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);
289
337
  }
290
- return insertedRun;
291
- }, this.pool);
292
- 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 = {
293
345
  runId: run.id,
294
- 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)
295
372
  });
296
- return run;
297
373
  }
298
374
  async pauseWorkflow({
299
375
  runId,
@@ -325,6 +401,9 @@ class WorkflowEngine {
325
401
  if (current.status !== "paused" /* PAUSED */) {
326
402
  throw new WorkflowEngineError(`Cannot resume workflow run in '${current.status}' status, must be 'paused'`, current.workflowId, runId);
327
403
  }
404
+ if (this.getInvokeChildWorkflowStepEntry(current.timeline, current.currentStepId)) {
405
+ return current;
406
+ }
328
407
  return this.triggerEvent({
329
408
  runId,
330
409
  resourceId,
@@ -344,6 +423,9 @@ class WorkflowEngine {
344
423
  return run;
345
424
  }
346
425
  const stepId = run.currentStepId;
426
+ if (this.getInvokeChildWorkflowStepEntry(run.timeline, stepId)) {
427
+ return run;
428
+ }
347
429
  const waitForStep = this.getWaitForStepEntry(run.timeline, stepId);
348
430
  if (!waitForStep) {
349
431
  return run;
@@ -392,6 +474,7 @@ class WorkflowEngine {
392
474
  expectedStatuses: ["pending" /* PENDING */, "running" /* RUNNING */, "paused" /* PAUSED */]
393
475
  });
394
476
  this.logger.log(`cancelled workflow run with id ${runId}`);
477
+ await this.notifyParentOfChildTerminalRun(run);
395
478
  return run;
396
479
  }
397
480
  async triggerEvent({
@@ -415,7 +498,8 @@ class WorkflowEngine {
415
498
  }
416
499
  };
417
500
  await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
418
- expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds
501
+ expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
502
+ ...retrySendOptions(run.maxRetries)
419
503
  });
420
504
  this.logger.log(`event ${eventName} sent for workflow run with id ${runId}`);
421
505
  return run;
@@ -494,7 +578,7 @@ class WorkflowEngine {
494
578
  return run.resourceId ?? undefined;
495
579
  }
496
580
  async handleWorkflowRun([job]) {
497
- const { runId = "", resourceId, workflowId = "", input, event } = job?.data ?? {};
581
+ const { runId = "", resourceId, workflowId = "", event } = job?.data ?? {};
498
582
  let run;
499
583
  let scopedResourceId;
500
584
  try {
@@ -517,6 +601,14 @@ class WorkflowEngine {
517
601
  throw new WorkflowEngineError(`Workflow run ${runId} does not match job workflowId ${workflowId}`, workflowId, runId);
518
602
  }
519
603
  scopedResourceId = this.resolveScopedResourceId(resourceId, run);
604
+ if (job?.retryCount !== undefined && run.retryCount !== job.retryCount) {
605
+ await this.updateRun({
606
+ runId,
607
+ resourceId: scopedResourceId,
608
+ data: { retryCount: job.retryCount }
609
+ });
610
+ run = { ...run, retryCount: job.retryCount };
611
+ }
520
612
  if (run.status === "cancelled" /* CANCELLED */) {
521
613
  this.logger.log(`Workflow run ${runId} is cancelled, skipping`);
522
614
  return;
@@ -620,6 +712,22 @@ class WorkflowEngine {
620
712
  }
621
713
  const timeoutMs = options?.timeout ? parseDuration(options.timeout) : undefined;
622
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);
623
731
  }
624
732
  };
625
733
  let step = { ...baseStep };
@@ -644,7 +752,7 @@ class WorkflowEngine {
644
752
  const shouldComplete = run.status === "running" /* RUNNING */ && (noParsedSteps || isLastParsedStep || hasPluginSteps && result !== undefined);
645
753
  if (shouldComplete) {
646
754
  const normalizedResult = result === undefined ? {} : result;
647
- await this.updateRun({
755
+ const completedRun = await this.updateRun({
648
756
  runId,
649
757
  resourceId: scopedResourceId,
650
758
  data: {
@@ -654,56 +762,244 @@ class WorkflowEngine {
654
762
  jobId: job?.id
655
763
  }
656
764
  });
765
+ await this.notifyParentOfChildTerminalRun(completedRun);
657
766
  this.logger.log("Workflow run completed.", {
658
767
  runId,
659
768
  workflowId
660
769
  });
661
770
  }
662
771
  } catch (error) {
663
- if (run && run.retryCount < run.maxRetries) {
664
- await this.updateRun({
665
- runId,
666
- resourceId: scopedResourceId,
667
- data: {
668
- retryCount: run.retryCount + 1,
669
- jobId: job?.id
670
- }
671
- });
672
- const retryDelay = 2 ** run.retryCount * 1000;
673
- const pgBossJob = {
674
- runId,
675
- resourceId: scopedResourceId,
676
- workflowId,
677
- input
678
- };
679
- await this.boss?.send("workflow-run", pgBossJob, {
680
- startAfter: new Date(Date.now() + retryDelay),
681
- expireInSeconds: defaultExpireInSeconds
682
- });
683
- return;
684
- }
685
772
  if (runId) {
686
- await this.updateRun({
773
+ const updatedRun = await this.updateRun({
687
774
  runId,
688
775
  resourceId: scopedResourceId,
689
776
  data: {
690
- status: "failed" /* FAILED */,
691
777
  error: error instanceof Error ? error.message : String(error),
692
778
  jobId: job?.id
693
779
  }
694
780
  });
781
+ if (updatedRun.status === "completed" /* COMPLETED */ || updatedRun.status === "failed" /* FAILED */ || updatedRun.status === "cancelled" /* CANCELLED */) {
782
+ await this.notifyParentOfChildTerminalRun(updatedRun);
783
+ }
695
784
  }
696
785
  throw error;
697
786
  }
698
787
  }
788
+ async handleWorkflowRunDlq([job]) {
789
+ const { runId } = job?.data ?? {};
790
+ if (!runId)
791
+ return;
792
+ const run = await getWorkflowRun({ runId }, { db: this.db });
793
+ if (!run || run.status !== "running" /* RUNNING */)
794
+ return;
795
+ const failedRun = await this.updateRun({
796
+ runId,
797
+ resourceId: run.resourceId ?? undefined,
798
+ data: {
799
+ status: "failed" /* FAILED */,
800
+ error: run.error ?? "Workflow run worker died or job expired before completion"
801
+ }
802
+ });
803
+ await this.notifyParentOfChildTerminalRun(failedRun);
804
+ this.logger.log("Marked stuck workflow run as failed", {
805
+ runId,
806
+ workflowId: run.workflowId
807
+ });
808
+ }
699
809
  getCachedStepEntry(timeline, stepId) {
700
810
  const stepEntry = timeline[stepId];
701
811
  return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
702
812
  }
703
813
  getWaitForStepEntry(timeline, stepId) {
704
- const entry = timeline[`${stepId}-wait-for`];
814
+ const entry = timeline[waitForTimelineKey(stepId)];
705
815
  return entry && typeof entry === "object" && "waitFor" in entry ? entry : null;
706
816
  }
817
+ getInvokeChildWorkflowStepEntry(timeline, stepId) {
818
+ const entry = timeline[invokeChildWorkflowTimelineKey(stepId)];
819
+ return isInvokeChildWorkflowTimelineEntry(entry) ? entry : null;
820
+ }
821
+ getCompletedChildOutput(childRun) {
822
+ return childRun.output === undefined ? {} : childRun.output;
823
+ }
824
+ throwForNonCompletedChild(childRun) {
825
+ throw new WorkflowEngineError(`Child workflow ${childRun.workflowId} ${childRun.status}${childRun.error ? `: ${childRun.error}` : ""}`, childRun.workflowId, childRun.id);
826
+ }
827
+ assertInvokeChildWorkflowStepOwnership({
828
+ childRun,
829
+ parentRun,
830
+ stepId,
831
+ workflowId
832
+ }) {
833
+ const expectedParentResourceId = parentRun.resourceId ?? null;
834
+ const matches = childRun.workflowId === workflowId && childRun.parentRunId === parentRun.id && childRun.parentStepId === stepId && childRun.parentResourceId === expectedParentResourceId;
835
+ if (!matches) {
836
+ throw new WorkflowEngineError(`Idempotency key resolved to workflow run ${childRun.id}, which does not belong to invokeChildWorkflow step '${stepId}'`, workflowId, parentRun.id);
837
+ }
838
+ }
839
+ async invokeChildWorkflowStep({
840
+ run,
841
+ stepId,
842
+ workflowId,
843
+ input,
844
+ resourceId,
845
+ idempotencyKey,
846
+ options
847
+ }) {
848
+ let invokeOutput;
849
+ let hasInvokeOutput = false;
850
+ const childResourceId = resourceId ?? run.resourceId ?? undefined;
851
+ const childIdempotencyKey = idempotencyKey;
852
+ await withPostgresTransaction(this.db, async (db) => {
853
+ const lockedRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
854
+ if (lockedRun.status === "cancelled" /* CANCELLED */ || lockedRun.status === "paused" /* PAUSED */ || lockedRun.status === "failed" /* FAILED */) {
855
+ return;
856
+ }
857
+ const lockedCached = this.getCachedStepEntry(lockedRun.timeline, stepId);
858
+ if (lockedCached?.output !== undefined) {
859
+ invokeOutput = lockedCached.output;
860
+ hasInvokeOutput = true;
861
+ return;
862
+ }
863
+ const lockedInvoke = this.getInvokeChildWorkflowStepEntry(lockedRun.timeline, stepId);
864
+ if (lockedInvoke) {
865
+ const existingChildResourceId = "childResourceId" in lockedInvoke.invokeChildWorkflow ? lockedInvoke.invokeChildWorkflow.childResourceId ?? undefined : childResourceId;
866
+ const existingChildRun = await this.getRun({
867
+ runId: lockedInvoke.invokeChildWorkflow.childRunId,
868
+ resourceId: existingChildResourceId
869
+ });
870
+ if (existingChildRun.status === "completed" /* COMPLETED */) {
871
+ invokeOutput = this.getCompletedChildOutput(existingChildRun);
872
+ hasInvokeOutput = true;
873
+ await this.updateRun({
874
+ runId: run.id,
875
+ resourceId: run.resourceId ?? undefined,
876
+ data: {
877
+ timeline: merge(lockedRun.timeline, {
878
+ [stepId]: {
879
+ output: invokeOutput,
880
+ timestamp: new Date
881
+ }
882
+ })
883
+ }
884
+ }, { db });
885
+ return;
886
+ }
887
+ if (existingChildRun.status === "failed" /* FAILED */ || existingChildRun.status === "cancelled" /* CANCELLED */) {
888
+ this.throwForNonCompletedChild(existingChildRun);
889
+ }
890
+ await this.pauseRunForWait({
891
+ run: lockedRun,
892
+ stepId,
893
+ eventName: getInvokeChildWorkflowEventName(existingChildRun.id),
894
+ skipOutput: true,
895
+ db
896
+ });
897
+ return;
898
+ }
899
+ const result = await this.createWorkflowRun({
900
+ workflowId,
901
+ input,
902
+ resourceId: childResourceId,
903
+ idempotencyKey: childIdempotencyKey,
904
+ options,
905
+ parentRunId: run.id,
906
+ parentStepId: stepId,
907
+ parentResourceId: run.resourceId ?? undefined,
908
+ enqueue: true,
909
+ db
910
+ });
911
+ const childRun = result.run;
912
+ if (!result.created) {
913
+ this.assertInvokeChildWorkflowStepOwnership({
914
+ childRun,
915
+ parentRun: lockedRun,
916
+ stepId,
917
+ workflowId
918
+ });
919
+ if (childRun.status === "completed" /* COMPLETED */) {
920
+ invokeOutput = this.getCompletedChildOutput(childRun);
921
+ hasInvokeOutput = true;
922
+ await this.updateRun({
923
+ runId: run.id,
924
+ resourceId: run.resourceId ?? undefined,
925
+ data: {
926
+ timeline: merge(lockedRun.timeline, {
927
+ [invokeChildWorkflowTimelineKey(stepId)]: {
928
+ invokeChildWorkflow: {
929
+ childRunId: childRun.id,
930
+ childWorkflowId: childRun.workflowId,
931
+ childResourceId: childRun.resourceId
932
+ },
933
+ timestamp: new Date
934
+ },
935
+ [stepId]: {
936
+ output: invokeOutput,
937
+ timestamp: new Date
938
+ }
939
+ })
940
+ }
941
+ }, { db });
942
+ return;
943
+ }
944
+ if (childRun.status === "failed" /* FAILED */ || childRun.status === "cancelled" /* CANCELLED */) {
945
+ this.throwForNonCompletedChild(childRun);
946
+ }
947
+ }
948
+ await this.pauseRunForWait({
949
+ run: lockedRun,
950
+ stepId,
951
+ eventName: getInvokeChildWorkflowEventName(childRun.id),
952
+ skipOutput: true,
953
+ db,
954
+ timeline: merge(lockedRun.timeline, {
955
+ [invokeChildWorkflowTimelineKey(stepId)]: {
956
+ invokeChildWorkflow: {
957
+ childRunId: childRun.id,
958
+ childWorkflowId: childRun.workflowId,
959
+ childResourceId: childRun.resourceId
960
+ },
961
+ timestamp: new Date
962
+ }
963
+ })
964
+ });
965
+ }, this.pool);
966
+ if (hasInvokeOutput) {
967
+ return invokeOutput;
968
+ }
969
+ }
970
+ async pauseRunForWait({
971
+ run,
972
+ stepId,
973
+ eventName,
974
+ timeoutEvent,
975
+ skipOutput,
976
+ db,
977
+ timeline
978
+ }) {
979
+ const baseTimeline = timeline ?? run.timeline;
980
+ const waitFor = {};
981
+ if (eventName)
982
+ waitFor.eventName = eventName;
983
+ if (timeoutEvent)
984
+ waitFor.timeoutEvent = timeoutEvent;
985
+ if (skipOutput)
986
+ waitFor.skipOutput = true;
987
+ await this.updateRun({
988
+ runId: run.id,
989
+ resourceId: run.resourceId ?? undefined,
990
+ data: {
991
+ status: "paused" /* PAUSED */,
992
+ currentStepId: stepId,
993
+ pausedAt: new Date,
994
+ timeline: merge(baseTimeline, {
995
+ [waitForTimelineKey(stepId)]: {
996
+ waitFor,
997
+ timestamp: new Date
998
+ }
999
+ })
1000
+ }
1001
+ }, { db });
1002
+ }
707
1003
  async runStep({
708
1004
  stepId,
709
1005
  run,
@@ -792,21 +1088,7 @@ ${error.stack}` : String(error)
792
1088
  const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
793
1089
  await withPostgresTransaction(this.db, async (db) => {
794
1090
  const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
795
- return this.updateRun({
796
- runId: run.id,
797
- resourceId: run.resourceId ?? undefined,
798
- data: {
799
- status: "paused" /* PAUSED */,
800
- currentStepId: stepId,
801
- pausedAt: new Date,
802
- timeline: merge(freshRun.timeline, {
803
- [`${stepId}-wait-for`]: {
804
- waitFor: { eventName, timeoutEvent },
805
- timestamp: new Date
806
- }
807
- })
808
- }
809
- }, { db });
1091
+ return this.pauseRunForWait({ run: freshRun, stepId, eventName, timeoutEvent, db });
810
1092
  }, this.pool);
811
1093
  if (timeoutDate && timeoutEvent) {
812
1094
  try {
@@ -819,7 +1101,8 @@ ${error.stack}` : String(error)
819
1101
  };
820
1102
  await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
821
1103
  startAfter: timeoutDate.getTime() <= Date.now() ? new Date : timeoutDate,
822
- expireInSeconds: defaultExpireInSeconds
1104
+ expireInSeconds: defaultExpireInSeconds,
1105
+ ...retrySendOptions(run.maxRetries)
823
1106
  });
824
1107
  } catch (error) {
825
1108
  await this.updateRun({
@@ -919,7 +1202,7 @@ ${error.stack}` : String(error)
919
1202
  pausedAt: new Date,
920
1203
  timeline: merge(freshRun.timeline, {
921
1204
  [`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
922
- [`${stepId}-wait-for`]: {
1205
+ [waitForTimelineKey(stepId)]: {
923
1206
  waitFor: { timeoutEvent: pollEvent, skipOutput: true },
924
1207
  timestamp: new Date
925
1208
  }
@@ -936,7 +1219,8 @@ ${error.stack}` : String(error)
936
1219
  event: { name: pollEvent, data: {} }
937
1220
  }, {
938
1221
  startAfter: new Date(Date.now() + intervalMs),
939
- expireInSeconds: defaultExpireInSeconds
1222
+ expireInSeconds: defaultExpireInSeconds,
1223
+ ...retrySendOptions(run.maxRetries)
940
1224
  });
941
1225
  } catch (error) {
942
1226
  await this.updateRun({
@@ -979,6 +1263,9 @@ ${error.stack}` : String(error)
979
1263
  statuses,
980
1264
  workflowId
981
1265
  }) {
1266
+ if (workflowId)
1267
+ validateWorkflowId(workflowId);
1268
+ validateResourceId(resourceId);
982
1269
  return getWorkflowRuns({
983
1270
  resourceId,
984
1271
  startingAfter,
@@ -991,6 +1278,8 @@ ${error.stack}` : String(error)
991
1278
  }
992
1279
  export {
993
1280
  workflow,
1281
+ validateWorkflowId,
1282
+ validateResourceId,
994
1283
  parseDuration,
995
1284
  createWorkflowRef,
996
1285
  WorkflowStatus,
@@ -1001,5 +1290,5 @@ export {
1001
1290
  StepType
1002
1291
  };
1003
1292
 
1004
- //# debugId=9A3E4733F40A491264756E2164756E21
1293
+ //# debugId=F3273B34BE68C60E64756E2164756E21
1005
1294
  //# sourceMappingURL=index.js.map