@dbos-inc/dbos-sdk 1.28.14-preview.g65f6845035 → 1.29.84-preview.gd5bdeb6a8d

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.
Files changed (74) hide show
  1. package/dist/src/context.d.ts +36 -0
  2. package/dist/src/context.d.ts.map +1 -1
  3. package/dist/src/context.js +104 -1
  4. package/dist/src/context.js.map +1 -1
  5. package/dist/src/data_validation.d.ts.map +1 -1
  6. package/dist/src/data_validation.js +6 -2
  7. package/dist/src/data_validation.js.map +1 -1
  8. package/dist/src/dbos-executor.d.ts +36 -20
  9. package/dist/src/dbos-executor.d.ts.map +1 -1
  10. package/dist/src/dbos-executor.js +309 -21
  11. package/dist/src/dbos-executor.js.map +1 -1
  12. package/dist/src/dbos-runtime/runtime.d.ts +3 -8
  13. package/dist/src/dbos-runtime/runtime.d.ts.map +1 -1
  14. package/dist/src/dbos-runtime/runtime.js +7 -11
  15. package/dist/src/dbos-runtime/runtime.js.map +1 -1
  16. package/dist/src/dbos.d.ts +109 -0
  17. package/dist/src/dbos.d.ts.map +1 -0
  18. package/dist/src/dbos.js +551 -0
  19. package/dist/src/dbos.js.map +1 -0
  20. package/dist/src/debugger/debug_workflow.d.ts.map +1 -1
  21. package/dist/src/debugger/debug_workflow.js +5 -1
  22. package/dist/src/debugger/debug_workflow.js.map +1 -1
  23. package/dist/src/decorators.d.ts +11 -2
  24. package/dist/src/decorators.d.ts.map +1 -1
  25. package/dist/src/decorators.js +30 -8
  26. package/dist/src/decorators.js.map +1 -1
  27. package/dist/src/error.d.ts +6 -0
  28. package/dist/src/error.d.ts.map +1 -1
  29. package/dist/src/error.js +20 -2
  30. package/dist/src/error.js.map +1 -1
  31. package/dist/src/eventreceiver.d.ts +6 -2
  32. package/dist/src/eventreceiver.d.ts.map +1 -1
  33. package/dist/src/httpServer/handler.d.ts +0 -1
  34. package/dist/src/httpServer/handler.d.ts.map +1 -1
  35. package/dist/src/httpServer/handler.js +5 -13
  36. package/dist/src/httpServer/handler.js.map +1 -1
  37. package/dist/src/httpServer/middleware.d.ts +13 -2
  38. package/dist/src/httpServer/middleware.d.ts.map +1 -1
  39. package/dist/src/httpServer/middleware.js +101 -1
  40. package/dist/src/httpServer/middleware.js.map +1 -1
  41. package/dist/src/httpServer/server.d.ts +3 -2
  42. package/dist/src/httpServer/server.d.ts.map +1 -1
  43. package/dist/src/httpServer/server.js +40 -30
  44. package/dist/src/httpServer/server.js.map +1 -1
  45. package/dist/src/index.d.ts +2 -1
  46. package/dist/src/index.d.ts.map +1 -1
  47. package/dist/src/index.js +3 -3
  48. package/dist/src/index.js.map +1 -1
  49. package/dist/src/procedure.d.ts +1 -0
  50. package/dist/src/procedure.d.ts.map +1 -1
  51. package/dist/src/procedure.js.map +1 -1
  52. package/dist/src/scheduler/scheduler.d.ts.map +1 -1
  53. package/dist/src/scheduler/scheduler.js +3 -1
  54. package/dist/src/scheduler/scheduler.js.map +1 -1
  55. package/dist/src/system_database.d.ts.map +1 -1
  56. package/dist/src/system_database.js.map +1 -1
  57. package/dist/src/telemetry/logs.d.ts +11 -5
  58. package/dist/src/telemetry/logs.d.ts.map +1 -1
  59. package/dist/src/telemetry/logs.js +8 -11
  60. package/dist/src/telemetry/logs.js.map +1 -1
  61. package/dist/src/testing/testing_runtime.d.ts.map +1 -1
  62. package/dist/src/testing/testing_runtime.js +10 -4
  63. package/dist/src/testing/testing_runtime.js.map +1 -1
  64. package/dist/src/user_database.d.ts +1 -1
  65. package/dist/src/user_database.d.ts.map +1 -1
  66. package/dist/src/wfqueue.d.ts.map +1 -1
  67. package/dist/src/wfqueue.js +17 -2
  68. package/dist/src/wfqueue.js.map +1 -1
  69. package/dist/src/workflow.d.ts +7 -6
  70. package/dist/src/workflow.d.ts.map +1 -1
  71. package/dist/src/workflow.js +10 -187
  72. package/dist/src/workflow.js.map +1 -1
  73. package/dist/tsconfig.build.tsbuildinfo +1 -1
  74. package/package.json +5 -1
@@ -4,10 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DBOSExecutor = exports.OperationType = exports.dbosNull = void 0;
7
- /* eslint-disable @typescript-eslint/no-explicit-any */
8
7
  const error_1 = require("./error");
9
8
  const workflow_1 = require("./workflow");
10
9
  const transaction_1 = require("./transaction");
10
+ const step_1 = require("./step");
11
11
  const collector_1 = require("./telemetry/collector");
12
12
  const traces_1 = require("./telemetry/traces");
13
13
  const logs_1 = require("./telemetry/logs");
@@ -84,7 +84,9 @@ class DBOSExecutor {
84
84
  typeormEntities = [];
85
85
  drizzleEntities = {};
86
86
  eventReceivers = [];
87
- scheduler = null;
87
+ scheduler = undefined;
88
+ wfqEnded = undefined;
89
+ static globalInstance = undefined;
88
90
  /* WORKFLOW EXECUTOR LIFE CYCLE MANAGEMENT */
89
91
  constructor(config, systemDatabase) {
90
92
  this.config = config;
@@ -143,6 +145,7 @@ class DBOSExecutor {
143
145
  }, this.flushBufferIntervalMs);
144
146
  this.logger.debug("Started workflow status buffer worker");
145
147
  this.initialized = false;
148
+ DBOSExecutor.globalInstance = this;
146
149
  }
147
150
  configureDbClient() {
148
151
  const userDbClient = this.config.userDbclient;
@@ -387,6 +390,9 @@ class DBOSExecutor {
387
390
  }
388
391
  await this.procedurePool.end();
389
392
  await this.logger.destroy();
393
+ if (DBOSExecutor.globalInstance === this) {
394
+ DBOSExecutor.globalInstance = undefined;
395
+ }
390
396
  }
391
397
  /* WORKFLOW OPERATIONS */
392
398
  #registerWorkflow(ro) {
@@ -401,6 +407,7 @@ class DBOSExecutor {
401
407
  const workflowInfo = {
402
408
  workflow: wf,
403
409
  config: { ...ro.workflowConfig },
410
+ registration: ro,
404
411
  };
405
412
  this.workflowInfoMap.set(wfn, workflowInfo);
406
413
  this.logger.debug(`Registered workflow ${wfn}`);
@@ -414,6 +421,7 @@ class DBOSExecutor {
414
421
  const txnInfo = {
415
422
  transaction: txf,
416
423
  config: { ...ro.txnConfig },
424
+ registration: ro,
417
425
  };
418
426
  this.transactionInfoMap.set(tfn, txnInfo);
419
427
  this.logger.debug(`Registered transaction ${tfn}`);
@@ -424,11 +432,12 @@ class DBOSExecutor {
424
432
  if (this.stepInfoMap.has(cfn)) {
425
433
  throw new error_1.DBOSError(`Repeated Commmunicator name: ${cfn}`);
426
434
  }
427
- const commInfo = {
435
+ const stepInfo = {
428
436
  step: comm,
429
437
  config: { ...ro.commConfig },
438
+ registration: ro,
430
439
  };
431
- this.stepInfoMap.set(cfn, commInfo);
440
+ this.stepInfoMap.set(cfn, stepInfo);
432
441
  this.logger.debug(`Registered step ${cfn}`);
433
442
  }
434
443
  #registerProcedure(ro) {
@@ -440,6 +449,7 @@ class DBOSExecutor {
440
449
  const procInfo = {
441
450
  procedure: proc,
442
451
  config: { ...ro.procConfig },
452
+ registration: ro,
443
453
  };
444
454
  this.procedureInfoMap.set(cfn, procInfo);
445
455
  this.logger.debug(`Registered stored proc ${cfn}`);
@@ -532,6 +542,28 @@ class DBOSExecutor {
532
542
  throw new error_1.DBOSNotRegisteredError(wf.name);
533
543
  }
534
544
  const wConfig = wInfo.config;
545
+ // Compatibility with the old way of calling workflows, which would include a parentCtx
546
+ const pctx = (0, context_1.getCurrentContextStore)();
547
+ const passContext = wInfo.registration?.passContext ?? true;
548
+ if (!passContext && pctx) {
549
+ const span = this.tracer.startSpan(wf.name, {
550
+ operationUUID: workflowUUID,
551
+ operationType: exports.OperationType.WORKFLOW,
552
+ status: workflow_1.StatusString.PENDING,
553
+ authenticatedUser: pctx.authenticatedUser,
554
+ assumedRole: pctx.assumedRole,
555
+ authenticatedRoles: pctx.authenticatedRoles,
556
+ }
557
+ // TODO the span should be taken from pctx
558
+ //pctx.span
559
+ );
560
+ params.parentCtx = new context_1.DBOSContextImpl(wf.name, span, this.logger);
561
+ params.parentCtx.request = pctx.request || {};
562
+ params.parentCtx.authenticatedUser = pctx.authenticatedUser || "";
563
+ params.parentCtx.assumedRole = pctx.assumedRole || "";
564
+ params.parentCtx.authenticatedRoles = pctx.authenticatedRoles || [];
565
+ params.parentCtx.workflowUUID = workflowUUID;
566
+ }
535
567
  const wCtxt = new workflow_1.WorkflowContextImpl(this, params.parentCtx, workflowUUID, wConfig, wf.name, presetUUID, params.tempWfType, params.tempWfName);
536
568
  const internalStatus = {
537
569
  workflowUUID: workflowUUID,
@@ -570,7 +602,16 @@ class DBOSExecutor {
570
602
  let result;
571
603
  // Execute the workflow.
572
604
  try {
573
- result = await wf.call(params.configuredInstance, wCtxt, ...args);
605
+ let cresult;
606
+ await (0, context_1.runWithWorkflowContext)(wCtxt, async () => {
607
+ if (passContext) {
608
+ cresult = await wf.call(params.configuredInstance, wCtxt, ...args);
609
+ }
610
+ else {
611
+ cresult = await wf.call(params.configuredInstance, ...args);
612
+ }
613
+ });
614
+ result = cresult;
574
615
  internalStatus.output = result;
575
616
  internalStatus.status = workflow_1.StatusString.SUCCESS;
576
617
  if (internalStatus.queueName) {
@@ -675,17 +716,19 @@ class DBOSExecutor {
675
716
  if (utils_1.DBOSJSON.stringify(args) !== utils_1.DBOSJSON.stringify(recordedInputs)) {
676
717
  throw new error_1.DBOSDebuggerError(`Detect different input for the workflow UUID ${workflowUUID}!\n Received: ${utils_1.DBOSJSON.stringify(args)}\n Original: ${utils_1.DBOSJSON.stringify(recordedInputs)}`);
677
718
  }
678
- const workflowPromise = wf.call(params.configuredInstance, wCtxt, ...args)
679
- .then(async (result) => {
680
- // Check if the result is the same.
681
- const recordedResult = await this.systemDatabase.getWorkflowResult(workflowUUID);
682
- if (result === undefined && !recordedResult) {
683
- return result;
684
- }
685
- if (utils_1.DBOSJSON.stringify(result) !== utils_1.DBOSJSON.stringify(recordedResult)) {
686
- this.logger.error(`Detect different output for the workflow UUID ${workflowUUID}!\n Received: ${utils_1.DBOSJSON.stringify(result)}\n Original: ${utils_1.DBOSJSON.stringify(recordedResult)}`);
687
- }
688
- return recordedResult; // Always return the recorded result.
719
+ const workflowPromise = (0, context_1.runWithWorkflowContext)(wCtxt, async () => {
720
+ return await wf.call(params.configuredInstance, wCtxt, ...args)
721
+ .then(async (result) => {
722
+ // Check if the result is the same.
723
+ const recordedResult = await this.systemDatabase.getWorkflowResult(workflowUUID);
724
+ if (result === undefined && !recordedResult) {
725
+ return result;
726
+ }
727
+ if (utils_1.DBOSJSON.stringify(result) !== utils_1.DBOSJSON.stringify(recordedResult)) {
728
+ this.logger.error(`Detect different output for the workflow UUID ${workflowUUID}!\n Received: ${utils_1.DBOSJSON.stringify(result)}\n Original: ${utils_1.DBOSJSON.stringify(recordedResult)}`);
729
+ }
730
+ return recordedResult; // Always return the recorded result.
731
+ });
689
732
  });
690
733
  return new workflow_1.InvokedHandle(this.systemDatabase, workflowPromise, workflowUUID, wf.name, callerUUID, callerFunctionID);
691
734
  }
@@ -702,6 +745,122 @@ class DBOSExecutor {
702
745
  tempWfClass: (0, decorators_1.getRegisteredMethodClassName)(txn),
703
746
  }, ...args)).getResult();
704
747
  }
748
+ async callTransactionFunction(txn, clsinst, wfCtx, ...args) {
749
+ const txnInfo = this.getTransactionInfo(txn);
750
+ if (txnInfo === undefined) {
751
+ throw new error_1.DBOSNotRegisteredError(txn.name);
752
+ }
753
+ const readOnly = txnInfo.config.readOnly ?? false;
754
+ let retryWaitMillis = 1;
755
+ const backoffFactor = 1.5;
756
+ const maxRetryWaitMs = 2000; // Maximum wait 2 seconds.
757
+ const funcId = wfCtx.functionIDGetIncrement();
758
+ const span = this.tracer.startSpan(txn.name, {
759
+ operationUUID: wfCtx.workflowUUID,
760
+ operationType: exports.OperationType.TRANSACTION,
761
+ authenticatedUser: wfCtx.authenticatedUser,
762
+ assumedRole: wfCtx.assumedRole,
763
+ authenticatedRoles: wfCtx.authenticatedRoles,
764
+ readOnly: readOnly,
765
+ isolationLevel: txnInfo.config.isolationLevel,
766
+ }, wfCtx.span);
767
+ while (true) {
768
+ let txn_snapshot = "invalid";
769
+ const workflowUUID = wfCtx.workflowUUID;
770
+ const wrappedTransaction = async (client) => {
771
+ const tCtxt = new transaction_1.TransactionContextImpl(this.userDatabase.getName(), client, wfCtx, span, this.logger, funcId, txn.name);
772
+ // If the UUID is preset, it is possible this execution previously happened. Check, and return its original result if it did.
773
+ // Note: It is possible to retrieve a generated ID from a workflow handle, run a concurrent execution, and cause trouble for yourself. We recommend against this.
774
+ if (wfCtx.presetUUID) {
775
+ const check = await wfCtx.checkTxExecution(client, funcId);
776
+ txn_snapshot = check.txn_snapshot;
777
+ if (check.output !== exports.dbosNull) {
778
+ tCtxt.span.setAttribute("cached", true);
779
+ tCtxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
780
+ this.tracer.endSpan(tCtxt.span);
781
+ return check.output;
782
+ }
783
+ }
784
+ else {
785
+ // Collect snapshot information for read-only transactions and non-preset UUID transactions, if not already collected above
786
+ txn_snapshot = await wfCtx.retrieveTxSnapshot(client);
787
+ }
788
+ // For non-read-only transactions, flush the result buffer.
789
+ if (!readOnly) {
790
+ await wfCtx.flushResultBuffer(client);
791
+ }
792
+ // Execute the user's transaction.
793
+ let cresult;
794
+ if (txnInfo.registration.passContext) {
795
+ await (0, context_1.runWithTransactionContext)(tCtxt, async () => {
796
+ cresult = await txn.call(clsinst, tCtxt, ...args);
797
+ });
798
+ }
799
+ else {
800
+ await (0, context_1.runWithTransactionContext)(tCtxt, async () => {
801
+ const tf = txn;
802
+ cresult = await tf.call(clsinst, ...args);
803
+ });
804
+ }
805
+ const result = cresult;
806
+ // Record the execution, commit, and return.
807
+ if (readOnly) {
808
+ // Buffer the output of read-only transactions instead of synchronously writing it.
809
+ const readOutput = {
810
+ output: result,
811
+ txn_snapshot: txn_snapshot,
812
+ created_at: Date.now(),
813
+ };
814
+ wfCtx.resultBuffer.set(funcId, readOutput);
815
+ }
816
+ else {
817
+ try {
818
+ // Synchronously record the output of write transactions and obtain the transaction ID.
819
+ const pg_txn_id = await wfCtx.recordOutputTx(client, funcId, txn_snapshot, result);
820
+ tCtxt.span.setAttribute("pg_txn_id", pg_txn_id);
821
+ wfCtx.resultBuffer.clear();
822
+ }
823
+ catch (error) {
824
+ if (this.userDatabase.isFailedSqlTransactionError(error)) {
825
+ this.logger.error(`Postgres aborted the ${txn.name} @Transaction of Workflow ${workflowUUID}, but the function did not raise an exception. Please ensure that the @Transaction method raises an exception if the database transaction is aborted.`);
826
+ throw new error_1.DBOSFailedSqlTransactionError(workflowUUID, txn.name);
827
+ }
828
+ else {
829
+ throw error;
830
+ }
831
+ }
832
+ }
833
+ return result;
834
+ };
835
+ try {
836
+ const result = await this.userDatabase.transaction(wrappedTransaction, txnInfo.config);
837
+ span.setStatus({ code: api_1.SpanStatusCode.OK });
838
+ this.tracer.endSpan(span);
839
+ return result;
840
+ }
841
+ catch (err) {
842
+ if (this.userDatabase.isRetriableTransactionError(err)) {
843
+ // serialization_failure in PostgreSQL
844
+ span.addEvent("TXN SERIALIZATION FAILURE", { "retryWaitMillis": retryWaitMillis }, performance.now());
845
+ // Retry serialization failures.
846
+ await (0, utils_1.sleepms)(retryWaitMillis);
847
+ retryWaitMillis *= backoffFactor;
848
+ retryWaitMillis = retryWaitMillis < maxRetryWaitMs ? retryWaitMillis : maxRetryWaitMs;
849
+ continue;
850
+ }
851
+ // Record and throw other errors.
852
+ const e = err;
853
+ await this.userDatabase.transaction(async (client) => {
854
+ await wfCtx.flushResultBuffer(client);
855
+ await wfCtx.recordErrorTx(client, funcId, txn_snapshot, e);
856
+ }, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
857
+ wfCtx.resultBuffer.clear();
858
+ span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: e.message });
859
+ this.tracer.endSpan(span);
860
+ throw err;
861
+ }
862
+ }
863
+ }
705
864
  async procedure(proc, params, ...args) {
706
865
  // Create a workflow and call procedure.
707
866
  const temp_workflow = async (ctxt, ...args) => {
@@ -748,13 +907,128 @@ class DBOSExecutor {
748
907
  tempWfClass: (0, decorators_1.getRegisteredMethodClassName)(stepFn),
749
908
  }, ...args)).getResult();
750
909
  }
910
+ /**
911
+ * Execute a step function.
912
+ * If it encounters any error, retry according to its configured retry policy until the maximum number of attempts is reached, then throw an DBOSError.
913
+ * The step may execute many times, but once it is complete, it will not re-execute.
914
+ */
915
+ async callStepFunction(stepFn, clsInst, wfCtx, ...args) {
916
+ const commInfo = this.getStepInfo(stepFn);
917
+ if (commInfo === undefined) {
918
+ throw new error_1.DBOSNotRegisteredError(stepFn.name);
919
+ }
920
+ const funcID = wfCtx.functionIDGetIncrement();
921
+ const maxRetryIntervalSec = 3600; // Maximum retry interval: 1 hour
922
+ const span = this.tracer.startSpan(stepFn.name, {
923
+ operationUUID: wfCtx.workflowUUID,
924
+ operationType: exports.OperationType.COMMUNICATOR,
925
+ authenticatedUser: wfCtx.authenticatedUser,
926
+ assumedRole: wfCtx.assumedRole,
927
+ authenticatedRoles: wfCtx.authenticatedRoles,
928
+ retriesAllowed: commInfo.config.retriesAllowed,
929
+ intervalSeconds: commInfo.config.intervalSeconds,
930
+ maxAttempts: commInfo.config.maxAttempts,
931
+ backoffRate: commInfo.config.backoffRate,
932
+ }, wfCtx.span);
933
+ const ctxt = new step_1.StepContextImpl(wfCtx, funcID, span, this.logger, commInfo.config, stepFn.name);
934
+ await this.userDatabase.transaction(async (client) => {
935
+ await wfCtx.flushResultBuffer(client);
936
+ }, { isolationLevel: transaction_1.IsolationLevel.ReadCommitted });
937
+ wfCtx.resultBuffer.clear();
938
+ // Check if this execution previously happened, returning its original result if it did.
939
+ const check = await this.systemDatabase.checkOperationOutput(wfCtx.workflowUUID, ctxt.functionID);
940
+ if (check !== exports.dbosNull) {
941
+ ctxt.span.setAttribute("cached", true);
942
+ ctxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
943
+ this.tracer.endSpan(ctxt.span);
944
+ return check;
945
+ }
946
+ // Execute the step function. If it throws an exception, retry with exponential backoff.
947
+ // After reaching the maximum number of retries, throw an DBOSError.
948
+ let result = exports.dbosNull;
949
+ let err = exports.dbosNull;
950
+ if (ctxt.retriesAllowed) {
951
+ let numAttempts = 0;
952
+ let intervalSeconds = ctxt.intervalSeconds;
953
+ if (intervalSeconds > maxRetryIntervalSec) {
954
+ this.logger.warn(`Step config interval exceeds maximum allowed interval, capped to ${maxRetryIntervalSec} seconds!`);
955
+ }
956
+ while (result === exports.dbosNull && numAttempts++ < ctxt.maxAttempts) {
957
+ try {
958
+ let cresult;
959
+ if (commInfo.registration.passContext) {
960
+ await (0, context_1.runWithStepContext)(ctxt, async () => {
961
+ cresult = await stepFn.call(clsInst, ctxt, ...args);
962
+ });
963
+ }
964
+ else {
965
+ await (0, context_1.runWithStepContext)(ctxt, async () => {
966
+ const sf = stepFn;
967
+ cresult = await sf.call(clsInst, ...args);
968
+ });
969
+ }
970
+ result = cresult;
971
+ }
972
+ catch (error) {
973
+ const e = error;
974
+ this.logger.warn(`Error in step being automatically retried. Attempt ${numAttempts} of ${ctxt.maxAttempts}. ${e.stack}`);
975
+ span.addEvent(`Step attempt ${numAttempts + 1} failed`, { "retryIntervalSeconds": intervalSeconds, "error": error.message }, performance.now());
976
+ if (numAttempts < ctxt.maxAttempts) {
977
+ // Sleep for an interval, then increase the interval by backoffRate.
978
+ // Cap at the maximum allowed retry interval.
979
+ await (0, utils_1.sleepms)(intervalSeconds * 1000);
980
+ intervalSeconds *= ctxt.backoffRate;
981
+ intervalSeconds = intervalSeconds < maxRetryIntervalSec ? intervalSeconds : maxRetryIntervalSec;
982
+ }
983
+ }
984
+ }
985
+ }
986
+ else {
987
+ try {
988
+ let cresult;
989
+ if (commInfo.registration.passContext) {
990
+ await (0, context_1.runWithStepContext)(ctxt, async () => {
991
+ cresult = await stepFn.call(clsInst, ctxt, ...args);
992
+ });
993
+ }
994
+ else {
995
+ await (0, context_1.runWithStepContext)(ctxt, async () => {
996
+ const sf = stepFn;
997
+ cresult = await sf.call(clsInst, ...args);
998
+ });
999
+ }
1000
+ result = cresult;
1001
+ }
1002
+ catch (error) {
1003
+ err = error;
1004
+ }
1005
+ }
1006
+ // `result` can only be dbosNull when the step timed out
1007
+ if (result === exports.dbosNull) {
1008
+ // Record the error, then throw it.
1009
+ err = err === exports.dbosNull ? new error_1.DBOSError("Step reached maximum retries.", 1) : err;
1010
+ await this.systemDatabase.recordOperationError(wfCtx.workflowUUID, ctxt.functionID, err);
1011
+ ctxt.span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: err.message });
1012
+ this.tracer.endSpan(ctxt.span);
1013
+ throw err;
1014
+ }
1015
+ else {
1016
+ // Record the execution and return.
1017
+ await this.systemDatabase.recordOperationOutput(wfCtx.workflowUUID, ctxt.functionID, result);
1018
+ ctxt.span.setStatus({ code: api_1.SpanStatusCode.OK });
1019
+ this.tracer.endSpan(ctxt.span);
1020
+ return result;
1021
+ }
1022
+ }
751
1023
  async send(destinationUUID, message, topic, idempotencyKey) {
752
1024
  // Create a workflow and call send.
753
1025
  const temp_workflow = async (ctxt, destinationUUID, message, topic) => {
754
1026
  return await ctxt.send(destinationUUID, message, topic);
755
1027
  };
756
1028
  const workflowUUID = idempotencyKey ? destinationUUID + idempotencyKey : undefined;
757
- return (await this.workflow(temp_workflow, { workflowUUID: workflowUUID, tempWfType: TempWorkflowType.send, configuredInstance: null }, destinationUUID, message, topic)).getResult();
1029
+ return (await this.workflow(temp_workflow, {
1030
+ workflowUUID: workflowUUID, tempWfType: TempWorkflowType.send, configuredInstance: null,
1031
+ }, destinationUUID, message, topic)).getResult();
758
1032
  }
759
1033
  /**
760
1034
  * Wait for a workflow to emit an event, then return its value.
@@ -765,8 +1039,17 @@ class DBOSExecutor {
765
1039
  /**
766
1040
  * Retrieve a handle for a workflow UUID.
767
1041
  */
768
- retrieveWorkflow(workflowUUID) {
769
- return new workflow_1.RetrievedHandle(this.systemDatabase, workflowUUID);
1042
+ retrieveWorkflow(workflowID) {
1043
+ return new workflow_1.RetrievedHandle(this.systemDatabase, workflowID);
1044
+ }
1045
+ getWorkflowStatus(workflowID) {
1046
+ return this.systemDatabase.getWorkflowStatus(workflowID);
1047
+ }
1048
+ getWorkflows(input) {
1049
+ return this.systemDatabase.getWorkflows(input);
1050
+ }
1051
+ getWorkflowQueue(input) {
1052
+ return this.systemDatabase.getWorkflowQueue(input);
770
1053
  }
771
1054
  async queryUserDB(sql, params) {
772
1055
  if (params !== undefined) {
@@ -833,6 +1116,7 @@ class DBOSExecutor {
833
1116
  }
834
1117
  await this.scheduler?.destroyScheduler();
835
1118
  wfqueue_1.wfQueueRunner.stop();
1119
+ await this.wfqEnded;
836
1120
  }
837
1121
  async executeWorkflowUUID(workflowUUID, startNewWorkflow = false) {
838
1122
  const wfStatus = await this.systemDatabase.getWorkflowStatus(workflowUUID);
@@ -848,7 +1132,7 @@ class DBOSExecutor {
848
1132
  if (wfInfo) {
849
1133
  return this.workflow(wfInfo.workflow, {
850
1134
  workflowUUID: workflowStartUUID, parentCtx: parentCtx, configuredInstance: configuredInst, recovery: true,
851
- queueName: wfStatus.queueName, executeWorkflow: true
1135
+ queueName: wfStatus.queueName, executeWorkflow: true,
852
1136
  },
853
1137
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
854
1138
  ...inputs);
@@ -906,8 +1190,12 @@ class DBOSExecutor {
906
1190
  this.logger.error(`Unrecognized temporary workflow! UUID ${workflowUUID}, name ${wfName}`);
907
1191
  throw new error_1.DBOSNotRegisteredError(wfName);
908
1192
  }
1193
+ return this.workflow(temp_workflow, {
1194
+ workflowUUID: workflowStartUUID, parentCtx: parentCtx ?? undefined, configuredInstance: clsinst,
1195
+ recovery: true, tempWfType, tempWfClass, tempWfName,
1196
+ },
909
1197
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
910
- return this.workflow(temp_workflow, { workflowUUID: workflowStartUUID, parentCtx: parentCtx ?? undefined, configuredInstance: clsinst, recovery: true, tempWfType, tempWfClass, tempWfName }, ...inputs);
1198
+ ...inputs);
911
1199
  }
912
1200
  async getEventDispatchState(svc, wfn, key) {
913
1201
  return await this.systemDatabase.getEventDispatchState(svc, wfn, key);