pg-workflows 0.6.0 → 0.6.1

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
@@ -271,15 +271,23 @@ async function runMigrations(db) {
271
271
  job_id varchar(256)
272
272
  );
273
273
  `, []);
274
- await db.executeSql(`
275
- CREATE INDEX workflow_runs_workflow_id_idx ON workflow_runs USING btree (workflow_id);
276
- `, []);
277
274
  await db.executeSql(`
278
275
  CREATE INDEX workflow_runs_created_at_idx ON workflow_runs USING btree (created_at);
276
+ CREATE INDEX workflow_runs_resource_id_created_at_idx ON workflow_runs USING btree (resource_id, created_at DESC);
277
+ CREATE INDEX workflow_runs_status_created_at_idx ON workflow_runs USING btree (status, created_at DESC);
278
+ CREATE INDEX workflow_runs_workflow_id_created_at_idx ON workflow_runs USING btree (workflow_id, created_at DESC);
279
+ CREATE INDEX workflow_runs_resource_id_workflow_id_created_at_idx ON workflow_runs USING btree (resource_id, workflow_id, created_at DESC);
279
280
  `, []);
281
+ } else {
280
282
  await db.executeSql(`
281
- CREATE INDEX workflow_runs_resource_id_idx ON workflow_runs USING btree (resource_id);
282
- `, []);
283
+ DROP INDEX IF EXISTS workflow_runs_workflow_id_idx;
284
+ DROP INDEX IF EXISTS workflow_runs_resource_id_idx;
285
+ CREATE INDEX IF NOT EXISTS workflow_runs_created_at_idx ON workflow_runs USING btree (created_at);
286
+ CREATE INDEX IF NOT EXISTS workflow_runs_resource_id_created_at_idx ON workflow_runs USING btree (resource_id, created_at DESC);
287
+ CREATE INDEX IF NOT EXISTS workflow_runs_status_created_at_idx ON workflow_runs USING btree (status, created_at DESC);
288
+ CREATE INDEX IF NOT EXISTS workflow_runs_workflow_id_created_at_idx ON workflow_runs USING btree (workflow_id, created_at DESC);
289
+ CREATE INDEX IF NOT EXISTS workflow_runs_resource_id_workflow_id_created_at_idx ON workflow_runs USING btree (resource_id, workflow_id, created_at DESC);
290
+ `, []);
283
291
  }
284
292
  }
285
293
 
@@ -317,8 +325,7 @@ async function insertWorkflowRun({
317
325
  status,
318
326
  input,
319
327
  maxRetries,
320
- timeoutAt,
321
- timeline
328
+ timeoutAt
322
329
  }, db) {
323
330
  const runId = generateKSUID("run");
324
331
  const now = new Date;
@@ -348,7 +355,7 @@ async function insertWorkflowRun({
348
355
  timeoutAt,
349
356
  now,
350
357
  now,
351
- JSON.stringify(timeline ?? {}),
358
+ "{}",
352
359
  0
353
360
  ]);
354
361
  const insertedRun = result.rows[0];
@@ -376,7 +383,8 @@ async function getWorkflowRun({
376
383
  async function updateWorkflowRun({
377
384
  runId,
378
385
  resourceId,
379
- data
386
+ data,
387
+ expectedStatuses
380
388
  }, db) {
381
389
  const now = new Date;
382
390
  const updates = ["updated_at = $1"];
@@ -432,10 +440,20 @@ async function updateWorkflowRun({
432
440
  values.push(data.jobId);
433
441
  paramIndex++;
434
442
  }
435
- const whereClause = resourceId ? `WHERE id = $${paramIndex} AND resource_id = $${paramIndex + 1}` : `WHERE id = $${paramIndex}`;
436
443
  values.push(runId);
444
+ const idParam = paramIndex;
445
+ paramIndex++;
437
446
  if (resourceId) {
438
447
  values.push(resourceId);
448
+ paramIndex++;
449
+ }
450
+ if (expectedStatuses && expectedStatuses.length > 0) {
451
+ values.push(expectedStatuses);
452
+ paramIndex++;
453
+ }
454
+ let whereClause = resourceId ? `WHERE id = $${idParam} AND resource_id = $${idParam + 1}` : `WHERE id = $${idParam}`;
455
+ if (expectedStatuses && expectedStatuses.length > 0) {
456
+ whereClause += ` AND status = ANY($${paramIndex - 1})`;
439
457
  }
440
458
  const query = `
441
459
  UPDATE workflow_runs
@@ -476,20 +494,28 @@ async function getWorkflowRuns({
476
494
  values.push(workflowId);
477
495
  paramIndex++;
478
496
  }
479
- if (startingAfter) {
480
- const cursorResult = await db.executeSql("SELECT created_at FROM workflow_runs WHERE id = $1 LIMIT 1", [startingAfter]);
481
- if (cursorResult.rows[0]?.created_at) {
482
- conditions.push(`created_at < $${paramIndex}`);
483
- values.push(typeof cursorResult.rows[0].created_at === "string" ? new Date(cursorResult.rows[0].created_at) : cursorResult.rows[0].created_at);
484
- paramIndex++;
497
+ const cursorIds = [startingAfter, endingBefore].filter(Boolean);
498
+ if (cursorIds.length > 0) {
499
+ const cursorResult = await db.executeSql("SELECT id, created_at FROM workflow_runs WHERE id = ANY($1)", [cursorIds]);
500
+ const cursorMap = new Map;
501
+ for (const row of cursorResult.rows) {
502
+ cursorMap.set(row.id, typeof row.created_at === "string" ? new Date(row.created_at) : row.created_at);
485
503
  }
486
- }
487
- if (endingBefore) {
488
- const cursorResult = await db.executeSql("SELECT created_at FROM workflow_runs WHERE id = $1 LIMIT 1", [endingBefore]);
489
- if (cursorResult.rows[0]?.created_at) {
490
- conditions.push(`created_at > $${paramIndex}`);
491
- values.push(typeof cursorResult.rows[0].created_at === "string" ? new Date(cursorResult.rows[0].created_at) : cursorResult.rows[0].created_at);
492
- paramIndex++;
504
+ if (startingAfter) {
505
+ const cursor = cursorMap.get(startingAfter);
506
+ if (cursor) {
507
+ conditions.push(`created_at < $${paramIndex}`);
508
+ values.push(cursor);
509
+ paramIndex++;
510
+ }
511
+ }
512
+ if (endingBefore) {
513
+ const cursor = cursorMap.get(endingBefore);
514
+ if (cursor) {
515
+ conditions.push(`created_at > $${paramIndex}`);
516
+ values.push(cursor);
517
+ paramIndex++;
518
+ }
493
519
  }
494
520
  }
495
521
  const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
@@ -686,8 +712,7 @@ class WorkflowEngine {
686
712
  status: "running" /* RUNNING */,
687
713
  input,
688
714
  maxRetries: options?.retries ?? workflow2.retries ?? 0,
689
- timeoutAt,
690
- timeline: undefined
715
+ timeoutAt
691
716
  }, _db);
692
717
  const job = {
693
718
  runId: insertedRun.id,
@@ -718,7 +743,8 @@ class WorkflowEngine {
718
743
  data: {
719
744
  status: "paused" /* PAUSED */,
720
745
  pausedAt: new Date
721
- }
746
+ },
747
+ expectedStatuses: ["running" /* RUNNING */, "pending" /* PENDING */]
722
748
  });
723
749
  this.logger.log("Paused workflow run", {
724
750
  runId,
@@ -732,6 +758,10 @@ class WorkflowEngine {
732
758
  options
733
759
  }) {
734
760
  await this.checkIfHasStarted();
761
+ const current = await this.getRun({ runId, resourceId });
762
+ if (current.status !== "paused" /* PAUSED */) {
763
+ throw new WorkflowEngineError(`Cannot resume workflow run in '${current.status}' status, must be 'paused'`, current.workflowId, runId);
764
+ }
735
765
  return this.triggerEvent({
736
766
  runId,
737
767
  resourceId,
@@ -751,8 +781,7 @@ class WorkflowEngine {
751
781
  return run;
752
782
  }
753
783
  const stepId = run.currentStepId;
754
- const waitForStepEntry = run.timeline[`${stepId}-wait-for`];
755
- const waitForStep = waitForStepEntry && typeof waitForStepEntry === "object" && "waitFor" in waitForStepEntry ? waitForStepEntry : null;
784
+ const waitForStep = this.getWaitForStepEntry(run.timeline, stepId);
756
785
  if (!waitForStep) {
757
786
  return run;
758
787
  }
@@ -761,18 +790,21 @@ class WorkflowEngine {
761
790
  return this.resumeWorkflow({ runId, resourceId });
762
791
  }
763
792
  if (skipOutput && timeoutEvent) {
764
- await this.updateRun({
765
- runId,
766
- resourceId,
767
- data: {
768
- timeline: import_es_toolkit.merge(run.timeline, {
769
- [stepId]: {
770
- output: data ?? {},
771
- timestamp: new Date
772
- }
773
- })
774
- }
775
- });
793
+ await withPostgresTransaction(this.db, async (db) => {
794
+ const freshRun = await this.getRun({ runId, resourceId }, { exclusiveLock: true, db });
795
+ return this.updateRun({
796
+ runId,
797
+ resourceId,
798
+ data: {
799
+ timeline: import_es_toolkit.merge(freshRun.timeline, {
800
+ [stepId]: {
801
+ output: data ?? {},
802
+ timestamp: new Date
803
+ }
804
+ })
805
+ }
806
+ }, { db });
807
+ }, this.pool);
776
808
  return this.triggerEvent({ runId, resourceId, eventName: timeoutEvent });
777
809
  }
778
810
  if (eventName) {
@@ -793,7 +825,8 @@ class WorkflowEngine {
793
825
  resourceId,
794
826
  data: {
795
827
  status: "cancelled" /* CANCELLED */
796
- }
828
+ },
829
+ expectedStatuses: ["pending" /* PENDING */, "running" /* RUNNING */, "paused" /* PAUSED */]
797
830
  });
798
831
  this.logger.log(`cancelled workflow run with id ${runId}`);
799
832
  return run;
@@ -818,7 +851,7 @@ class WorkflowEngine {
818
851
  data
819
852
  }
820
853
  };
821
- this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
854
+ await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
822
855
  expireInSeconds: options?.expireInSeconds ?? defaultExpireInSeconds
823
856
  });
824
857
  this.logger.log(`event ${eventName} sent for workflow run with id ${runId}`);
@@ -834,10 +867,17 @@ class WorkflowEngine {
834
867
  async updateRun({
835
868
  runId,
836
869
  resourceId,
837
- data
870
+ data,
871
+ expectedStatuses
838
872
  }, { db } = {}) {
839
- const run = await updateWorkflowRun({ runId, resourceId, data }, db ?? this.db);
873
+ const run = await updateWorkflowRun({ runId, resourceId, data, expectedStatuses }, db ?? this.db);
840
874
  if (!run) {
875
+ if (expectedStatuses) {
876
+ const current = await getWorkflowRun({ runId, resourceId }, { db: db ?? this.db });
877
+ if (current) {
878
+ throw new WorkflowEngineError(`Cannot update workflow run in '${current.status}' status, expected: ${expectedStatuses.join(", ")}`, current.workflowId, runId);
879
+ }
880
+ }
841
881
  throw new WorkflowRunNotFoundError(runId);
842
882
  }
843
883
  return run;
@@ -920,36 +960,40 @@ class WorkflowEngine {
920
960
  throw new WorkflowEngineError("Missing current step id", workflowId, runId);
921
961
  }
922
962
  if (run.status === "paused" /* PAUSED */) {
923
- const waitForStepEntry = run.timeline[`${run.currentStepId}-wait-for`];
924
- const waitForStep = waitForStepEntry && typeof waitForStepEntry === "object" && "waitFor" in waitForStepEntry ? waitForStepEntry : null;
925
- const currentStep = this.getCachedStepEntry(run.timeline, run.currentStepId);
926
- const waitFor = waitForStep?.waitFor;
927
- const hasCurrentStepOutput = currentStep?.output !== undefined;
928
- const eventMatches = waitFor && event?.name && (event.name === waitFor.eventName || event.name === waitFor.timeoutEvent) && !hasCurrentStepOutput;
929
- if (eventMatches) {
930
- const isTimeout = event?.name === waitFor?.timeoutEvent;
931
- const skipOutput = waitFor?.skipOutput;
932
- run = await this.updateRun({
933
- runId,
934
- resourceId: scopedResourceId,
935
- data: {
936
- status: "running" /* RUNNING */,
937
- pausedAt: null,
938
- resumedAt: new Date,
939
- jobId: job?.id,
940
- ...skipOutput ? {} : {
941
- timeline: import_es_toolkit.merge(run.timeline, {
942
- [run.currentStepId]: {
943
- output: event?.data ?? {},
944
- ...isTimeout ? { timedOut: true } : {},
945
- timestamp: new Date
946
- }
947
- })
963
+ run = await withPostgresTransaction(this.db, async (db) => {
964
+ const lockedRun = await this.getRun({ runId, resourceId: scopedResourceId }, { exclusiveLock: true, db });
965
+ if (lockedRun.status !== "paused" /* PAUSED */) {
966
+ return lockedRun;
967
+ }
968
+ const waitForStep = this.getWaitForStepEntry(lockedRun.timeline, lockedRun.currentStepId);
969
+ const currentStep = this.getCachedStepEntry(lockedRun.timeline, lockedRun.currentStepId);
970
+ const waitFor = waitForStep?.waitFor;
971
+ const hasCurrentStepOutput = currentStep?.output !== undefined;
972
+ const eventMatches = waitFor && event?.name && (event.name === waitFor.eventName || event.name === waitFor.timeoutEvent) && !hasCurrentStepOutput;
973
+ if (eventMatches) {
974
+ const isTimeout = event?.name === waitFor?.timeoutEvent;
975
+ const skipOutput = waitFor?.skipOutput;
976
+ return this.updateRun({
977
+ runId,
978
+ resourceId: scopedResourceId,
979
+ data: {
980
+ status: "running" /* RUNNING */,
981
+ pausedAt: null,
982
+ resumedAt: new Date,
983
+ jobId: job?.id,
984
+ ...skipOutput ? {} : {
985
+ timeline: import_es_toolkit.merge(lockedRun.timeline, {
986
+ [lockedRun.currentStepId]: {
987
+ output: event?.data ?? {},
988
+ ...isTimeout ? { timedOut: true } : {},
989
+ timestamp: new Date
990
+ }
991
+ })
992
+ }
948
993
  }
949
- }
950
- });
951
- } else {
952
- run = await this.updateRun({
994
+ }, { db });
995
+ }
996
+ return this.updateRun({
953
997
  runId,
954
998
  resourceId: scopedResourceId,
955
999
  data: {
@@ -958,8 +1002,8 @@ class WorkflowEngine {
958
1002
  resumedAt: new Date,
959
1003
  jobId: job?.id
960
1004
  }
961
- });
962
- }
1005
+ }, { db });
1006
+ }, this.pool);
963
1007
  }
964
1008
  const baseStep = {
965
1009
  run: async (stepId, handler) => {
@@ -1089,6 +1133,10 @@ class WorkflowEngine {
1089
1133
  const stepEntry = timeline[stepId];
1090
1134
  return stepEntry && typeof stepEntry === "object" && "output" in stepEntry ? stepEntry : null;
1091
1135
  }
1136
+ getWaitForStepEntry(timeline, stepId) {
1137
+ const entry = timeline[`${stepId}-wait-for`];
1138
+ return entry && typeof entry === "object" && "waitFor" in entry ? entry : null;
1139
+ }
1092
1140
  async runStep({
1093
1141
  stepId,
1094
1142
  run,
@@ -1130,7 +1178,7 @@ class WorkflowEngine {
1130
1178
  runId: run.id,
1131
1179
  resourceId: run.resourceId ?? undefined,
1132
1180
  data: {
1133
- timeline: import_es_toolkit.merge(run.timeline, {
1181
+ timeline: import_es_toolkit.merge(persistedRun.timeline, {
1134
1182
  [stepId]: {
1135
1183
  output,
1136
1184
  timestamp: new Date
@@ -1174,64 +1222,46 @@ ${error.stack}` : String(error)
1174
1222
  if (cached?.output !== undefined) {
1175
1223
  return cached.timedOut ? undefined : cached.output;
1176
1224
  }
1177
- const fastForwardConfig = persistedRun.timeline.__fastForward;
1178
- if (fastForwardConfig && eventName !== PAUSE_EVENT_NAME) {
1179
- let output = {};
1180
- if (eventName) {
1181
- if (typeof fastForwardConfig === "object" && fastForwardConfig !== null && !Array.isArray(fastForwardConfig)) {
1182
- const mockData = fastForwardConfig[stepId];
1183
- if (mockData !== undefined) {
1184
- output = mockData;
1185
- }
1186
- }
1187
- }
1188
- run = await this.updateRun({
1225
+ const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
1226
+ await withPostgresTransaction(this.db, async (db) => {
1227
+ const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
1228
+ return this.updateRun({
1189
1229
  runId: run.id,
1190
1230
  resourceId: run.resourceId ?? undefined,
1191
1231
  data: {
1232
+ status: "paused" /* PAUSED */,
1192
1233
  currentStepId: stepId,
1193
- timeline: import_es_toolkit.merge(run.timeline, {
1194
- [stepId]: {
1195
- output,
1234
+ pausedAt: new Date,
1235
+ timeline: import_es_toolkit.merge(freshRun.timeline, {
1236
+ [`${stepId}-wait-for`]: {
1237
+ waitFor: { eventName, timeoutEvent },
1196
1238
  timestamp: new Date
1197
1239
  }
1198
1240
  })
1199
1241
  }
1200
- });
1201
- this.logger.log(`Step ${stepId} fast-forwarded`, {
1202
- runId: run.id,
1203
- workflowId: run.workflowId
1204
- });
1205
- return output;
1206
- }
1207
- const timeoutEvent = timeoutDate ? `__timeout_${stepId}` : undefined;
1208
- await this.updateRun({
1209
- runId: run.id,
1210
- resourceId: run.resourceId ?? undefined,
1211
- data: {
1212
- status: "paused" /* PAUSED */,
1213
- currentStepId: stepId,
1214
- pausedAt: new Date,
1215
- timeline: import_es_toolkit.merge(run.timeline, {
1216
- [`${stepId}-wait-for`]: {
1217
- waitFor: { eventName, timeoutEvent },
1218
- timestamp: new Date
1219
- }
1220
- })
1221
- }
1222
- });
1242
+ }, { db });
1243
+ }, this.pool);
1223
1244
  if (timeoutDate && timeoutEvent) {
1224
- const job = {
1225
- runId: run.id,
1226
- resourceId: run.resourceId ?? undefined,
1227
- workflowId: run.workflowId,
1228
- input: run.input,
1229
- event: { name: timeoutEvent, data: { date: timeoutDate.toISOString() } }
1230
- };
1231
- await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
1232
- startAfter: timeoutDate.getTime() <= Date.now() ? new Date : timeoutDate,
1233
- expireInSeconds: defaultExpireInSeconds
1234
- });
1245
+ try {
1246
+ const job = {
1247
+ runId: run.id,
1248
+ resourceId: run.resourceId ?? undefined,
1249
+ workflowId: run.workflowId,
1250
+ input: run.input,
1251
+ event: { name: timeoutEvent, data: { date: timeoutDate.toISOString() } }
1252
+ };
1253
+ await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, job, {
1254
+ startAfter: timeoutDate.getTime() <= Date.now() ? new Date : timeoutDate,
1255
+ expireInSeconds: defaultExpireInSeconds
1256
+ });
1257
+ } catch (error) {
1258
+ await this.updateRun({
1259
+ runId: run.id,
1260
+ resourceId: run.resourceId ?? undefined,
1261
+ data: { status: "running" /* RUNNING */, pausedAt: null }
1262
+ });
1263
+ throw error;
1264
+ }
1235
1265
  }
1236
1266
  this.logger.log(`Step ${stepId} waiting${eventName ? ` for event "${eventName}"` : ""}${timeoutDate ? ` until ${timeoutDate.toISOString()}` : ""}`, { runId: run.id, workflowId: run.workflowId });
1237
1267
  }
@@ -1256,59 +1286,99 @@ ${error.stack}` : String(error)
1256
1286
  const pollStateEntry = persistedRun.timeline[`${stepId}-poll`];
1257
1287
  const startedAt = pollStateEntry && typeof pollStateEntry === "object" && "startedAt" in pollStateEntry ? new Date(pollStateEntry.startedAt) : new Date;
1258
1288
  if (timeoutMs !== undefined && Date.now() >= startedAt.getTime() + timeoutMs) {
1259
- await this.updateRun({
1289
+ await withPostgresTransaction(this.db, async (db) => {
1290
+ const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
1291
+ return this.updateRun({
1292
+ runId: run.id,
1293
+ resourceId: run.resourceId ?? undefined,
1294
+ data: {
1295
+ currentStepId: stepId,
1296
+ timeline: import_es_toolkit.merge(freshRun.timeline, {
1297
+ [stepId]: { output: {}, timedOut: true, timestamp: new Date }
1298
+ })
1299
+ }
1300
+ }, { db });
1301
+ }, this.pool);
1302
+ return { timedOut: true };
1303
+ }
1304
+ let result;
1305
+ try {
1306
+ result = await conditionFn();
1307
+ } catch (error) {
1308
+ this.logger.error(`Poll conditionFn for step ${stepId} threw an error, treating as non-match and continuing to poll`, error, { runId: run.id, workflowId: run.workflowId });
1309
+ if (timeoutMs !== undefined && Date.now() >= startedAt.getTime() + timeoutMs) {
1310
+ await withPostgresTransaction(this.db, async (db) => {
1311
+ const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
1312
+ return this.updateRun({
1313
+ runId: run.id,
1314
+ resourceId: run.resourceId ?? undefined,
1315
+ data: {
1316
+ currentStepId: stepId,
1317
+ timeline: import_es_toolkit.merge(freshRun.timeline, {
1318
+ [stepId]: { output: {}, timedOut: true, timestamp: new Date }
1319
+ })
1320
+ }
1321
+ }, { db });
1322
+ }, this.pool);
1323
+ return { timedOut: true };
1324
+ }
1325
+ result = false;
1326
+ }
1327
+ if (result !== false) {
1328
+ await withPostgresTransaction(this.db, async (db) => {
1329
+ const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
1330
+ return this.updateRun({
1331
+ runId: run.id,
1332
+ resourceId: run.resourceId ?? undefined,
1333
+ data: {
1334
+ currentStepId: stepId,
1335
+ timeline: import_es_toolkit.merge(freshRun.timeline, {
1336
+ [stepId]: { output: result, timestamp: new Date }
1337
+ })
1338
+ }
1339
+ }, { db });
1340
+ }, this.pool);
1341
+ return { timedOut: false, data: result };
1342
+ }
1343
+ const pollEvent = `__poll_${stepId}`;
1344
+ await withPostgresTransaction(this.db, async (db) => {
1345
+ const freshRun = await this.getRun({ runId: run.id, resourceId: run.resourceId ?? undefined }, { exclusiveLock: true, db });
1346
+ return this.updateRun({
1260
1347
  runId: run.id,
1261
1348
  resourceId: run.resourceId ?? undefined,
1262
1349
  data: {
1350
+ status: "paused" /* PAUSED */,
1263
1351
  currentStepId: stepId,
1264
- timeline: import_es_toolkit.merge(persistedRun.timeline, {
1265
- [stepId]: { output: {}, timedOut: true, timestamp: new Date }
1352
+ pausedAt: new Date,
1353
+ timeline: import_es_toolkit.merge(freshRun.timeline, {
1354
+ [`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
1355
+ [`${stepId}-wait-for`]: {
1356
+ waitFor: { timeoutEvent: pollEvent, skipOutput: true },
1357
+ timestamp: new Date
1358
+ }
1266
1359
  })
1267
1360
  }
1361
+ }, { db });
1362
+ }, this.pool);
1363
+ try {
1364
+ await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, {
1365
+ runId: run.id,
1366
+ resourceId: run.resourceId ?? undefined,
1367
+ workflowId: run.workflowId,
1368
+ input: run.input,
1369
+ event: { name: pollEvent, data: {} }
1370
+ }, {
1371
+ startAfter: new Date(Date.now() + intervalMs),
1372
+ expireInSeconds: defaultExpireInSeconds
1268
1373
  });
1269
- return { timedOut: true };
1270
- }
1271
- const result = await conditionFn();
1272
- if (result !== false) {
1374
+ } catch (error) {
1273
1375
  await this.updateRun({
1274
1376
  runId: run.id,
1275
1377
  resourceId: run.resourceId ?? undefined,
1276
- data: {
1277
- currentStepId: stepId,
1278
- timeline: import_es_toolkit.merge(persistedRun.timeline, {
1279
- [stepId]: { output: result, timestamp: new Date }
1280
- })
1281
- }
1378
+ data: { status: "running" /* RUNNING */, pausedAt: null }
1282
1379
  });
1283
- return { timedOut: false, data: result };
1380
+ throw error;
1284
1381
  }
1285
- const pollEvent = `__poll_${stepId}`;
1286
- await this.updateRun({
1287
- runId: run.id,
1288
- resourceId: run.resourceId ?? undefined,
1289
- data: {
1290
- status: "paused" /* PAUSED */,
1291
- currentStepId: stepId,
1292
- pausedAt: new Date,
1293
- timeline: import_es_toolkit.merge(persistedRun.timeline, {
1294
- [`${stepId}-poll`]: { startedAt: startedAt.toISOString() },
1295
- [`${stepId}-wait-for`]: {
1296
- waitFor: { timeoutEvent: pollEvent, skipOutput: true },
1297
- timestamp: new Date
1298
- }
1299
- })
1300
- }
1301
- });
1302
- await this.boss.send(WORKFLOW_RUN_QUEUE_NAME, {
1303
- runId: run.id,
1304
- resourceId: run.resourceId ?? undefined,
1305
- workflowId: run.workflowId,
1306
- input: run.input,
1307
- event: { name: pollEvent, data: {} }
1308
- }, {
1309
- startAfter: new Date(Date.now() + intervalMs),
1310
- expireInSeconds: defaultExpireInSeconds
1311
- });
1312
1382
  this.logger.log(`Step ${stepId} polling every ${intervalMs}ms...`, {
1313
1383
  runId: run.id,
1314
1384
  workflowId: run.workflowId
@@ -1353,5 +1423,5 @@ ${error.stack}` : String(error)
1353
1423
  }
1354
1424
  }
1355
1425
 
1356
- //# debugId=68C46DF96F96057E64756E2164756E21
1426
+ //# debugId=12BE08AB4C2E4D0564756E2164756E21
1357
1427
  //# sourceMappingURL=index.js.map
package/dist/index.d.cts CHANGED
@@ -236,10 +236,11 @@ declare class WorkflowEngine {
236
236
  exclusiveLock?: boolean;
237
237
  db?: Db;
238
238
  }): Promise<WorkflowRun>;
239
- updateRun({ runId, resourceId, data }: {
239
+ updateRun({ runId, resourceId, data, expectedStatuses }: {
240
240
  runId: string;
241
241
  resourceId?: string;
242
242
  data: Partial<WorkflowRun>;
243
+ expectedStatuses?: string[];
243
244
  }, { db }?: {
244
245
  db?: Db;
245
246
  }): Promise<WorkflowRun>;
@@ -256,6 +257,7 @@ declare class WorkflowEngine {
256
257
  private resolveScopedResourceId;
257
258
  private handleWorkflowRun;
258
259
  private getCachedStepEntry;
260
+ private getWaitForStepEntry;
259
261
  private runStep;
260
262
  private waitStep;
261
263
  private pollStep;
package/dist/index.d.ts CHANGED
@@ -236,10 +236,11 @@ declare class WorkflowEngine {
236
236
  exclusiveLock?: boolean;
237
237
  db?: Db;
238
238
  }): Promise<WorkflowRun>;
239
- updateRun({ runId, resourceId, data }: {
239
+ updateRun({ runId, resourceId, data, expectedStatuses }: {
240
240
  runId: string;
241
241
  resourceId?: string;
242
242
  data: Partial<WorkflowRun>;
243
+ expectedStatuses?: string[];
243
244
  }, { db }?: {
244
245
  db?: Db;
245
246
  }): Promise<WorkflowRun>;
@@ -256,6 +257,7 @@ declare class WorkflowEngine {
256
257
  private resolveScopedResourceId;
257
258
  private handleWorkflowRun;
258
259
  private getCachedStepEntry;
260
+ private getWaitForStepEntry;
259
261
  private runStep;
260
262
  private waitStep;
261
263
  private pollStep;