@dbos-inc/dbos-sdk 4.20.3-preview → 4.20.5-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.
@@ -517,9 +517,7 @@ class SystemDatabase {
517
517
  async recordWorkflowOutput(workflowID, status) {
518
518
  const client = await this.pool.connect();
519
519
  try {
520
- await this.updateWorkflowStatus(client, workflowID, workflow_1.StatusString.SUCCESS, {
521
- update: { output: status.output, resetDeduplicationID: true, setCompletedAt: true },
522
- });
520
+ await this.#recordWorkflowOutcome(client, workflowID, workflow_1.StatusString.SUCCESS, { output: status.output });
523
521
  }
524
522
  finally {
525
523
  client.release();
@@ -528,14 +526,37 @@ class SystemDatabase {
528
526
  async recordWorkflowError(workflowID, status) {
529
527
  const client = await this.pool.connect();
530
528
  try {
531
- await this.updateWorkflowStatus(client, workflowID, workflow_1.StatusString.ERROR, {
532
- update: { error: status.error, resetDeduplicationID: true, setCompletedAt: true },
533
- });
529
+ await this.#recordWorkflowOutcome(client, workflowID, workflow_1.StatusString.ERROR, { error: status.error });
534
530
  }
535
531
  finally {
536
532
  client.release();
537
533
  }
538
534
  }
535
+ // Record a workflow's terminal outcome (SUCCESS or ERROR), but never overwrite
536
+ // the terminal CANCELLED status: a workflow can be cancelled during its final
537
+ // step, and if so it must not be able to subsequently complete. If the
538
+ // workflow is cancelled, abort the function so it does not complete. This
539
+ // mirrors the cancellation check done before each step.
540
+ async #recordWorkflowOutcome(client, workflowID, status, outcome) {
541
+ let cancelled = false;
542
+ try {
543
+ await client.query('BEGIN');
544
+ await this.updateWorkflowStatus(client, workflowID, status, {
545
+ update: { ...outcome, resetDeduplicationID: true, setCompletedAt: true },
546
+ where: { notStatus: workflow_1.StatusString.CANCELLED },
547
+ throwOnFailure: false,
548
+ });
549
+ cancelled = (await this.getWorkflowStatusValue(client, workflowID)) === workflow_1.StatusString.CANCELLED;
550
+ await client.query('COMMIT');
551
+ }
552
+ catch (e) {
553
+ await client.query('ROLLBACK');
554
+ throw e;
555
+ }
556
+ if (cancelled) {
557
+ throw new error_1.DBOSWorkflowCancelledError(workflowID);
558
+ }
559
+ }
539
560
  async getPendingWorkflows(executorID, appVersion) {
540
561
  const getWorkflows = await this.pool.query(`SELECT workflow_uuid, queue_name
541
562
  FROM "${this.schemaName}".workflow_status
@@ -1164,9 +1185,10 @@ class SystemDatabase {
1164
1185
  //throw new Error('Message notification map is not empty - shutdown is not clean.');
1165
1186
  }
1166
1187
  }
1167
- async awaitWorkflowResult(workflowID, timeoutSeconds, callerID, timerFuncID) {
1188
+ async awaitWorkflowResult(workflowID, timeoutSeconds, callerID, timerFuncID, pollingIntervalMs) {
1168
1189
  const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
1169
1190
  let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
1191
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalResultMs;
1170
1192
  while (true) {
1171
1193
  let resolveNotification;
1172
1194
  const statusPromise = new Promise((resolve) => {
@@ -1216,14 +1238,14 @@ class SystemDatabase {
1216
1238
  let timeoutPromise = Promise.resolve();
1217
1239
  let timeoutCancel = () => { };
1218
1240
  if (timerFuncID !== undefined && callerID !== undefined && timeoutms !== undefined) {
1219
- const { promise, cancel, endTime } = await this.#durableSleep(callerID, timerFuncID, timeoutms, this.dbPollingIntervalResultMs);
1241
+ const { promise, cancel, endTime } = await this.#durableSleep(callerID, timerFuncID, timeoutms, pollIntervalMs);
1220
1242
  finishTime = endTime;
1221
1243
  timeoutPromise = promise;
1222
1244
  timeoutCancel = cancel;
1223
1245
  }
1224
1246
  else {
1225
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalResultMs;
1226
- poll = Math.min(this.dbPollingIntervalResultMs, poll);
1247
+ let poll = finishTime ? finishTime - ct : pollIntervalMs;
1248
+ poll = Math.min(pollIntervalMs, poll);
1227
1249
  const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
1228
1250
  timeoutPromise = promise;
1229
1251
  timeoutCancel = cancel;
@@ -1242,8 +1264,9 @@ class SystemDatabase {
1242
1264
  }
1243
1265
  }
1244
1266
  }
1245
- async awaitFirstWorkflowId(workflowIds, callerID) {
1267
+ async awaitFirstWorkflowId(workflowIds, callerID, pollingIntervalMs) {
1246
1268
  const placeholders = workflowIds.map((_, i) => `$${i + 1}`).join(', ');
1269
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalResultMs;
1247
1270
  while (true) {
1248
1271
  let resolveNotification;
1249
1272
  const wakeupPromise = new Promise((resolve) => {
@@ -1264,7 +1287,7 @@ class SystemDatabase {
1264
1287
  if (rows.length > 0) {
1265
1288
  return rows[0].workflow_uuid;
1266
1289
  }
1267
- const { promise: sleepPromise, cancel: sleepCancel } = (0, utils_1.cancellableSleep)(this.dbPollingIntervalResultMs);
1290
+ const { promise: sleepPromise, cancel: sleepCancel } = (0, utils_1.cancellableSleep)(pollIntervalMs);
1268
1291
  try {
1269
1292
  await Promise.race([wakeupPromise, sleepPromise]);
1270
1293
  }
@@ -1357,7 +1380,7 @@ class SystemDatabase {
1357
1380
  throw err;
1358
1381
  }
1359
1382
  }
1360
- async recv(workflowID, functionID, timeoutFunctionID, topic, timeoutSeconds = dbos_executor_1.DBOSExecutor.defaultNotificationTimeoutSec) {
1383
+ async recv(workflowID, functionID, timeoutFunctionID, topic, timeoutSeconds = dbos_executor_1.DBOSExecutor.defaultNotificationTimeoutSec, pollingIntervalMs) {
1361
1384
  topic = topic ?? this.nullTopic;
1362
1385
  const startTime = Date.now();
1363
1386
  // First, check for previous executions.
@@ -1370,6 +1393,7 @@ class SystemDatabase {
1370
1393
  }
1371
1394
  const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
1372
1395
  let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
1396
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalEventMs;
1373
1397
  while (true) {
1374
1398
  // register the key with the global notifications listener.
1375
1399
  let resolveNotification;
@@ -1393,14 +1417,14 @@ class SystemDatabase {
1393
1417
  let timeoutPromise = Promise.resolve();
1394
1418
  let timeoutCancel = () => { };
1395
1419
  if (timeoutms) {
1396
- const { promise, cancel, endTime } = await this.#durableSleep(workflowID, timeoutFunctionID, timeoutms, this.dbPollingIntervalEventMs);
1420
+ const { promise, cancel, endTime } = await this.#durableSleep(workflowID, timeoutFunctionID, timeoutms, pollIntervalMs);
1397
1421
  timeoutPromise = promise;
1398
1422
  timeoutCancel = cancel;
1399
1423
  finishTime = endTime;
1400
1424
  }
1401
1425
  else {
1402
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalEventMs;
1403
- poll = Math.min(this.dbPollingIntervalEventMs, poll);
1426
+ let poll = finishTime ? finishTime - ct : pollIntervalMs;
1427
+ poll = Math.min(pollIntervalMs, poll);
1404
1428
  const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
1405
1429
  timeoutPromise = promise;
1406
1430
  timeoutCancel = cancel;
@@ -1488,7 +1512,7 @@ class SystemDatabase {
1488
1512
  client.release();
1489
1513
  }
1490
1514
  }
1491
- async getEvent(workflowID, key, timeoutSeconds, callerWorkflow) {
1515
+ async getEvent(workflowID, key, timeoutSeconds, callerWorkflow, pollingIntervalMs) {
1492
1516
  const startTime = Date.now();
1493
1517
  // Check if the operation has been done before for OAOO (only do this inside a workflow).
1494
1518
  if (callerWorkflow) {
@@ -1506,6 +1530,7 @@ class SystemDatabase {
1506
1530
  const payloadKey = `${workflowID}::${key}`;
1507
1531
  const timeoutms = timeoutSeconds !== undefined ? timeoutSeconds * 1000 : undefined;
1508
1532
  let finishTime = timeoutms !== undefined ? Date.now() + timeoutms : undefined;
1533
+ const pollIntervalMs = pollingIntervalMs ?? this.dbPollingIntervalEventMs;
1509
1534
  // Register the key with the global notifications listener first... we do not want to look in the DB first
1510
1535
  // or that would cause a timing hole.
1511
1536
  while (true) {
@@ -1538,14 +1563,14 @@ class SystemDatabase {
1538
1563
  let timeoutPromise = Promise.resolve();
1539
1564
  let timeoutCancel = () => { };
1540
1565
  if (callerWorkflow && timeoutms) {
1541
- const { promise, cancel, endTime } = await this.#durableSleep(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutms, this.dbPollingIntervalEventMs);
1566
+ const { promise, cancel, endTime } = await this.#durableSleep(callerWorkflow.workflowID, callerWorkflow.timeoutFunctionID ?? -1, timeoutms, pollIntervalMs);
1542
1567
  timeoutPromise = promise;
1543
1568
  timeoutCancel = cancel;
1544
1569
  finishTime = endTime;
1545
1570
  }
1546
1571
  else {
1547
- let poll = finishTime ? finishTime - ct : this.dbPollingIntervalEventMs;
1548
- poll = Math.min(this.dbPollingIntervalEventMs, poll);
1572
+ let poll = finishTime ? finishTime - ct : pollIntervalMs;
1573
+ poll = Math.min(pollIntervalMs, poll);
1549
1574
  const { promise, cancel } = (0, utils_1.cancellableSleep)(poll);
1550
1575
  timeoutPromise = promise;
1551
1576
  timeoutCancel = cancel;
@@ -2758,6 +2783,10 @@ class SystemDatabase {
2758
2783
  const param = args.push(where.status);
2759
2784
  whereClause += ` AND status=$${param}`;
2760
2785
  }
2786
+ if (where.notStatus) {
2787
+ const param = args.push(where.notStatus);
2788
+ whereClause += ` AND status!=$${param}`;
2789
+ }
2761
2790
  const result = await client.query(`UPDATE "${this.schemaName}".workflow_status ${setClause} ${whereClause}`, args);
2762
2791
  const throwOnFailure = options.throwOnFailure ?? true;
2763
2792
  if (throwOnFailure && result.rowCount !== 1) {
@@ -3013,13 +3042,13 @@ __decorate([
3013
3042
  __decorate([
3014
3043
  dbRetry(),
3015
3044
  __metadata("design:type", Function),
3016
- __metadata("design:paramtypes", [String, Number, String, Number]),
3045
+ __metadata("design:paramtypes", [String, Number, String, Number, Number]),
3017
3046
  __metadata("design:returntype", Promise)
3018
3047
  ], SystemDatabase.prototype, "awaitWorkflowResult", null);
3019
3048
  __decorate([
3020
3049
  dbRetry(),
3021
3050
  __metadata("design:type", Function),
3022
- __metadata("design:paramtypes", [Array, String]),
3051
+ __metadata("design:paramtypes", [Array, String, Number]),
3023
3052
  __metadata("design:returntype", Promise)
3024
3053
  ], SystemDatabase.prototype, "awaitFirstWorkflowId", null);
3025
3054
  __decorate([
@@ -3043,7 +3072,7 @@ __decorate([
3043
3072
  __decorate([
3044
3073
  dbRetry(),
3045
3074
  __metadata("design:type", Function),
3046
- __metadata("design:paramtypes", [String, Number, Number, String, Number]),
3075
+ __metadata("design:paramtypes", [String, Number, Number, String, Number, Number]),
3047
3076
  __metadata("design:returntype", Promise)
3048
3077
  ], SystemDatabase.prototype, "recv", null);
3049
3078
  __decorate([
@@ -3055,7 +3084,7 @@ __decorate([
3055
3084
  __decorate([
3056
3085
  dbRetry(),
3057
3086
  __metadata("design:type", Function),
3058
- __metadata("design:paramtypes", [String, String, Number, Object]),
3087
+ __metadata("design:paramtypes", [String, String, Number, Object, Number]),
3059
3088
  __metadata("design:returntype", Promise)
3060
3089
  ], SystemDatabase.prototype, "getEvent", null);
3061
3090
  __decorate([