@dbos-inc/dbos-sdk 4.20.3-preview → 4.20.7-preview

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.
@@ -385,10 +385,8 @@ class SystemDatabase {
385
385
  notificationsMap = new NotificationMap();
386
386
  workflowEventsMap = new NotificationMap();
387
387
  streamsMap = new NotificationMap();
388
- cancelWakeupMap = new NotificationMap();
389
388
  customPool = false;
390
389
  runningWorkflowMap = new Map(); // Map from workflowID to workflow promise, queue name and partition key
391
- workflowCancellationMap = new Map(); // Map from workflowID to its cancellation status.
392
390
  constructor(systemDatabaseUrl, logger, serializer, sysDbPoolSize = exports.DEFAULT_POOL_SIZE, systemDatabasePool, schemaName = 'dbos', useListenNotify = true) {
393
391
  this.systemDatabaseUrl = systemDatabaseUrl;
394
392
  this.logger = logger;
@@ -517,9 +515,7 @@ class SystemDatabase {
517
515
  async recordWorkflowOutput(workflowID, status) {
518
516
  const client = await this.pool.connect();
519
517
  try {
520
- await this.updateWorkflowStatus(client, workflowID, workflow_1.StatusString.SUCCESS, {
521
- update: { output: status.output, resetDeduplicationID: true, setCompletedAt: true },
522
- });
518
+ await this.#recordWorkflowOutcome(client, workflowID, workflow_1.StatusString.SUCCESS, { output: status.output });
523
519
  }
524
520
  finally {
525
521
  client.release();
@@ -528,14 +524,37 @@ class SystemDatabase {
528
524
  async recordWorkflowError(workflowID, status) {
529
525
  const client = await this.pool.connect();
530
526
  try {
531
- await this.updateWorkflowStatus(client, workflowID, workflow_1.StatusString.ERROR, {
532
- update: { error: status.error, resetDeduplicationID: true, setCompletedAt: true },
533
- });
527
+ await this.#recordWorkflowOutcome(client, workflowID, workflow_1.StatusString.ERROR, { error: status.error });
534
528
  }
535
529
  finally {
536
530
  client.release();
537
531
  }
538
532
  }
533
+ // Record a workflow's terminal outcome (SUCCESS or ERROR), but never overwrite
534
+ // the terminal CANCELLED status: a workflow can be cancelled during its final
535
+ // step, and if so it must not be able to subsequently complete. If the
536
+ // workflow is cancelled, abort the function so it does not complete. This
537
+ // mirrors the cancellation check done before each step.
538
+ async #recordWorkflowOutcome(client, workflowID, status, outcome) {
539
+ let cancelled = false;
540
+ try {
541
+ await client.query('BEGIN');
542
+ await this.updateWorkflowStatus(client, workflowID, status, {
543
+ update: { ...outcome, resetDeduplicationID: true, setCompletedAt: true },
544
+ where: { notStatus: workflow_1.StatusString.CANCELLED },
545
+ throwOnFailure: false,
546
+ });
547
+ cancelled = (await this.getWorkflowStatusValue(client, workflowID)) === workflow_1.StatusString.CANCELLED;
548
+ await client.query('COMMIT');
549
+ }
550
+ catch (e) {
551
+ await client.query('ROLLBACK');
552
+ throw e;
553
+ }
554
+ if (cancelled) {
555
+ throw new error_1.DBOSWorkflowCancelledError(workflowID);
556
+ }
557
+ }
539
558
  async getPendingWorkflows(executorID, appVersion) {
540
559
  const getWorkflows = await this.pool.query(`SELECT workflow_uuid, queue_name
541
560
  FROM "${this.schemaName}".workflow_status
@@ -697,23 +716,11 @@ class SystemDatabase {
697
716
  completed_at = (EXTRACT(EPOCH FROM now()) * 1000)::bigint
698
717
  WHERE workflow_uuid = ANY($2)
699
718
  AND status NOT IN ($3, $4)`, [workflow_1.StatusString.CANCELLED, workflowIDs, workflow_1.StatusString.SUCCESS, workflow_1.StatusString.ERROR]);
700
- for (const workflowID of workflowIDs) {
701
- this.#setWFCancelMap(workflowID);
702
- }
703
719
  }
704
720
  async checkIfCanceled(workflowID) {
705
- const client = await this.pool.connect();
706
- try {
707
- await this.#checkIfCanceled(client, workflowID);
708
- }
709
- finally {
710
- client.release();
711
- }
721
+ await this.#checkIfCanceled(this.pool, workflowID);
712
722
  }
713
723
  async resumeWorkflows(workflowIDs, queueName) {
714
- for (const workflowID of workflowIDs) {
715
- this.#clearWFCancelMap(workflowID);
716
- }
717
724
  await this.pool.query(`UPDATE "${this.schemaName}".workflow_status
718
725
  SET status = $1, queue_name = $2, recovery_attempts = 0,
719
726
  workflow_deadline_epoch_ms = NULL, deduplication_id = NULL,
@@ -768,7 +775,6 @@ class SystemDatabase {
768
775
  await this.pool.query(`DELETE FROM "${this.schemaName}".workflow_status WHERE workflow_uuid = ANY($1)`, [allIds]);
769
776
  for (const wfid of allIds) {
770
777
  this.runningWorkflowMap.delete(wfid);
771
- this.workflowCancellationMap.delete(wfid);
772
778
  }
773
779
  }
774
780
  async forkWorkflow(workflowID, startStep, options = {}) {
@@ -1131,7 +1137,6 @@ class SystemDatabase {
1131
1137
  .finally(() => {
1132
1138
  // Remove itself from pending workflow map.
1133
1139
  this.runningWorkflowMap.delete(workflowID);
1134
- this.workflowCancellationMap.delete(workflowID);
1135
1140
  });
1136
1141
  this.runningWorkflowMap.set(workflowID, {
1137
1142
  promise: awaitWorkflowPromise,
@@ -1164,149 +1169,91 @@ class SystemDatabase {
1164
1169
  //throw new Error('Message notification map is not empty - shutdown is not clean.');
1165
1170
  }
1166
1171
  }
1167
- async awaitWorkflowResult(workflowID, timeoutSeconds, callerID, timerFuncID) {
1172
+ async awaitWorkflowResult(workflowID, timeoutSeconds, callerID, timerFuncID, pollingIntervalMs) {
1168
1173
  const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
1169
1174
  let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
1175
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalResultMs;
1170
1176
  while (true) {
1171
- let resolveNotification;
1172
- const statusPromise = new Promise((resolve) => {
1173
- resolveNotification = resolve;
1174
- });
1175
- const irh = this.cancelWakeupMap.registerCallback(workflowID, (_res) => {
1176
- resolveNotification();
1177
- });
1178
- const crh = callerID
1179
- ? this.cancelWakeupMap.registerCallback(callerID, (_res) => {
1180
- resolveNotification();
1181
- })
1182
- : undefined;
1177
+ if (callerID)
1178
+ await this.checkIfCanceled(callerID);
1183
1179
  try {
1184
- if (callerID)
1185
- await this.checkIfCanceled(callerID);
1186
- try {
1187
- const { rows } = await this.pool.query(`SELECT status, output, error, serialization FROM "${this.schemaName}".workflow_status
1188
- WHERE workflow_uuid=$1`, [workflowID]);
1189
- if (rows.length > 0) {
1190
- const status = rows[0].status;
1191
- if (status === workflow_1.StatusString.SUCCESS) {
1192
- return { output: rows[0].output, serialization: rows[0].serialization };
1193
- }
1194
- else if (status === workflow_1.StatusString.ERROR) {
1195
- return { error: rows[0].error, serialization: rows[0].serialization };
1196
- }
1197
- else if (status === workflow_1.StatusString.CANCELLED) {
1198
- return { cancelled: true };
1199
- }
1200
- else if (status === workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED) {
1201
- return { maxRecoveryAttemptsExceeded: true };
1202
- }
1203
- else {
1204
- // Status is not actionable
1205
- }
1180
+ const { rows } = await this.pool.query(`SELECT status, output, error, serialization FROM "${this.schemaName}".workflow_status
1181
+ WHERE workflow_uuid=$1`, [workflowID]);
1182
+ if (rows.length > 0) {
1183
+ const status = rows[0].status;
1184
+ if (status === workflow_1.StatusString.SUCCESS) {
1185
+ return { output: rows[0].output, serialization: rows[0].serialization };
1186
+ }
1187
+ else if (status === workflow_1.StatusString.ERROR) {
1188
+ return { error: rows[0].error, serialization: rows[0].serialization };
1189
+ }
1190
+ else if (status === workflow_1.StatusString.CANCELLED) {
1191
+ return { cancelled: true };
1192
+ }
1193
+ else if (status === workflow_1.StatusString.MAX_RECOVERY_ATTEMPTS_EXCEEDED) {
1194
+ return { maxRecoveryAttemptsExceeded: true };
1195
+ }
1196
+ else {
1197
+ // Status is not actionable
1206
1198
  }
1207
- }
1208
- catch (e) {
1209
- const err = e;
1210
- this.logger.error(`Exception from system database: ${err}`, err);
1211
- throw err;
1212
- }
1213
- const ct = Date.now();
1214
- if (finishTime && ct > finishTime)
1215
- return undefined; // Time's up
1216
- let timeoutPromise = Promise.resolve();
1217
- let timeoutCancel = () => { };
1218
- if (timerFuncID !== undefined && callerID !== undefined && timeoutms !== undefined) {
1219
- const { promise, cancel, endTime } = await this.#durableSleep(callerID, timerFuncID, timeoutms, this.dbPollingIntervalResultMs);
1220
- finishTime = endTime;
1221
- timeoutPromise = promise;
1222
- timeoutCancel = cancel;
1223
- }
1224
- else {
1225
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalResultMs;
1226
- poll = Math.min(this.dbPollingIntervalResultMs, poll);
1227
- const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
1228
- timeoutPromise = promise;
1229
- timeoutCancel = cancel;
1230
- }
1231
- try {
1232
- await Promise.race([statusPromise, timeoutPromise]);
1233
- }
1234
- finally {
1235
- timeoutCancel();
1236
1199
  }
1237
1200
  }
1238
- finally {
1239
- this.cancelWakeupMap.deregisterCallback(irh);
1240
- if (crh)
1241
- this.cancelWakeupMap.deregisterCallback(crh);
1201
+ catch (e) {
1202
+ const err = e;
1203
+ this.logger.error(`Exception from system database: ${err}`, err);
1204
+ throw err;
1205
+ }
1206
+ const ct = Date.now();
1207
+ if (finishTime && ct > finishTime)
1208
+ return undefined; // Time's up
1209
+ if (timerFuncID !== undefined && callerID !== undefined && timeoutms !== undefined) {
1210
+ finishTime = await this.#durableSleep(callerID, timerFuncID, timeoutms);
1242
1211
  }
1212
+ let poll = finishTime ? finishTime - Date.now() : pollIntervalMs;
1213
+ poll = Math.min(pollIntervalMs, poll);
1214
+ await (0, utils_1.sleepms)(poll);
1243
1215
  }
1244
1216
  }
1245
- async awaitFirstWorkflowId(workflowIds, callerID) {
1217
+ async awaitFirstWorkflowId(workflowIds, callerID, pollingIntervalMs) {
1246
1218
  const placeholders = workflowIds.map((_, i) => `$${i + 1}`).join(', ');
1219
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalResultMs;
1247
1220
  while (true) {
1248
- let resolveNotification;
1249
- const wakeupPromise = new Promise((resolve) => {
1250
- resolveNotification = resolve;
1251
- });
1252
- // Register cancel callbacks for all target workflows and the caller.
1253
- const cbHandles = workflowIds.map((wfid) => this.cancelWakeupMap.registerCallback(wfid, () => resolveNotification()));
1254
- const callerCbHandle = callerID
1255
- ? this.cancelWakeupMap.registerCallback(callerID, () => resolveNotification())
1256
- : undefined;
1257
- try {
1258
- if (callerID)
1259
- await this.checkIfCanceled(callerID);
1260
- const { rows } = await this.pool.query(`SELECT workflow_uuid FROM "${this.schemaName}".workflow_status
1261
- WHERE workflow_uuid IN (${placeholders})
1262
- AND status NOT IN ('${workflow_1.StatusString.PENDING}', '${workflow_1.StatusString.ENQUEUED}', '${workflow_1.StatusString.DELAYED}')
1263
- LIMIT 1`, workflowIds);
1264
- if (rows.length > 0) {
1265
- return rows[0].workflow_uuid;
1266
- }
1267
- const { promise: sleepPromise, cancel: sleepCancel } = (0, utils_1.cancellableSleep)(this.dbPollingIntervalResultMs);
1268
- try {
1269
- await Promise.race([wakeupPromise, sleepPromise]);
1270
- }
1271
- finally {
1272
- sleepCancel();
1273
- }
1274
- }
1275
- finally {
1276
- for (const h of cbHandles) {
1277
- this.cancelWakeupMap.deregisterCallback(h);
1278
- }
1279
- if (callerCbHandle)
1280
- this.cancelWakeupMap.deregisterCallback(callerCbHandle);
1221
+ if (callerID)
1222
+ await this.checkIfCanceled(callerID);
1223
+ const { rows } = await this.pool.query(`SELECT workflow_uuid FROM "${this.schemaName}".workflow_status
1224
+ WHERE workflow_uuid IN (${placeholders})
1225
+ AND status NOT IN ('${workflow_1.StatusString.PENDING}', '${workflow_1.StatusString.ENQUEUED}', '${workflow_1.StatusString.DELAYED}')
1226
+ LIMIT 1`, workflowIds);
1227
+ if (rows.length > 0) {
1228
+ return rows[0].workflow_uuid;
1229
+ }
1230
+ await (0, utils_1.sleepms)(pollIntervalMs);
1231
+ }
1232
+ }
1233
+ async awaitWorkflowIds(workflowIds, callerID, pollingIntervalMs) {
1234
+ const remainingWorkflowIds = new Set(workflowIds);
1235
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalResultMs;
1236
+ while (remainingWorkflowIds.size > 0) {
1237
+ const currentWorkflowIds = [...remainingWorkflowIds];
1238
+ if (callerID)
1239
+ await this.checkIfCanceled(callerID);
1240
+ const { rows } = await this.pool.query(`SELECT workflow_uuid FROM "${this.schemaName}".workflow_status
1241
+ WHERE workflow_uuid = ANY($1::text[])
1242
+ AND status NOT IN ('${workflow_1.StatusString.PENDING}', '${workflow_1.StatusString.ENQUEUED}', '${workflow_1.StatusString.DELAYED}')`, [currentWorkflowIds]);
1243
+ for (const row of rows) {
1244
+ remainingWorkflowIds.delete(row.workflow_uuid);
1245
+ }
1246
+ if (remainingWorkflowIds.size === 0) {
1247
+ return;
1281
1248
  }
1249
+ await (0, utils_1.sleepms)(pollIntervalMs);
1282
1250
  }
1283
1251
  }
1284
1252
  // ==================== Sleep ====================
1285
1253
  async durableSleepms(workflowID, functionID, durationMS) {
1286
- let cancelled = false;
1287
- let resolveNotification;
1288
- const cancelPromise = new Promise((resolve) => {
1289
- resolveNotification = () => {
1290
- cancelled = true;
1291
- resolve();
1292
- };
1293
- });
1294
- const cbr = this.cancelWakeupMap.registerCallback(workflowID, resolveNotification);
1295
- try {
1296
- const { cancel: cancelInitial, endTime } = await this.#durableSleep(workflowID, functionID, durationMS);
1297
- cancelInitial();
1298
- while (!cancelled && Date.now() < endTime) {
1299
- const { promise, cancel } = (0, utils_1.cancellableSleep)(Math.min(endTime - Date.now(), utils_1.sleepConfig.maxTimeoutMS));
1300
- try {
1301
- await Promise.race([cancelPromise, promise]);
1302
- }
1303
- finally {
1304
- cancel();
1305
- }
1306
- }
1307
- }
1308
- finally {
1309
- this.cancelWakeupMap.deregisterCallback(cbr);
1254
+ const endTime = await this.#durableSleep(workflowID, functionID, durationMS);
1255
+ while (Date.now() < endTime) {
1256
+ await (0, utils_1.sleepms)(Math.min(endTime - Date.now(), utils_1.sleepConfig.maxTimeoutMS));
1310
1257
  }
1311
1258
  await this.checkIfCanceled(workflowID);
1312
1259
  }
@@ -1357,7 +1304,7 @@ class SystemDatabase {
1357
1304
  throw err;
1358
1305
  }
1359
1306
  }
1360
- async recv(workflowID, functionID, timeoutFunctionID, topic, timeoutSeconds = dbos_executor_1.DBOSExecutor.defaultNotificationTimeoutSec) {
1307
+ async recv(workflowID, functionID, timeoutFunctionID, topic, timeoutSeconds = dbos_executor_1.DBOSExecutor.defaultNotificationTimeoutSec, pollingIntervalMs) {
1361
1308
  topic = topic ?? this.nullTopic;
1362
1309
  const startTime = Date.now();
1363
1310
  // First, check for previous executions.
@@ -1370,6 +1317,7 @@ class SystemDatabase {
1370
1317
  }
1371
1318
  const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
1372
1319
  let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
1320
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalEventMs;
1373
1321
  while (true) {
1374
1322
  // register the key with the global notifications listener.
1375
1323
  let resolveNotification;
@@ -1378,9 +1326,6 @@ class SystemDatabase {
1378
1326
  });
1379
1327
  const payload = `${workflowID}::${topic}`;
1380
1328
  const cbr = this.notificationsMap.registerCallback(payload, resolveNotification);
1381
- const crh = this.cancelWakeupMap.registerCallback(workflowID, (_res) => {
1382
- resolveNotification();
1383
- });
1384
1329
  try {
1385
1330
  await this.checkIfCanceled(workflowID);
1386
1331
  // Check if the key is already in the DB, then wait for the notification if it isn't.
@@ -1390,31 +1335,21 @@ class SystemDatabase {
1390
1335
  const ct = Date.now();
1391
1336
  if (finishTime && ct > finishTime)
1392
1337
  break; // Time's up
1393
- let timeoutPromise = Promise.resolve();
1394
- let timeoutCancel = () => { };
1395
1338
  if (timeoutms) {
1396
- const { promise, cancel, endTime } = await this.#durableSleep(workflowID, timeoutFunctionID, timeoutms, this.dbPollingIntervalEventMs);
1397
- timeoutPromise = promise;
1398
- timeoutCancel = cancel;
1399
- finishTime = endTime;
1400
- }
1401
- else {
1402
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalEventMs;
1403
- poll = Math.min(this.dbPollingIntervalEventMs, poll);
1404
- const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
1405
- timeoutPromise = promise;
1406
- timeoutCancel = cancel;
1339
+ finishTime = await this.#durableSleep(workflowID, timeoutFunctionID, timeoutms);
1407
1340
  }
1341
+ let poll = finishTime ? finishTime - Date.now() : pollIntervalMs;
1342
+ poll = Math.min(pollIntervalMs, poll);
1343
+ const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
1408
1344
  try {
1409
- await Promise.race([messagePromise, timeoutPromise]);
1345
+ await Promise.race([messagePromise, promise]);
1410
1346
  }
1411
1347
  finally {
1412
- timeoutCancel();
1348
+ cancel();
1413
1349
  }
1414
1350
  }
1415
1351
  finally {
1416
1352
  this.notificationsMap.deregisterCallback(cbr);
1417
- this.cancelWakeupMap.deregisterCallback(crh);
1418
1353
  }
1419
1354
  }
1420
1355
  await this.checkIfCanceled(workflowID);
@@ -1488,7 +1423,7 @@ class SystemDatabase {
1488
1423
  client.release();
1489
1424
  }
1490
1425
  }
1491
- async getEvent(workflowID, key, timeoutSeconds, callerWorkflow) {
1426
+ async getEvent(workflowID, key, timeoutSeconds, callerWorkflow, pollingIntervalMs) {
1492
1427
  const startTime = Date.now();
1493
1428
  // Check if the operation has been done before for OAOO (only do this inside a workflow).
1494
1429
  if (callerWorkflow) {
@@ -1506,6 +1441,7 @@ class SystemDatabase {
1506
1441
  const payloadKey = `${workflowID}::${key}`;
1507
1442
  const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
1508
1443
  let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
1444
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalEventMs;
1509
1445
  // Register the key with the global notifications listener first... we do not want to look in the DB first
1510
1446
  // or that would cause a timing hole.
1511
1447
  while (true) {
@@ -1514,11 +1450,6 @@ class SystemDatabase {
1514
1450
  resolveNotification = resolve;
1515
1451
  });
1516
1452
  const cbr = this.workflowEventsMap.registerCallback(payloadKey, resolveNotification);
1517
- const crh = callerWorkflow?.workflowID
1518
- ? this.cancelWakeupMap.registerCallback(callerWorkflow.workflowID, (_res) => {
1519
- resolveNotification();
1520
- })
1521
- : undefined;
1522
1453
  try {
1523
1454
  if (callerWorkflow?.workflowID)
1524
1455
  await this.checkIfCanceled(callerWorkflow?.workflowID);
@@ -1535,32 +1466,21 @@ class SystemDatabase {
1535
1466
  if (finishTime && ct > finishTime)
1536
1467
  break; // Time's up
1537
1468
  // If we have a callerWorkflow, we want a durable sleep, otherwise, not
1538
- let timeoutPromise = Promise.resolve();
1539
- let timeoutCancel = () => { };
1540
1469
  if (callerWorkflow && timeoutms) {
1541
- const { promise, cancel, endTime } = await this.#durableSleep(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutms, this.dbPollingIntervalEventMs);
1542
- timeoutPromise = promise;
1543
- timeoutCancel = cancel;
1544
- finishTime = endTime;
1545
- }
1546
- else {
1547
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalEventMs;
1548
- poll = Math.min(this.dbPollingIntervalEventMs, poll);
1549
- const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
1550
- timeoutPromise = promise;
1551
- timeoutCancel = cancel;
1470
+ finishTime = await this.#durableSleep(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutms);
1552
1471
  }
1472
+ let poll = finishTime ? finishTime - Date.now() : pollIntervalMs;
1473
+ poll = Math.min(pollIntervalMs, poll);
1474
+ const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
1553
1475
  try {
1554
- await Promise.race([valuePromise, timeoutPromise]);
1476
+ await Promise.race([valuePromise, promise]);
1555
1477
  }
1556
1478
  finally {
1557
- timeoutCancel();
1479
+ cancel();
1558
1480
  }
1559
1481
  }
1560
1482
  finally {
1561
1483
  this.workflowEventsMap.deregisterCallback(cbr);
1562
- if (crh)
1563
- this.cancelWakeupMap.deregisterCallback(crh);
1564
1484
  }
1565
1485
  }
1566
1486
  // Record the output if it is inside a workflow.
@@ -2758,6 +2678,10 @@ class SystemDatabase {
2758
2678
  const param = args.push(where.status);
2759
2679
  whereClause += ` AND status=$${param}`;
2760
2680
  }
2681
+ if (where.notStatus) {
2682
+ const param = args.push(where.notStatus);
2683
+ whereClause += ` AND status!=$${param}`;
2684
+ }
2761
2685
  const result = await client.query(`UPDATE "${this.schemaName}".workflow_status ${setClause} ${whereClause}`, args);
2762
2686
  const throwOnFailure = options.throwOnFailure ?? true;
2763
2687
  if (throwOnFailure && result.rowCount !== 1) {
@@ -2832,31 +2756,18 @@ class SystemDatabase {
2832
2756
  });
2833
2757
  return output;
2834
2758
  }
2835
- #setWFCancelMap(workflowID) {
2836
- if (this.runningWorkflowMap.has(workflowID)) {
2837
- this.workflowCancellationMap.set(workflowID, true);
2838
- }
2839
- this.cancelWakeupMap.callCallbacks(workflowID);
2840
- }
2841
- #clearWFCancelMap(workflowID) {
2842
- if (this.workflowCancellationMap.has(workflowID)) {
2843
- this.workflowCancellationMap.delete(workflowID);
2844
- }
2845
- }
2846
2759
  async #checkIfCanceled(client, workflowID) {
2847
- if (this.workflowCancellationMap.get(workflowID) === true) {
2848
- throw new error_1.DBOSWorkflowCancelledError(workflowID);
2849
- }
2850
2760
  const statusValue = await this.getWorkflowStatusValue(client, workflowID);
2851
2761
  if (statusValue === workflow_1.StatusString.CANCELLED) {
2852
2762
  throw new error_1.DBOSWorkflowCancelledError(workflowID);
2853
2763
  }
2854
2764
  }
2855
- async #durableSleep(workflowID, functionID, durationMS, maxSleepPerIteration) {
2856
- if (maxSleepPerIteration === undefined)
2857
- maxSleepPerIteration = durationMS;
2858
- const curTime = Date.now();
2859
- let endTimeMs = curTime + durationMS;
2765
+ // Durably records (or, on recovery, reads back) the wakeup deadline for a sleep or
2766
+ // timeout so it survives recovery. Returns the absolute end time in epoch ms; the
2767
+ // caller is responsible for actually waiting until then. Throws if the workflow has
2768
+ // been cancelled.
2769
+ async #durableSleep(workflowID, functionID, durationMS) {
2770
+ const endTimeMs = Date.now() + durationMS;
2860
2771
  const client = await this.pool.connect();
2861
2772
  try {
2862
2773
  const res = await this.#getOperationResultAndThrowIfCancelled(client, workflowID, functionID);
@@ -2864,18 +2775,13 @@ class SystemDatabase {
2864
2775
  if (res.functionName !== exports.DBOS_FUNCNAME_SLEEP) {
2865
2776
  throw new error_1.DBOSUnexpectedStepError(workflowID, functionID, exports.DBOS_FUNCNAME_SLEEP, res.functionName);
2866
2777
  }
2867
- endTimeMs = JSON.parse(res.output);
2868
- }
2869
- else {
2870
- await this.recordOperationResultInternal(client, workflowID, functionID, exports.DBOS_FUNCNAME_SLEEP, false, Date.now(), Date.now(), {
2871
- output: serialization_1.DBOSPortableJSON.stringify(endTimeMs),
2872
- serialization: serialization_1.DBOSPortableJSON.name(),
2873
- });
2778
+ return JSON.parse(res.output);
2874
2779
  }
2875
- return {
2876
- ...(0, utils_1.cancellableSleep)(Math.max(Math.min(maxSleepPerIteration, endTimeMs - curTime), 0)),
2877
- endTime: endTimeMs,
2878
- };
2780
+ await this.recordOperationResultInternal(client, workflowID, functionID, exports.DBOS_FUNCNAME_SLEEP, false, Date.now(), Date.now(), {
2781
+ output: serialization_1.DBOSPortableJSON.stringify(endTimeMs),
2782
+ serialization: serialization_1.DBOSPortableJSON.name(),
2783
+ });
2784
+ return endTimeMs;
2879
2785
  }
2880
2786
  finally {
2881
2787
  client.release();
@@ -3013,15 +2919,21 @@ __decorate([
3013
2919
  __decorate([
3014
2920
  dbRetry(),
3015
2921
  __metadata("design:type", Function),
3016
- __metadata("design:paramtypes", [String, Number, String, Number]),
2922
+ __metadata("design:paramtypes", [String, Number, String, Number, Number]),
3017
2923
  __metadata("design:returntype", Promise)
3018
2924
  ], SystemDatabase.prototype, "awaitWorkflowResult", null);
3019
2925
  __decorate([
3020
2926
  dbRetry(),
3021
2927
  __metadata("design:type", Function),
3022
- __metadata("design:paramtypes", [Array, String]),
2928
+ __metadata("design:paramtypes", [Array, String, Number]),
3023
2929
  __metadata("design:returntype", Promise)
3024
2930
  ], SystemDatabase.prototype, "awaitFirstWorkflowId", null);
2931
+ __decorate([
2932
+ dbRetry(),
2933
+ __metadata("design:type", Function),
2934
+ __metadata("design:paramtypes", [Array, String, Number]),
2935
+ __metadata("design:returntype", Promise)
2936
+ ], SystemDatabase.prototype, "awaitWorkflowIds", null);
3025
2937
  __decorate([
3026
2938
  dbRetry(),
3027
2939
  __metadata("design:type", Function),
@@ -3043,7 +2955,7 @@ __decorate([
3043
2955
  __decorate([
3044
2956
  dbRetry(),
3045
2957
  __metadata("design:type", Function),
3046
- __metadata("design:paramtypes", [String, Number, Number, String, Number]),
2958
+ __metadata("design:paramtypes", [String, Number, Number, String, Number, Number]),
3047
2959
  __metadata("design:returntype", Promise)
3048
2960
  ], SystemDatabase.prototype, "recv", null);
3049
2961
  __decorate([
@@ -3055,7 +2967,7 @@ __decorate([
3055
2967
  __decorate([
3056
2968
  dbRetry(),
3057
2969
  __metadata("design:type", Function),
3058
- __metadata("design:paramtypes", [String, String, Number, Object]),
2970
+ __metadata("design:paramtypes", [String, String, Number, Object, Number]),
3059
2971
  __metadata("design:returntype", Promise)
3060
2972
  ], SystemDatabase.prototype, "getEvent", null);
3061
2973
  __decorate([