pg-workflows 0.9.0 → 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.cjs CHANGED
@@ -90,10 +90,15 @@ var WORKFLOW_RUN_DLQ_QUEUE_NAME = "workflow_run_dlq";
90
90
  var DEFAULT_PGBOSS_SCHEMA = "pgboss_v12_pgworkflow";
91
91
  var MAX_WORKFLOW_ID_LENGTH = 256;
92
92
  var MAX_RESOURCE_ID_LENGTH = 256;
93
+ var INVOKE_CHILD_WORKFLOW_TIMELINE_SUFFIX = "invoke-child-workflow";
94
+ var WAIT_FOR_TIMELINE_SUFFIX = "wait-for";
95
+ var invokeChildWorkflowTimelineKey = (stepId) => `${stepId}-${INVOKE_CHILD_WORKFLOW_TIMELINE_SUFFIX}`;
96
+ var waitForTimelineKey = (stepId) => `${stepId}-${WAIT_FOR_TIMELINE_SUFFIX}`;
97
+ var isInvokeChildWorkflowTimelineEntry = (entry) => !!entry && typeof entry === "object" && ("invokeChildWorkflow" in entry);
93
98
 
94
99
  // src/db/migration.ts
95
100
  var MIGRATION_LOCK_ID = 738291645;
96
- var CURRENT_SCHEMA_VERSION = 3;
101
+ var CURRENT_SCHEMA_VERSION = 4;
97
102
  async function runMigrations(db) {
98
103
  if (await isSchemaUpToDate(db)) {
99
104
  return;
@@ -151,6 +156,11 @@ async function runMigrations(db) {
151
156
  commands.push("ALTER TABLE workflow_runs ALTER COLUMN resource_id TYPE varchar(256)");
152
157
  commands.push("ALTER TABLE workflow_runs ALTER COLUMN workflow_id TYPE varchar(256)");
153
158
  }
159
+ if (currentVersion < 4) {
160
+ commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS parent_run_id varchar(32)");
161
+ commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS parent_step_id varchar(256)");
162
+ commands.push("ALTER TABLE workflow_runs ADD COLUMN IF NOT EXISTS parent_resource_id varchar(256)");
163
+ }
154
164
  if (currentVersion === 0) {
155
165
  commands.push(`INSERT INTO workflow_schema_version (version) VALUES (${CURRENT_SCHEMA_VERSION})`);
156
166
  } else {
@@ -213,7 +223,10 @@ function mapRowToWorkflowRun(row) {
213
223
  retryCount: row.retry_count,
214
224
  maxRetries: row.max_retries,
215
225
  jobId: row.job_id,
216
- idempotencyKey: row.idempotency_key
226
+ idempotencyKey: row.idempotency_key,
227
+ parentRunId: row.parent_run_id,
228
+ parentStepId: row.parent_step_id,
229
+ parentResourceId: row.parent_resource_id
217
230
  };
218
231
  }
219
232
  async function insertWorkflowRun({
@@ -224,7 +237,10 @@ async function insertWorkflowRun({
224
237
  input,
225
238
  maxRetries,
226
239
  timeoutAt,
227
- idempotencyKey
240
+ idempotencyKey,
241
+ parentRunId,
242
+ parentStepId,
243
+ parentResourceId
228
244
  }, db) {
229
245
  const runId = generateKSUID("run");
230
246
  const now = new Date;
@@ -241,9 +257,12 @@ async function insertWorkflowRun({
241
257
  updated_at,
242
258
  timeline,
243
259
  retry_count,
244
- idempotency_key
260
+ idempotency_key,
261
+ parent_run_id,
262
+ parent_step_id,
263
+ parent_resource_id
245
264
  )
246
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
265
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
247
266
  ON CONFLICT (idempotency_key) WHERE idempotency_key IS NOT NULL DO NOTHING
248
267
  RETURNING *`, [
249
268
  runId,
@@ -258,7 +277,10 @@ async function insertWorkflowRun({
258
277
  now,
259
278
  "{}",
260
279
  0,
261
- idempotencyKey ?? null
280
+ idempotencyKey ?? null,
281
+ parentRunId ?? null,
282
+ parentStepId ?? null,
283
+ parentResourceId ?? null
262
284
  ]);
263
285
  if (result.rows[0]) {
264
286
  return { run: mapRowToWorkflowRun(result.rows[0]), created: true };
@@ -529,6 +551,7 @@ var StepType;
529
551
  StepType2["WAIT_UNTIL"] = "waitUntil";
530
552
  StepType2["DELAY"] = "delay";
531
553
  StepType2["POLL"] = "poll";
554
+ StepType2["INVOKE_CHILD_WORKFLOW"] = "invokeChildWorkflow";
532
555
  })(StepType ||= {});
533
556
 
534
557
  // src/client.ts
@@ -636,7 +659,8 @@ class WorkflowClient {
636
659
  };
637
660
  await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
638
661
  startAfter: new Date,
639
- expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds
662
+ expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds,
663
+ db: _db
640
664
  });
641
665
  }
642
666
  return insertedRun;
@@ -699,6 +723,11 @@ class WorkflowClient {
699
723
  if (current.status !== "paused" /* PAUSED */) {
700
724
  throw new WorkflowEngineError(`Cannot resume workflow run in '${current.status}' status, must be 'paused'`, current.workflowId, runId);
701
725
  }
726
+ const currentStepId = current.currentStepId;
727
+ const currentStepTimelineEntry = current.timeline[invokeChildWorkflowTimelineKey(currentStepId)];
728
+ if (isInvokeChildWorkflowTimelineEntry(currentStepTimelineEntry)) {
729
+ return current;
730
+ }
702
731
  return this.triggerEvent({
703
732
  runId,
704
733
  resourceId,
@@ -717,8 +746,12 @@ class WorkflowClient {
717
746
  if (run.status !== "paused" /* PAUSED */) {
718
747
  return run;
719
748
  }
720
- const stepId = run.currentStepId;
721
- const waitForEntry = run.timeline[`${stepId}-wait-for`];
749
+ const currentStepId = run.currentStepId;
750
+ const currentStepTimelineEntry = run.timeline[invokeChildWorkflowTimelineKey(currentStepId)];
751
+ if (isInvokeChildWorkflowTimelineEntry(currentStepTimelineEntry)) {
752
+ return run;
753
+ }
754
+ const waitForEntry = run.timeline[waitForTimelineKey(currentStepId)];
722
755
  if (!waitForEntry || typeof waitForEntry !== "object" || !("waitFor" in waitForEntry)) {
723
756
  return run;
724
757
  }
@@ -736,7 +769,7 @@ class WorkflowClient {
736
769
  resourceId,
737
770
  data: {
738
771
  timeline: import_es_toolkit.merge(freshRun.timeline, {
739
- [stepId]: {
772
+ [currentStepId]: {
740
773
  output: data ?? {},
741
774
  timestamp: new Date
742
775
  }
@@ -836,7 +869,10 @@ function createWorkflowRef(id, options) {
836
869
  retries: defineOptions?.retries
837
870
  });
838
871
  Object.defineProperty(ref, "id", { value: id, enumerable: true });
839
- Object.defineProperty(ref, "inputSchema", { value: options?.inputSchema, enumerable: true });
872
+ Object.defineProperty(ref, "inputSchema", {
873
+ value: options?.inputSchema,
874
+ enumerable: true
875
+ });
840
876
  return ref;
841
877
  }
842
878
  function createWorkflowFactory(plugins = []) {
@@ -931,7 +967,7 @@ function parseWorkflowHandler(handler) {
931
967
  const propertyAccess = node.expression;
932
968
  const objectName = propertyAccess.expression.getText(sourceFile);
933
969
  const methodName = propertyAccess.name.text;
934
- if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll")) {
970
+ if (objectName === "step" && (methodName === "run" || methodName === "waitFor" || methodName === "pause" || methodName === "waitUntil" || methodName === "delay" || methodName === "sleep" || methodName === "poll" || methodName === "invokeChildWorkflow")) {
935
971
  const firstArg = node.arguments[0];
936
972
  if (firstArg) {
937
973
  const { id, isDynamic } = extractStepId(firstArg);
@@ -964,7 +1000,8 @@ var StepTypeToIcon = {
964
1000
  ["pause" /* PAUSE */]: "⏸",
965
1001
  ["waitUntil" /* WAIT_UNTIL */]: "⏲",
966
1002
  ["delay" /* DELAY */]: "⏱",
967
- ["poll" /* POLL */]: "↻"
1003
+ ["poll" /* POLL */]: "↻",
1004
+ ["invokeChildWorkflow" /* INVOKE_CHILD_WORKFLOW */]: "↪"
968
1005
  };
969
1006
  var defaultLogger2 = {
970
1007
  log: (_message) => console.warn(_message),
@@ -976,6 +1013,7 @@ var retrySendOptions = (maxRetries) => ({
976
1013
  retryBackoff: true,
977
1014
  retryDelay: 1
978
1015
  });
1016
+ var getInvokeChildWorkflowEventName = (childRunId) => `__invoke_child_workflow_completed:${childRunId}`;
979
1017
  var defaultHeartbeatSeconds = process.env.WORKFLOW_RUN_HEARTBEAT_SECONDS ? Number.parseInt(process.env.WORKFLOW_RUN_HEARTBEAT_SECONDS, 10) : 30;
980
1018
 
981
1019
  class WorkflowEngine {
@@ -1081,31 +1119,57 @@ class WorkflowEngine {
1081
1119
  this.workflows.clear();
1082
1120
  return this;
1083
1121
  }
1084
- async startWorkflow(refOrParams, inputArg, optionsArg) {
1085
- let workflowId;
1086
- let input;
1087
- let resourceId;
1088
- let idempotencyKey;
1089
- let options;
1122
+ resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg) {
1090
1123
  if (typeof refOrParams === "function" && "id" in refOrParams) {
1091
- workflowId = refOrParams.id;
1092
- input = inputArg;
1093
- options = optionsArg;
1094
- resourceId = optionsArg?.resourceId;
1095
- idempotencyKey = optionsArg?.idempotencyKey;
1096
- } else {
1097
- const params = refOrParams;
1098
- workflowId = params.workflowId;
1099
- input = params.input;
1100
- resourceId = params.resourceId;
1101
- idempotencyKey = params.idempotencyKey;
1102
- options = params.options;
1124
+ return {
1125
+ workflowId: refOrParams.id,
1126
+ input: inputArg,
1127
+ options: optionsArg,
1128
+ resourceId: optionsArg?.resourceId,
1129
+ idempotencyKey: optionsArg?.idempotencyKey
1130
+ };
1103
1131
  }
1104
- validateWorkflowId(workflowId);
1105
- validateResourceId(resourceId);
1132
+ const params = refOrParams;
1133
+ return {
1134
+ workflowId: params.workflowId,
1135
+ input: params.input,
1136
+ resourceId: params.resourceId ?? params.options?.resourceId,
1137
+ idempotencyKey: params.idempotencyKey ?? params.options?.idempotencyKey,
1138
+ options: params.options
1139
+ };
1140
+ }
1141
+ async startWorkflow(refOrParams, inputArg, optionsArg) {
1142
+ const { workflowId, input, resourceId, idempotencyKey, options } = this.resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg);
1106
1143
  if (!this._started) {
1107
1144
  await this.start(false, { batchSize: options?.batchSize ?? 1 });
1108
1145
  }
1146
+ const { run } = await this.createWorkflowRun({
1147
+ workflowId,
1148
+ input,
1149
+ resourceId,
1150
+ idempotencyKey,
1151
+ options
1152
+ });
1153
+ this.logger.log("Started workflow run", {
1154
+ runId: run.id,
1155
+ workflowId
1156
+ });
1157
+ return run;
1158
+ }
1159
+ async createWorkflowRun({
1160
+ workflowId,
1161
+ input,
1162
+ resourceId,
1163
+ idempotencyKey,
1164
+ options,
1165
+ parentRunId,
1166
+ parentStepId,
1167
+ parentResourceId,
1168
+ enqueue = true,
1169
+ db
1170
+ }) {
1171
+ validateWorkflowId(workflowId);
1172
+ validateResourceId(resourceId);
1109
1173
  const workflow2 = this.workflows.get(workflowId);
1110
1174
  if (!workflow2) {
1111
1175
  throw new WorkflowEngineError(`Unknown workflow ${workflowId}`);
@@ -1122,38 +1186,60 @@ class WorkflowEngine {
1122
1186
  }
1123
1187
  }
1124
1188
  const initialStepId = workflow2.steps[0]?.id ?? "__start__";
1125
- const run = await withPostgresTransaction(this.boss.getDb(), async (_db) => {
1126
- const timeoutAt = options?.timeout ? new Date(Date.now() + options.timeout) : workflow2.timeout ? new Date(Date.now() + workflow2.timeout) : null;
1127
- const { run: insertedRun, created } = await insertWorkflowRun({
1128
- resourceId,
1129
- workflowId,
1130
- currentStepId: initialStepId,
1131
- status: "running" /* RUNNING */,
1132
- input,
1133
- maxRetries: options?.retries ?? workflow2.retries ?? 0,
1134
- timeoutAt,
1135
- idempotencyKey
1136
- }, _db);
1137
- if (created) {
1138
- const job = {
1139
- runId: insertedRun.id,
1140
- resourceId,
1141
- workflowId,
1142
- input
1143
- };
1144
- await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
1145
- startAfter: new Date,
1146
- expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds2,
1147
- ...retrySendOptions(insertedRun.maxRetries)
1148
- });
1189
+ const timeoutAt = options?.timeout ? new Date(Date.now() + options.timeout) : workflow2.timeout ? new Date(Date.now() + workflow2.timeout) : null;
1190
+ const insertRun = async (targetDb) => await insertWorkflowRun({
1191
+ resourceId,
1192
+ workflowId,
1193
+ currentStepId: initialStepId,
1194
+ status: "running" /* RUNNING */,
1195
+ input,
1196
+ maxRetries: options?.retries ?? workflow2.retries ?? 0,
1197
+ timeoutAt,
1198
+ idempotencyKey,
1199
+ parentRunId,
1200
+ parentStepId,
1201
+ parentResourceId
1202
+ }, targetDb);
1203
+ const insertAndEnqueue = async (targetDb) => {
1204
+ const result = await insertRun(targetDb);
1205
+ if (enqueue && result.created) {
1206
+ await this.enqueueWorkflowRun(result.run, options, targetDb);
1149
1207
  }
1150
- return insertedRun;
1151
- }, this.pool);
1152
- this.logger.log("Started workflow run", {
1208
+ return result;
1209
+ };
1210
+ const { run, created } = db ? await insertAndEnqueue(db) : await withPostgresTransaction(this.boss.getDb(), insertAndEnqueue, this.pool);
1211
+ return { run, created };
1212
+ }
1213
+ async enqueueWorkflowRun(run, options, db) {
1214
+ const job = {
1153
1215
  runId: run.id,
1154
- workflowId
1216
+ resourceId: run.resourceId ?? undefined,
1217
+ workflowId: run.workflowId,
1218
+ input: run.input
1219
+ };
1220
+ await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
1221
+ startAfter: new Date,
1222
+ expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds2,
1223
+ ...retrySendOptions(run.maxRetries),
1224
+ ...db ? { db } : {}
1225
+ });
1226
+ }
1227
+ async notifyParentOfChildTerminalRun(childRun) {
1228
+ if (!childRun.parentRunId || !childRun.parentStepId) {
1229
+ return;
1230
+ }
1231
+ const parentRun = await getWorkflowRun({
1232
+ runId: childRun.parentRunId,
1233
+ resourceId: childRun.parentResourceId ?? undefined
1234
+ }, { db: this.db });
1235
+ if (!parentRun || parentRun.status === "completed" /* COMPLETED */ || parentRun.status === "failed" /* FAILED */ || parentRun.status === "cancelled" /* CANCELLED */) {
1236
+ return;
1237
+ }
1238
+ await this.triggerEvent({
1239
+ runId: parentRun.id,
1240
+ resourceId: parentRun.resourceId ?? undefined,
1241
+ eventName: getInvokeChildWorkflowEventName(childRun.id)
1155
1242
  });
1156
- return run;
1157
1243
  }
1158
1244
  async pauseWorkflow({
1159
1245
  runId,
@@ -1185,6 +1271,9 @@ class WorkflowEngine {
1185
1271
  if (current.status !== "paused" /* PAUSED */) {
1186
1272
  throw new WorkflowEngineError(`Cannot resume workflow run in '${current.status}' status, must be 'paused'`, current.workflowId, runId);
1187
1273
  }
1274
+ if (this.getInvokeChildWorkflowStepEntry(current.timeline, current.currentStepId)) {
1275
+ return current;
1276
+ }
1188
1277
  return this.triggerEvent({
1189
1278
  runId,
1190
1279
  resourceId,
@@ -1204,6 +1293,9 @@ class WorkflowEngine {
1204
1293
  return run;
1205
1294
  }
1206
1295
  const stepId = run.currentStepId;
1296
+ if (this.getInvokeChildWorkflowStepEntry(run.timeline, stepId)) {
1297
+ return run;
1298
+ }
1207
1299
  const waitForStep = this.getWaitForStepEntry(run.timeline, stepId);
1208
1300
  if (!waitForStep) {
1209
1301
  return run;
@@ -1252,6 +1344,7 @@ class WorkflowEngine {
1252
1344
  expectedStatuses: ["pending" /* PENDING */, "running" /* RUNNING */, "paused" /* PAUSED */]
1253
1345
  });
1254
1346
  this.logger.log(`cancelled workflow run with id ${runId}`);
1347
+ await this.notifyParentOfChildTerminalRun(run);
1255
1348
  return run;
1256
1349
  }
1257
1350
  async triggerEvent({
@@ -1489,6 +1582,22 @@ class WorkflowEngine {
1489
1582
  }
1490
1583
  const timeoutMs = options?.timeout ? parseDuration(options.timeout) : undefined;
1491
1584
  return this.pollStep({ run, stepId, conditionFn, intervalMs, timeoutMs });
1585
+ },
1586
+ invokeChildWorkflow: async (stepId, refOrParams, inputArg, optionsArg) => {
1587
+ if (!run) {
1588
+ throw new WorkflowEngineError("Missing workflow run", workflowId, runId);
1589
+ }
1590
+ const resolvedChildCall = this.resolveWorkflowRunParameters(refOrParams, inputArg, optionsArg);
1591
+ const childWorkflowInvocation = {
1592
+ run,
1593
+ stepId,
1594
+ workflowId: resolvedChildCall.workflowId,
1595
+ input: resolvedChildCall.input,
1596
+ options: resolvedChildCall.options,
1597
+ resourceId: resolvedChildCall.resourceId,
1598
+ idempotencyKey: resolvedChildCall.idempotencyKey
1599
+ };
1600
+ return this.invokeChildWorkflowStep(childWorkflowInvocation);
1492
1601
  }
1493
1602
  };
1494
1603
  let step = { ...baseStep };
@@ -1513,7 +1622,7 @@ class WorkflowEngine {
1513
1622
  const shouldComplete = run.status === "running" /* RUNNING */ && (noParsedSteps || isLastParsedStep || hasPluginSteps && result !== undefined);
1514
1623
  if (shouldComplete) {
1515
1624
  const normalizedResult = result === undefined ? {} : result;
1516
- await this.updateRun({
1625
+ const completedRun = await this.updateRun({
1517
1626
  runId,
1518
1627
  resourceId: scopedResourceId,
1519
1628
  data: {
@@ -1523,6 +1632,7 @@ class WorkflowEngine {
1523
1632
  jobId: job?.id
1524
1633
  }
1525
1634
  });
1635
+ await this.notifyParentOfChildTerminalRun(completedRun);
1526
1636
  this.logger.log("Workflow run completed.", {
1527
1637
  runId,
1528
1638
  workflowId
@@ -1530,7 +1640,7 @@ class WorkflowEngine {
1530
1640
  }
1531
1641
  } catch (error) {
1532
1642
  if (runId) {
1533
- await this.updateRun({
1643
+ const updatedRun = await this.updateRun({
1534
1644
  runId,
1535
1645
  resourceId: scopedResourceId,
1536
1646
  data: {
@@ -1538,6 +1648,9 @@ class WorkflowEngine {
1538
1648
  jobId: job?.id
1539
1649
  }
1540
1650
  });
1651
+ if (updatedRun.status === "completed" /* COMPLETED */ || updatedRun.status === "failed" /* FAILED */ || updatedRun.status === "cancelled" /* CANCELLED */) {
1652
+ await this.notifyParentOfChildTerminalRun(updatedRun);
1653
+ }
1541
1654
  }
1542
1655
  throw error;
1543
1656
  }
@@ -1549,7 +1662,7 @@ class WorkflowEngine {
1549
1662
  const run = await getWorkflowRun({ runId }, { db: this.db });
1550
1663
  if (!run || run.status !== "running" /* RUNNING */)
1551
1664
  return;
1552
- await this.updateRun({
1665
+ const failedRun = await this.updateRun({
1553
1666
  runId,
1554
1667
  resourceId: run.resourceId ?? undefined,
1555
1668
  data: {
@@ -1557,6 +1670,7 @@ class WorkflowEngine {
1557
1670
  error: run.error ?? "Workflow run worker died or job expired before completion"
1558
1671
  }
1559
1672
  });
1673
+ await this.notifyParentOfChildTerminalRun(failedRun);
1560
1674
  this.logger.log("Marked stuck workflow run as failed", {
1561
1675
  runId,
1562
1676
  workflowId: run.workflowId
@@ -1567,9 +1681,195 @@ class WorkflowEngine {
1567
1681
  return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
1568
1682
  }
1569
1683
  getWaitForStepEntry(timeline, stepId) {
1570
- const entry = timeline[`${stepId}-wait-for`];
1684
+ const entry = timeline[waitForTimelineKey(stepId)];
1571
1685
  return entry && typeof entry === "object" && "waitFor" in entry ? entry : null;
1572
1686
  }
1687
+ getInvokeChildWorkflowStepEntry(timeline, stepId) {
1688
+ const entry = timeline[invokeChildWorkflowTimelineKey(stepId)];
1689
+ return isInvokeChildWorkflowTimelineEntry(entry) ? entry : null;
1690
+ }
1691
+ getCompletedChildOutput(childRun) {
1692
+ return childRun.output === undefined ? {} : childRun.output;
1693
+ }
1694
+ throwForNonCompletedChild(childRun) {
1695
+ throw new WorkflowEngineError(`Child workflow ${childRun.workflowId} ${childRun.status}${childRun.error ? `: ${childRun.error}` : ""}`, childRun.workflowId, childRun.id);
1696
+ }
1697
+ assertInvokeChildWorkflowStepOwnership({
1698
+ childRun,
1699
+ parentRun,
1700
+ stepId,
1701
+ workflowId
1702
+ }) {
1703
+ const expectedParentResourceId = parentRun.resourceId ?? null;
1704
+ const matches = childRun.workflowId === workflowId && childRun.parentRunId === parentRun.id && childRun.parentStepId === stepId && childRun.parentResourceId === expectedParentResourceId;
1705
+ if (!matches) {
1706
+ throw new WorkflowEngineError(`Idempotency key resolved to workflow run ${childRun.id}, which does not belong to invokeChildWorkflow step '${stepId}'`, workflowId, parentRun.id);
1707
+ }
1708
+ }
1709
+ async invokeChildWorkflowStep({
1710
+ run,
1711
+ stepId,
1712
+ workflowId,
1713
+ input,
1714
+ resourceId,
1715
+ idempotencyKey,
1716
+ options
1717
+ }) {
1718
+ let invokeOutput;
1719
+ let hasInvokeOutput = false;
1720
+ const childResourceId = resourceId ?? run.resourceId ?? undefined;
1721
+ const childIdempotencyKey = idempotencyKey;
1722
+ await withPostgresTransaction(this.db, async (db) => {
1723
+ const lockedRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
1724
+ if (lockedRun.status === "cancelled" /* CANCELLED */ || lockedRun.status === "paused" /* PAUSED */ || lockedRun.status === "failed" /* FAILED */) {
1725
+ return;
1726
+ }
1727
+ const lockedCached = this.getCachedStepEntry(lockedRun.timeline, stepId);
1728
+ if (lockedCached?.output !== undefined) {
1729
+ invokeOutput = lockedCached.output;
1730
+ hasInvokeOutput = true;
1731
+ return;
1732
+ }
1733
+ const lockedInvoke = this.getInvokeChildWorkflowStepEntry(lockedRun.timeline, stepId);
1734
+ if (lockedInvoke) {
1735
+ const existingChildResourceId = "childResourceId" in lockedInvoke.invokeChildWorkflow ? lockedInvoke.invokeChildWorkflow.childResourceId ?? undefined : childResourceId;
1736
+ const existingChildRun = await this.getRun({
1737
+ runId: lockedInvoke.invokeChildWorkflow.childRunId,
1738
+ resourceId: existingChildResourceId
1739
+ });
1740
+ if (existingChildRun.status === "completed" /* COMPLETED */) {
1741
+ invokeOutput = this.getCompletedChildOutput(existingChildRun);
1742
+ hasInvokeOutput = true;
1743
+ await this.updateRun({
1744
+ runId: run.id,
1745
+ resourceId: run.resourceId ?? undefined,
1746
+ data: {
1747
+ timeline: import_es_toolkit2.merge(lockedRun.timeline, {
1748
+ [stepId]: {
1749
+ output: invokeOutput,
1750
+ timestamp: new Date
1751
+ }
1752
+ })
1753
+ }
1754
+ }, { db });
1755
+ return;
1756
+ }
1757
+ if (existingChildRun.status === "failed" /* FAILED */ || existingChildRun.status === "cancelled" /* CANCELLED */) {
1758
+ this.throwForNonCompletedChild(existingChildRun);
1759
+ }
1760
+ await this.pauseRunForWait({
1761
+ run: lockedRun,
1762
+ stepId,
1763
+ eventName: getInvokeChildWorkflowEventName(existingChildRun.id),
1764
+ skipOutput: true,
1765
+ db
1766
+ });
1767
+ return;
1768
+ }
1769
+ const result = await this.createWorkflowRun({
1770
+ workflowId,
1771
+ input,
1772
+ resourceId: childResourceId,
1773
+ idempotencyKey: childIdempotencyKey,
1774
+ options,
1775
+ parentRunId: run.id,
1776
+ parentStepId: stepId,
1777
+ parentResourceId: run.resourceId ?? undefined,
1778
+ enqueue: true,
1779
+ db
1780
+ });
1781
+ const childRun = result.run;
1782
+ if (!result.created) {
1783
+ this.assertInvokeChildWorkflowStepOwnership({
1784
+ childRun,
1785
+ parentRun: lockedRun,
1786
+ stepId,
1787
+ workflowId
1788
+ });
1789
+ if (childRun.status === "completed" /* COMPLETED */) {
1790
+ invokeOutput = this.getCompletedChildOutput(childRun);
1791
+ hasInvokeOutput = true;
1792
+ await this.updateRun({
1793
+ runId: run.id,
1794
+ resourceId: run.resourceId ?? undefined,
1795
+ data: {
1796
+ timeline: import_es_toolkit2.merge(lockedRun.timeline, {
1797
+ [invokeChildWorkflowTimelineKey(stepId)]: {
1798
+ invokeChildWorkflow: {
1799
+ childRunId: childRun.id,
1800
+ childWorkflowId: childRun.workflowId,
1801
+ childResourceId: childRun.resourceId
1802
+ },
1803
+ timestamp: new Date
1804
+ },
1805
+ [stepId]: {
1806
+ output: invokeOutput,
1807
+ timestamp: new Date
1808
+ }
1809
+ })
1810
+ }
1811
+ }, { db });
1812
+ return;
1813
+ }
1814
+ if (childRun.status === "failed" /* FAILED */ || childRun.status === "cancelled" /* CANCELLED */) {
1815
+ this.throwForNonCompletedChild(childRun);
1816
+ }
1817
+ }
1818
+ await this.pauseRunForWait({
1819
+ run: lockedRun,
1820
+ stepId,
1821
+ eventName: getInvokeChildWorkflowEventName(childRun.id),
1822
+ skipOutput: true,
1823
+ db,
1824
+ timeline: import_es_toolkit2.merge(lockedRun.timeline, {
1825
+ [invokeChildWorkflowTimelineKey(stepId)]: {
1826
+ invokeChildWorkflow: {
1827
+ childRunId: childRun.id,
1828
+ childWorkflowId: childRun.workflowId,
1829
+ childResourceId: childRun.resourceId
1830
+ },
1831
+ timestamp: new Date
1832
+ }
1833
+ })
1834
+ });
1835
+ }, this.pool);
1836
+ if (hasInvokeOutput) {
1837
+ return invokeOutput;
1838
+ }
1839
+ }
1840
+ async pauseRunForWait({
1841
+ run,
1842
+ stepId,
1843
+ eventName,
1844
+ timeoutEvent,
1845
+ skipOutput,
1846
+ db,
1847
+ timeline
1848
+ }) {
1849
+ const baseTimeline = timeline ?? run.timeline;
1850
+ const waitFor = {};
1851
+ if (eventName)
1852
+ waitFor.eventName = eventName;
1853
+ if (timeoutEvent)
1854
+ waitFor.timeoutEvent = timeoutEvent;
1855
+ if (skipOutput)
1856
+ waitFor.skipOutput = true;
1857
+ await this.updateRun({
1858
+ runId: run.id,
1859
+ resourceId: run.resourceId ?? undefined,
1860
+ data: {
1861
+ status: "paused" /* PAUSED */,
1862
+ currentStepId: stepId,
1863
+ pausedAt: new Date,
1864
+ timeline: import_es_toolkit2.merge(baseTimeline, {
1865
+ [waitForTimelineKey(stepId)]: {
1866
+ waitFor,
1867
+ timestamp: new Date
1868
+ }
1869
+ })
1870
+ }
1871
+ }, { db });
1872
+ }
1573
1873
  async runStep({
1574
1874
  stepId,
1575
1875
  run,
@@ -1658,21 +1958,7 @@ ${error.stack}` : String(error)
1658
1958
  const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
1659
1959
  await withPostgresTransaction(this.db, async (db) => {
1660
1960
  const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
1661
- return this.updateRun({
1662
- runId: run.id,
1663
- resourceId: run.resourceId ?? undefined,
1664
- data: {
1665
- status: "paused" /* PAUSED */,
1666
- currentStepId: stepId,
1667
- pausedAt: new Date,
1668
- timeline: import_es_toolkit2.merge(freshRun.timeline, {
1669
- [`${stepId}-wait-for`]: {
1670
- waitFor: { eventName, timeoutEvent },
1671
- timestamp: new Date
1672
- }
1673
- })
1674
- }
1675
- }, { db });
1961
+ return this.pauseRunForWait({ run: freshRun, stepId, eventName, timeoutEvent, db });
1676
1962
  }, this.pool);
1677
1963
  if (timeoutDate && timeoutEvent) {
1678
1964
  try {
@@ -1786,7 +2072,7 @@ ${error.stack}` : String(error)
1786
2072
  pausedAt: new Date,
1787
2073
  timeline: import_es_toolkit2.merge(freshRun.timeline, {
1788
2074
  [`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
1789
- [`${stepId}-wait-for`]: {
2075
+ [waitForTimelineKey(stepId)]: {
1790
2076
  waitFor: { timeoutEvent: pollEvent, skipOutput: true },
1791
2077
  timestamp: new Date
1792
2078
  }
@@ -1861,5 +2147,5 @@ ${error.stack}` : String(error)
1861
2147
  }
1862
2148
  }
1863
2149
 
1864
- //# debugId=CBDC1CAB86CECE9C64756E2164756E21
2150
+ //# debugId=8E8C82F1948B934364756E2164756E21
1865
2151
  //# sourceMappingURL=index.js.map