@dbos-inc/dbos-sdk 5.4.9-preview → 5.4.11-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.
Files changed (74) hide show
  1. package/dist/src/cli/cli.js +56 -13
  2. package/dist/src/cli/cli.js.map +1 -1
  3. package/dist/src/client.d.ts +20 -10
  4. package/dist/src/client.d.ts.map +1 -1
  5. package/dist/src/client.js +76 -44
  6. package/dist/src/client.js.map +1 -1
  7. package/dist/src/config.d.ts +1 -0
  8. package/dist/src/config.d.ts.map +1 -1
  9. package/dist/src/config.js +1 -0
  10. package/dist/src/config.js.map +1 -1
  11. package/dist/src/context.d.ts +6 -0
  12. package/dist/src/context.d.ts.map +1 -1
  13. package/dist/src/context.js +15 -1
  14. package/dist/src/context.js.map +1 -1
  15. package/dist/src/datasource.d.ts +2 -0
  16. package/dist/src/datasource.d.ts.map +1 -1
  17. package/dist/src/datasource.js +7 -1
  18. package/dist/src/datasource.js.map +1 -1
  19. package/dist/src/dbos-executor.d.ts +13 -4
  20. package/dist/src/dbos-executor.d.ts.map +1 -1
  21. package/dist/src/dbos-executor.js +64 -31
  22. package/dist/src/dbos-executor.js.map +1 -1
  23. package/dist/src/dbos.d.ts +60 -11
  24. package/dist/src/dbos.d.ts.map +1 -1
  25. package/dist/src/dbos.js +192 -65
  26. package/dist/src/dbos.js.map +1 -1
  27. package/dist/src/enqueue_options.d.ts +2 -0
  28. package/dist/src/enqueue_options.d.ts.map +1 -1
  29. package/dist/src/enqueue_options.js +3 -0
  30. package/dist/src/enqueue_options.js.map +1 -1
  31. package/dist/src/enqueue_workflow.d.ts.map +1 -1
  32. package/dist/src/enqueue_workflow.js +11 -5
  33. package/dist/src/enqueue_workflow.js.map +1 -1
  34. package/dist/src/enterprise.d.ts +23 -3
  35. package/dist/src/enterprise.d.ts.map +1 -1
  36. package/dist/src/enterprise.js +81 -44
  37. package/dist/src/enterprise.js.map +1 -1
  38. package/dist/src/eventreceiver.d.ts +2 -0
  39. package/dist/src/eventreceiver.d.ts.map +1 -1
  40. package/dist/src/eventreceiver.js +7 -1
  41. package/dist/src/eventreceiver.js.map +1 -1
  42. package/dist/src/index.d.ts +2 -1
  43. package/dist/src/index.d.ts.map +1 -1
  44. package/dist/src/index.js.map +1 -1
  45. package/dist/src/scheduler/scheduler.d.ts +5 -0
  46. package/dist/src/scheduler/scheduler.d.ts.map +1 -1
  47. package/dist/src/scheduler/scheduler.js +14 -4
  48. package/dist/src/scheduler/scheduler.js.map +1 -1
  49. package/dist/src/shard_keys.d.ts +13 -0
  50. package/dist/src/shard_keys.d.ts.map +1 -0
  51. package/dist/src/shard_keys.js +55 -0
  52. package/dist/src/shard_keys.js.map +1 -0
  53. package/dist/src/streams.d.ts +2 -0
  54. package/dist/src/streams.d.ts.map +1 -1
  55. package/dist/src/streams.js +5 -2
  56. package/dist/src/streams.js.map +1 -1
  57. package/dist/src/system_database.d.ts +143 -9
  58. package/dist/src/system_database.d.ts.map +1 -1
  59. package/dist/src/system_database.js +1095 -503
  60. package/dist/src/system_database.js.map +1 -1
  61. package/dist/src/wfqueue.d.ts +1 -2
  62. package/dist/src/wfqueue.d.ts.map +1 -1
  63. package/dist/src/wfqueue.js +83 -63
  64. package/dist/src/wfqueue.js.map +1 -1
  65. package/dist/src/workflow.d.ts +12 -2
  66. package/dist/src/workflow.d.ts.map +1 -1
  67. package/dist/src/workflow.js +14 -9
  68. package/dist/src/workflow.js.map +1 -1
  69. package/dist/src/workflow_management.d.ts +5 -3
  70. package/dist/src/workflow_management.d.ts.map +1 -1
  71. package/dist/src/workflow_management.js +45 -14
  72. package/dist/src/workflow_management.js.map +1 -1
  73. package/dist/tsconfig.tsbuildinfo +1 -1
  74. package/package.json +1 -1
package/dist/src/dbos.js CHANGED
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.DBOS = exports.runTransactionalInternalStep = exports.getResultInternal = exports.runInternalStep = exports.getExecutor = exports.resolveDelayEpochMS = exports.resolveStreamTimeoutMS = exports.resolveStreamPollingIntervalMs = exports.resolveStreamOffset = exports.resolvePollingIntervalMs = exports.resolveTimeoutSeconds = void 0;
29
+ exports.DBOS = exports.StepReservation = exports.runTransactionalInternalStep = exports.getResultInternal = exports.runInternalStep = exports.getExecutor = exports.resolveDelayEpochMS = exports.resolveStreamTimeoutMS = exports.resolveStreamPollingIntervalMs = exports.resolveStreamOffset = exports.resolvePollingIntervalMs = exports.resolveTimeoutSeconds = void 0;
30
30
  const context_1 = require("./context");
31
31
  const dbos_executor_1 = require("./dbos-executor");
32
32
  const traces_1 = require("./telemetry/traces");
@@ -48,6 +48,7 @@ const crontab_1 = require("./scheduler/crontab");
48
48
  const wfqueue_1 = require("./wfqueue");
49
49
  const enqueue_workflow_1 = require("./enqueue_workflow");
50
50
  const node_assert_1 = __importDefault(require("node:assert"));
51
+ const shard_keys_1 = require("./shard_keys");
51
52
  function resolveTimeoutSeconds(options) {
52
53
  if (options === undefined)
53
54
  return undefined;
@@ -192,6 +193,46 @@ async function runTransactionalInternalStep(callback, funcName) {
192
193
  return callback(undefined);
193
194
  }
194
195
  exports.runTransactionalInternalStep = runTransactionalInternalStep;
196
+ /**
197
+ * A step ID drawn by `DBOS.reserveStep`, run exactly once later with `run`.
198
+ * It must run in the workflow that reserved it; a reservation that never runs leaves a gap in the step IDs.
199
+ */
200
+ class StepReservation {
201
+ stepID;
202
+ ctx;
203
+ #used = false;
204
+ constructor(
205
+ /** The reserved step ID */
206
+ stepID, ctx) {
207
+ this.stepID = stepID;
208
+ this.ctx = ctx;
209
+ }
210
+ /**
211
+ * Run `func` as a checkpointed step at the reserved step ID
212
+ * @param func - The function to run as a step
213
+ * @param config - Configuration information for the step, particularly the retry policy and name
214
+ * @returns - result (either obtained from invoking function, or retrieved if run before)
215
+ */
216
+ async run(func, config = {}) {
217
+ (0, decorators_1.ensureDBOSIsLaunched)('steps');
218
+ if (this.#used) {
219
+ throw new error_1.DBOSInvalidWorkflowTransitionError('A step reservation can only be run once');
220
+ }
221
+ const ctx = (0, context_1.getCurrentContextStore)();
222
+ if (ctx === undefined ||
223
+ !(0, context_1.isInWorkflowCtx)(ctx) ||
224
+ ctx.workflowId !== this.ctx.workflowId ||
225
+ ctx.ownerXid !== this.ctx.ownerXid) {
226
+ throw new error_1.DBOSInvalidWorkflowTransitionError(`Step ${this.stepID} was reserved in workflow ${this.ctx.workflowId} and must be run from that workflow, outside any step or transaction`);
227
+ }
228
+ const name = config.name ?? func.name;
229
+ (0, step_1.validateStepConfig)(config, name);
230
+ // Claimed before any await, so a concurrent second run is rejected.
231
+ this.#used = true;
232
+ return await dbos_executor_1.DBOSExecutor.globalInstance.callStepFunctionAt(this.stepID, this.ctx, func, name, config, null);
233
+ }
234
+ }
235
+ exports.StepReservation = StepReservation;
195
236
  class DBOS {
196
237
  ///////
197
238
  // Lifecycle
@@ -219,8 +260,29 @@ class DBOS {
219
260
  * @param systemDatabaseUrl - The system database to create or migrate
220
261
  * @param options.schemaName - The schema holding the DBOS system tables (default: `dbos`)
221
262
  * @param options.applicationRole - A role to grant access to the system schema once migrated
263
+ * @param options.sharding - Create or migrate a sharded system database (requires @dbos-inc/dbos-enterprise)
222
264
  */
223
265
  static async migrate(systemDatabaseUrl, options = {}) {
266
+ if (options.sharding) {
267
+ // Sharded migrations come from Transact Enterprise; the grant is the same.
268
+ const sysdb = (0, system_database_1.createSystemDatabase)({
269
+ systemDatabaseUrl,
270
+ logger: new logs_1.GlobalLogger(),
271
+ serializer: serialization_1.DBOSJSON,
272
+ schemaName: options.schemaName ?? 'dbos',
273
+ sharding: true,
274
+ });
275
+ try {
276
+ await sysdb.runMigrations();
277
+ }
278
+ finally {
279
+ await sysdb.destroy();
280
+ }
281
+ if (options.applicationRole) {
282
+ await (0, system_database_1.grantDbosSchemaPermissions)(systemDatabaseUrl, options.applicationRole, new logs_1.GlobalLogger(), options.schemaName ?? 'dbos');
283
+ }
284
+ return;
285
+ }
224
286
  await (0, system_database_1.migrateSystemDatabase)(systemDatabaseUrl, new logs_1.GlobalLogger(), options.schemaName ?? 'dbos', options.applicationRole);
225
287
  }
226
288
  /**
@@ -249,7 +311,11 @@ class DBOS {
249
311
  }
250
312
  (0, decorators_1.finalizeClassRegistrations)();
251
313
  // Every enterprise feature requires Conductor to report the org entitled to it, and the package.
252
- const features = enterprise.enabledFeatures(config);
314
+ let features = enterprise.enabledFeatures(config);
315
+ if (internalConfig.sharding) {
316
+ // Auto-upgrade is disabled on a sharded system database, so it needs no entitlement.
317
+ features = features.filter((f) => f !== enterprise.AUTO_UPGRADE);
318
+ }
253
319
  const entitlementOptions = {
254
320
  enterpriseKey: config.transactEnterpriseKey,
255
321
  conductorKey: options?.conductorKey,
@@ -264,7 +330,7 @@ class DBOS {
264
330
  : undefined;
265
331
  // Workflows can also enroll for auto-upgrade individually, which the config flag's check above does not cover.
266
332
  const autoUpgradableWorkflowNames = (0, auto_upgrade_1.autoUpgradableWorkflows)(internalConfig.autoUpgrade);
267
- if (autoUpgradableWorkflowNames.length > 0 && !internalConfig.autoUpgrade) {
333
+ if (autoUpgradableWorkflowNames.length > 0 && !internalConfig.autoUpgrade && !internalConfig.sharding) {
268
334
  await enterprise.requireEnterprise([enterprise.AUTO_UPGRADE], entitlementOptions);
269
335
  loadedEnterprise ??= enterprise.load(enterprise.AUTO_UPGRADE.description);
270
336
  }
@@ -355,7 +421,11 @@ class DBOS {
355
421
  executor.conductor.start();
356
422
  }
357
423
  // Start the auto-upgrade loop if any workflow takes part.
358
- if (autoUpgradableWorkflowNames.length > 0) {
424
+ if (autoUpgradableWorkflowNames.length > 0 && executor.systemDatabase.sharded) {
425
+ // Its queries aren't keyed by shard, so it doesn't run on a sharded database.
426
+ executor.logger.warn('Auto-upgrade is disabled: it is not supported on a sharded system database.');
427
+ }
428
+ else if (autoUpgradableWorkflowNames.length > 0) {
359
429
  (0, node_assert_1.default)(loadedEnterprise, 'the enterprise package is loaded whenever a workflow enrolls in auto-upgrade');
360
430
  const upgradePass = new loadedEnterprise.AutoUpgradePass(executor, {
361
431
  graceMs: internalConfig.autoUpgradeGraceMs ?? auto_upgrade_1.DEFAULT_AUTO_UPGRADE_GRACE_MS,
@@ -419,7 +489,7 @@ class DBOS {
419
489
  executor.autoUpgrader = undefined;
420
490
  }
421
491
  await executor.awaitRunningWorkflows(drainTimeoutMS);
422
- await executor.stopWorkflowTimeoutSweep();
492
+ await executor.stopBackgroundJobs();
423
493
  }
424
494
  // Stop the conductor
425
495
  if (executor?.conductor) {
@@ -501,6 +571,11 @@ class DBOS {
501
571
  static get workflowID() {
502
572
  return (0, context_1.getCurrentContextStore)()?.workflowId;
503
573
  }
574
+ /** The shard key of the currently executing workflow; undefined outside a workflow or on an unsharded database. */
575
+ static get shardKey() {
576
+ const ctx = (0, context_1.getCurrentContextStore)();
577
+ return ctx?.workflowId !== undefined ? ctx.shardKey : undefined;
578
+ }
504
579
  /** Use portable serialization by default? */
505
580
  static get #defaultSerializationType() {
506
581
  return (0, context_1.getCurrentContextStore)()?.serializationType;
@@ -563,21 +638,23 @@ class DBOS {
563
638
  * @param workflowID - ID of the workflow
564
639
  * @returns status of the workflow as `WorkflowStatus`, or `null` if there is no workflow with `workflowID`
565
640
  */
566
- static getWorkflowStatus(workflowID) {
641
+ static async getWorkflowStatus(workflowID, options) {
567
642
  (0, decorators_1.ensureDBOSIsLaunched)('getWorkflowStatus');
568
- if (DBOS.isWithinWorkflow()) {
569
- if (DBOS.isInStep()) {
570
- // OK to use directly
571
- return DBOS.#executor.getWorkflowStatus(workflowID);
572
- }
573
- else if (DBOS.isInWorkflow()) {
574
- return DBOS.#executor.getWorkflowStatus(workflowID, DBOS.workflowID, (0, context_1.functionIDGetIncrement)());
575
- }
576
- else {
577
- throw new error_1.DBOSInvalidWorkflowTransitionError('Invalid call to `getWorkflowStatus` inside a `transaction`');
643
+ return await (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], () => {
644
+ if (DBOS.isWithinWorkflow()) {
645
+ if (DBOS.isInStep()) {
646
+ // OK to use directly
647
+ return DBOS.#executor.getWorkflowStatus(workflowID);
648
+ }
649
+ else if (DBOS.isInWorkflow()) {
650
+ return DBOS.#executor.getWorkflowStatus(workflowID, DBOS.workflowID, (0, context_1.functionIDGetIncrement)());
651
+ }
652
+ else {
653
+ throw new error_1.DBOSInvalidWorkflowTransitionError('Invalid call to `getWorkflowStatus` inside a `transaction`');
654
+ }
578
655
  }
579
- }
580
- return DBOS.#executor.getWorkflowStatus(workflowID);
656
+ return DBOS.#executor.getWorkflowStatus(workflowID);
657
+ });
581
658
  }
582
659
  /**
583
660
  * Get the workflow result, given a workflow ID
@@ -594,7 +671,8 @@ class DBOS {
594
671
  // Reserve the function ID synchronously, before any await.
595
672
  timerFuncID = (0, context_1.functionIDGetIncrement)();
596
673
  }
597
- return await getResultInternal(workflowID, timeoutSeconds, timerFuncID, undefined, pollingIntervalMs);
674
+ const shardKey = typeof options === 'object' ? options.shardKey : undefined;
675
+ return await (0, shard_keys_1.bindShardKeys)([[workflowID, shardKey]], () => getResultInternal(workflowID, timeoutSeconds, timerFuncID, undefined, pollingIntervalMs));
598
676
  }
599
677
  /**
600
678
  * Wait for any one of the given workflow handles to complete and return it.
@@ -618,9 +696,9 @@ class DBOS {
618
696
  handleMap.set(handle.workflowID, handle);
619
697
  }
620
698
  const workflowIds = [...handleMap.keys()];
621
- const completedId = await runInternalStep(async () => {
699
+ const completedId = await (0, shard_keys_1.bindShardKeys)(handles.map((h) => [h.workflowID, h.shardKey]), () => runInternalStep(async () => {
622
700
  return await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.awaitFirstWorkflowId(workflowIds, DBOS.workflowID, pollingIntervalMs);
623
- }, 'DBOS.waitFirst');
701
+ }, 'DBOS.waitFirst'));
624
702
  return handleMap.get(completedId);
625
703
  }
626
704
  /**
@@ -637,9 +715,9 @@ class DBOS {
637
715
  }
638
716
  const pollingIntervalMs = resolvePollingIntervalMs(options);
639
717
  const workflowIds = [...new Set(handles.map((handle) => handle.workflowID))];
640
- await runInternalStep(async () => {
718
+ await (0, shard_keys_1.bindShardKeys)(handles.map((h) => [h.workflowID, h.shardKey]), () => runInternalStep(async () => {
641
719
  await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.awaitWorkflowIds(workflowIds, DBOS.workflowID, pollingIntervalMs);
642
- }, 'DBOS.waitAll');
720
+ }, 'DBOS.waitAll'));
643
721
  return handles;
644
722
  }
645
723
  /**
@@ -649,15 +727,14 @@ class DBOS {
649
727
  * @param workflowID - ID of the workflow
650
728
  * @returns `WorkflowHandle` that can be used to poll for the status or result of any workflow with `workflowID`
651
729
  */
652
- static retrieveWorkflow(workflowID) {
730
+ static retrieveWorkflow(workflowID, options) {
653
731
  (0, decorators_1.ensureDBOSIsLaunched)('retrieveWorkflow');
654
- if (DBOS.isWithinWorkflow()) {
655
- if (!DBOS.isInWorkflow()) {
656
- throw new error_1.DBOSInvalidWorkflowTransitionError('Invalid call to `retrieveWorkflow` inside a `transaction` or `step`');
657
- }
658
- return new workflow_1.RetrievedHandle(workflowID);
732
+ if (DBOS.isWithinWorkflow() && !DBOS.isInWorkflow()) {
733
+ throw new error_1.DBOSInvalidWorkflowTransitionError('Invalid call to `retrieveWorkflow` inside a `transaction` or `step`');
659
734
  }
660
- return DBOS.#executor.retrieveWorkflow(workflowID);
735
+ // The resolved key, so the handle also works outside this binding.
736
+ const shardKey = (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], () => DBOS.#executor.systemDatabase.keyHint(workflowID));
737
+ return new workflow_1.RetrievedHandle(workflowID, shardKey);
661
738
  }
662
739
  /**
663
740
  * Query the system database for all workflows matching the provided predicate
@@ -687,14 +764,14 @@ class DBOS {
687
764
  * @param workflowID - ID of the workflow
688
765
  * @param priority - Priority value (0 to 2,147,483,647). Lower values are dequeued first.
689
766
  */
690
- static async setWorkflowPriority(workflowID, priority) {
767
+ static async setWorkflowPriority(workflowID, priority, options) {
691
768
  (0, decorators_1.ensureDBOSIsLaunched)('setWorkflowPriority');
692
769
  if (priority < dbos_executor_1.DBOS_QUEUE_MIN_PRIORITY || priority > dbos_executor_1.DBOS_QUEUE_MAX_PRIORITY) {
693
770
  throw new error_1.DBOSInvalidQueuePriorityError(priority, dbos_executor_1.DBOS_QUEUE_MIN_PRIORITY, dbos_executor_1.DBOS_QUEUE_MAX_PRIORITY);
694
771
  }
695
- return runInternalStep(async () => {
772
+ return (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], () => runInternalStep(async () => {
696
773
  return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.setWorkflowPriority(workflowID, priority);
697
- }, 'DBOS.setWorkflowPriority');
774
+ }, 'DBOS.setWorkflowPriority'));
698
775
  }
699
776
  /**
700
777
  * Update the delay on a DELAYED workflow.
@@ -706,9 +783,10 @@ class DBOS {
706
783
  static async setWorkflowDelay(workflowID, options) {
707
784
  (0, decorators_1.ensureDBOSIsLaunched)('setWorkflowDelay');
708
785
  const delayUntilEpochMS = resolveDelayEpochMS(options);
709
- return runInternalStep(async () => {
786
+ const shardKey = typeof options === 'object' ? options.shardKey : undefined;
787
+ return (0, shard_keys_1.bindShardKeys)([[workflowID, shardKey]], () => runInternalStep(async () => {
710
788
  return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.setWorkflowDelay(workflowID, delayUntilEpochMS);
711
- }, 'DBOS.setWorkflowDelay');
789
+ }, 'DBOS.setWorkflowDelay'));
712
790
  }
713
791
  /**
714
792
  * Retrieve the steps of a workflow
@@ -718,9 +796,9 @@ class DBOS {
718
796
  */
719
797
  static async listWorkflowSteps(workflowID, options) {
720
798
  (0, decorators_1.ensureDBOSIsLaunched)('listWorkflowSteps');
721
- return await runInternalStep(async () => {
799
+ return await (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], () => runInternalStep(async () => {
722
800
  return await DBOS.#executor.listWorkflowSteps(workflowID, true, options);
723
- }, 'DBOS.listWorkflowSteps');
801
+ }, 'DBOS.listWorkflowSteps'));
724
802
  }
725
803
  /**
726
804
  * Cancel a workflow given its ID.
@@ -739,9 +817,9 @@ class DBOS {
739
817
  */
740
818
  static async cancelWorkflows(workflowIDs, options) {
741
819
  (0, decorators_1.ensureDBOSIsLaunched)('cancelWorkflows');
742
- return runInternalStep(async () => {
820
+ return (0, shard_keys_1.bindShardKeys)(workflowIDs.map((wid) => [wid, options?.shardKey]), () => runInternalStep(async () => {
743
821
  return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.cancelWorkflows(workflowIDs, options?.cancelChildren);
744
- }, 'DBOS.cancelWorkflow');
822
+ }, 'DBOS.cancelWorkflow'));
745
823
  }
746
824
  /**
747
825
  * Resume a workflow given its ID.
@@ -749,8 +827,11 @@ class DBOS {
749
827
  * @throws DBOSNonExistentWorkflowError if the workflow does not exist
750
828
  */
751
829
  static async resumeWorkflow(workflowID, options) {
752
- const handles = await this.resumeWorkflows([workflowID], options);
753
- return handles[0];
830
+ return await (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], async () => {
831
+ await this.resumeWorkflows([workflowID], options);
832
+ // The resolved key, so the handle also works outside this binding.
833
+ return new workflow_1.RetrievedHandle(workflowID, DBOS.#executor.systemDatabase.keyHint(workflowID));
834
+ });
754
835
  }
755
836
  /**
756
837
  * Resume multiple workflows given their IDs.
@@ -760,10 +841,12 @@ class DBOS {
760
841
  */
761
842
  static async resumeWorkflows(workflowIDs, options) {
762
843
  (0, decorators_1.ensureDBOSIsLaunched)('resumeWorkflows');
763
- await runInternalStep(async () => {
764
- return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.resumeWorkflows(workflowIDs, options?.queueName);
765
- }, 'DBOS.resumeWorkflow');
766
- return workflowIDs.map((wfid) => this.retrieveWorkflow(wfid));
844
+ return await (0, shard_keys_1.bindShardKeys)(workflowIDs.map((wid) => [wid, options?.shardKey]), async () => {
845
+ await runInternalStep(async () => {
846
+ return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.resumeWorkflows(workflowIDs, options?.queueName);
847
+ }, 'DBOS.resumeWorkflow');
848
+ return workflowIDs.map((wfid) => new workflow_1.RetrievedHandle(wfid, options?.shardKey));
849
+ });
767
850
  }
768
851
  /**
769
852
  * Delete a workflow and optionally all its child workflows.
@@ -774,8 +857,8 @@ class DBOS {
774
857
  * @param workflowID - ID of the workflow to delete
775
858
  * @param deleteChildren - If true, also delete all child workflows recursively (default: false)
776
859
  */
777
- static async deleteWorkflow(workflowID, deleteChildren = false) {
778
- return this.deleteWorkflows([workflowID], deleteChildren);
860
+ static async deleteWorkflow(workflowID, deleteChildren = false, options) {
861
+ return this.deleteWorkflows([workflowID], deleteChildren, options);
779
862
  }
780
863
  /**
781
864
  * Delete multiple workflows and optionally all their child workflows.
@@ -785,11 +868,11 @@ class DBOS {
785
868
  * @param workflowIDs - IDs of the workflows to delete
786
869
  * @param deleteChildren - If true, also delete all child workflows recursively (default: false)
787
870
  */
788
- static async deleteWorkflows(workflowIDs, deleteChildren = false) {
871
+ static async deleteWorkflows(workflowIDs, deleteChildren = false, options) {
789
872
  (0, decorators_1.ensureDBOSIsLaunched)('deleteWorkflows');
790
- return runInternalStep(async () => {
873
+ return (0, shard_keys_1.bindShardKeys)(workflowIDs.map((wid) => [wid, options?.shardKey]), () => runInternalStep(async () => {
791
874
  return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.deleteWorkflows(workflowIDs, deleteChildren);
792
- }, 'DBOS.deleteWorkflow');
875
+ }, 'DBOS.deleteWorkflow'));
793
876
  }
794
877
  /**
795
878
  * Fork a workflow given its ID.
@@ -801,10 +884,14 @@ class DBOS {
801
884
  */
802
885
  static async forkWorkflow(workflowID, startStep, options) {
803
886
  (0, decorators_1.ensureDBOSIsLaunched)('forkWorkflow');
804
- const forkedID = await runInternalStep(async () => {
805
- return await DBOS.#executor.forkWorkflow(workflowID, startStep, options);
806
- }, 'DBOS.forkWorkflow');
807
- return this.retrieveWorkflow(forkedID);
887
+ return await (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], async () => {
888
+ const forkedID = await runInternalStep(async () => {
889
+ return await DBOS.#executor.forkWorkflow(workflowID, startStep, options);
890
+ }, 'DBOS.forkWorkflow');
891
+ // A fork lives under its original's shard key, however the caller supplied it.
892
+ const forkKey = DBOS.#executor.systemDatabase.keyHint(workflowID);
893
+ return new workflow_1.RetrievedHandle(forkedID, forkKey);
894
+ });
808
895
  }
809
896
  /**
810
897
  * Rewind a workflow given its ID: drop its recorded history from `startStep` onwards
@@ -828,10 +915,13 @@ class DBOS {
828
915
  static async rewindWorkflow(workflowID, options) {
829
916
  (0, decorators_1.ensureDBOSIsLaunched)('rewindWorkflow');
830
917
  const startStep = options?.startStep ?? 0;
831
- await runInternalStep(async () => {
832
- return await DBOS.#executor.rewindWorkflow(workflowID, startStep, options ?? {});
833
- }, 'DBOS.rewindWorkflow');
834
- return this.retrieveWorkflow(workflowID);
918
+ return await (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], async () => {
919
+ await runInternalStep(async () => {
920
+ return await DBOS.#executor.rewindWorkflow(workflowID, startStep, options ?? {});
921
+ }, 'DBOS.rewindWorkflow');
922
+ // The resolved key, so the handle also works outside this binding.
923
+ return new workflow_1.RetrievedHandle(workflowID, DBOS.#executor.systemDatabase.keyHint(workflowID));
924
+ });
835
925
  }
836
926
  /**
837
927
  * Sleep for the specified amount of time.
@@ -946,6 +1036,13 @@ class DBOS {
946
1036
  (0, decorators_1.ensureDBOSIsLaunched)('workflows');
947
1037
  return DBOS.#withTopContext({ workflowTimeoutMS: timeoutMS }, callback);
948
1038
  }
1039
+ /** Set the shard key of each workflow started, enqueued, or called in `callback`; their children don't inherit it. */
1040
+ static async withShardKey(shardKey, callback) {
1041
+ if (typeof shardKey !== 'string' || shardKey === '') {
1042
+ throw new error_1.DBOSError('shardKey must be a non-empty string');
1043
+ }
1044
+ return DBOS.#withTopContext({ shardKeyForNextWorkflow: shardKey }, callback);
1045
+ }
949
1046
  static async #withTopContext(options, callback) {
950
1047
  const pctx = (0, context_1.getCurrentContextStore)();
951
1048
  if (pctx) {
@@ -1033,16 +1130,16 @@ class DBOS {
1033
1130
  // calls in the same workflow don't race for IDs.
1034
1131
  const functionID = (0, context_1.functionIDGetIncrement)();
1035
1132
  const sermsg = await (0, serialization_1.serializeValue)(message, DBOS.#executor.serializer, options?.serializationType ?? DBOS.#defaultSerializationType);
1036
- return await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.send(DBOS.workflowID, functionID, destinationID, sermsg.serializedValue, topic, sermsg.serialization, idempotencyKey);
1133
+ return await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.send(DBOS.workflowID, functionID, destinationID, sermsg.serializedValue, topic, sermsg.serialization, idempotencyKey, options?.shardKey);
1037
1134
  }
1038
1135
  else if (DBOS.isInStep()) {
1039
1136
  // Inside a step: not recorded, but fenced on the enclosing workflow's ownership.
1040
1137
  const sermsg = await (0, serialization_1.serializeValue)(message, DBOS.#executor.serializer, options?.serializationType ?? DBOS.#defaultSerializationType);
1041
- return await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.sendFromStep(DBOS.workflowID, destinationID, sermsg.serializedValue, topic, sermsg.serialization, idempotencyKey);
1138
+ return await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.sendFromStep(DBOS.workflowID, destinationID, sermsg.serializedValue, topic, sermsg.serialization, idempotencyKey, options?.shardKey);
1042
1139
  }
1043
1140
  else {
1044
1141
  const sermsg = await (0, serialization_1.serializeValue)(message, DBOS.#executor.serializer, options?.serializationType ?? DBOS.#defaultSerializationType);
1045
- return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.sendDirect(destinationID, sermsg.serializedValue, topic, sermsg.serialization, idempotencyKey);
1142
+ return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.sendDirect(destinationID, sermsg.serializedValue, topic, sermsg.serialization, idempotencyKey, undefined, options?.shardKey);
1046
1143
  }
1047
1144
  }
1048
1145
  /**
@@ -1110,6 +1207,10 @@ class DBOS {
1110
1207
  */
1111
1208
  static async getEvent(workflowID, key, options) {
1112
1209
  (0, decorators_1.ensureDBOSIsLaunched)('getEvent');
1210
+ const shardKey = typeof options === 'object' ? options.shardKey : undefined;
1211
+ return await (0, shard_keys_1.bindShardKeys)([[workflowID, shardKey]], () => DBOS.#getEvent(workflowID, key, options));
1212
+ }
1213
+ static async #getEvent(workflowID, key, options) {
1113
1214
  const timeoutSeconds = resolveTimeoutSeconds(options);
1114
1215
  const pollingIntervalMs = resolvePollingIntervalMs(options);
1115
1216
  if (DBOS.isWithinWorkflow()) {
@@ -1187,6 +1288,7 @@ class DBOS {
1187
1288
  timeoutMS: resolveStreamTimeoutMS(options),
1188
1289
  functionName: system_database_1.DBOS_FUNCNAME_READSTREAM,
1189
1290
  checkpoint: true,
1291
+ shardKey: options.shardKey,
1190
1292
  });
1191
1293
  }
1192
1294
  /**
@@ -1210,6 +1312,7 @@ class DBOS {
1210
1312
  timeoutMS: resolveStreamTimeoutMS(options),
1211
1313
  functionName: system_database_1.DBOS_FUNCNAME_READSTREAMOFFSET,
1212
1314
  checkpoint: true,
1315
+ shardKey: options.shardKey,
1213
1316
  });
1214
1317
  }
1215
1318
  //////
@@ -1266,6 +1369,8 @@ class DBOS {
1266
1369
  const ppctx = (0, context_1.getCurrentContextStore)();
1267
1370
  const queueName = params.queueName ?? ppctx?.queueAssignedForWorkflows;
1268
1371
  const timeoutMS = params.timeoutMS ?? ppctx?.workflowTimeoutMS;
1372
+ // Set by DBOS.withShardKey; a workflow never inherits its caller's own key.
1373
+ const shardKey = (0, context_1.nextWorkflowShardKey)(ppctx);
1269
1374
  const instance = $this === undefined || typeof $this === 'function' ? undefined : $this;
1270
1375
  if (instance && !(instance instanceof decorators_1.ConfiguredInstance)) {
1271
1376
  throw new error_1.DBOSInvalidWorkflowTransitionError('Attempt to call a `workflow` function on an object that is not a `ConfiguredInstance`');
@@ -1303,7 +1408,7 @@ class DBOS {
1303
1408
  if (isChild) {
1304
1409
  const pwfid = ppctx.workflowId;
1305
1410
  const wfParams = {
1306
- workflowUUID: wfId || pwfid + '-' + funcId,
1411
+ workflowUUID: wfId || (0, context_1.childWorkflowID)(pwfid, funcId, ppctx?.shardKey, shardKey),
1307
1412
  configuredInstance: instance,
1308
1413
  queueName,
1309
1414
  timeoutMS,
@@ -1315,6 +1420,7 @@ class DBOS {
1315
1420
  workflowAttributes: params.workflowAttributes,
1316
1421
  authenticatedUser: params.authenticatedUser,
1317
1422
  authenticatedRoles: params.authenticatedRoles,
1423
+ shardKey,
1318
1424
  };
1319
1425
  return await invokeRegOp(wfParams, pwfid, funcId);
1320
1426
  }
@@ -1330,6 +1436,7 @@ class DBOS {
1330
1436
  workflowAttributes: params.workflowAttributes,
1331
1437
  authenticatedUser: params.authenticatedUser,
1332
1438
  authenticatedRoles: params.authenticatedRoles,
1439
+ shardKey,
1333
1440
  };
1334
1441
  return await invokeRegOp(wfParams, undefined, undefined);
1335
1442
  }
@@ -1356,6 +1463,8 @@ class DBOS {
1356
1463
  // Reserve the parent's child-funcID once, before any await. Each loop iteration passes it
1357
1464
  // through so the return-existing retry loop consumes a single funcID instead of one per try.
1358
1465
  const childFuncId = DBOS.isInWorkflow() ? (0, context_1.functionIDGetIncrement)() : undefined;
1466
+ // The key the attached-to workflow lives under, as the start would have used.
1467
+ const shardKey = (0, context_1.nextWorkflowShardKey)((0, context_1.getCurrentContextStore)());
1359
1468
  // Resolve the workflow ID once: if the caller supplied one explicitly or via
1360
1469
  // `withNextWorkflowID`, reuse it on every iteration.
1361
1470
  const resolvedWorkflowID = (0, context_1.getNextWFID)(params.workflowID);
@@ -1376,7 +1485,7 @@ class DBOS {
1376
1485
  const now = Date.now();
1377
1486
  await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.recordOperationResult(DBOS.workflowID, childFuncId, regOP.name, true, now, now, { childWorkflowID: existingID });
1378
1487
  }
1379
- return new workflow_1.RetrievedHandle(existingID);
1488
+ return new workflow_1.RetrievedHandle(existingID, shardKey);
1380
1489
  }
1381
1490
  // The prior workflow's deduplication_id was cleared between our INSERT and
1382
1491
  // the lookup (it completed or was cancelled). Loop and try to claim the slot.
@@ -1511,6 +1620,21 @@ class DBOS {
1511
1620
  }
1512
1621
  return func();
1513
1622
  }
1623
+ /**
1624
+ * Draw the next step ID now, so a step can run at it later (for example, from another task) with `run`.
1625
+ * This fixes the step's position in the workflow's history when it would otherwise depend on timing.
1626
+ * Must be called from workflow code, not from a step or transaction.
1627
+ * @returns - a `StepReservation` that runs a step at the reserved ID
1628
+ */
1629
+ static reserveStep() {
1630
+ (0, decorators_1.ensureDBOSIsLaunched)('steps');
1631
+ const ctx = (0, context_1.getCurrentContextStore)();
1632
+ if (ctx === undefined || !(0, context_1.isInWorkflowCtx)(ctx)) {
1633
+ throw new error_1.DBOSInvalidWorkflowTransitionError('DBOS.reserveStep must be called from a workflow, outside any step or transaction');
1634
+ }
1635
+ const stepID = (0, context_1.functionIDGetIncrement)();
1636
+ return new StepReservation(stepID, { ...ctx });
1637
+ }
1514
1638
  /**
1515
1639
  * Register serialization recipe; this is used to save/retrieve objects from the DBOS system
1516
1640
  * database. This includes workflow inputs, function return values, messages, and events.
@@ -1645,6 +1769,7 @@ class DBOS {
1645
1769
  cronTimezone: options.options?.cronTimezone ?? null,
1646
1770
  queueName: options.options?.queueName ?? null,
1647
1771
  applicationName: utils_1.globalParams.appName,
1772
+ shardKey: options.options?.shardKey,
1648
1773
  };
1649
1774
  await runTransactionalInternalStep((client) => dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.createSchedule(schedInternal, client), 'DBOS.createSchedule');
1650
1775
  }
@@ -1730,6 +1855,7 @@ class DBOS {
1730
1855
  automaticBackfill: sched.automaticBackfill ?? false,
1731
1856
  cronTimezone: sched.cronTimezone ?? null,
1732
1857
  queueName: sched.queueName ?? null,
1858
+ shardKey: sched.shardKey,
1733
1859
  });
1734
1860
  }
1735
1861
  await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.applySchedules(internals);
@@ -1741,7 +1867,7 @@ class DBOS {
1741
1867
  }
1742
1868
  const executor = dbos_executor_1.DBOSExecutor.globalInstance;
1743
1869
  const workflowID = await (0, scheduler_1.triggerSchedule)(executor.systemDatabase, executor.serializer, name);
1744
- return new workflow_1.RetrievedHandle(workflowID);
1870
+ return new workflow_1.RetrievedHandle(workflowID, await (0, scheduler_1.scheduleShardKey)(executor.systemDatabase, name));
1745
1871
  }
1746
1872
  static async backfillSchedule(name, start, end) {
1747
1873
  (0, decorators_1.ensureDBOSIsLaunched)('backfillSchedule');
@@ -1750,7 +1876,8 @@ class DBOS {
1750
1876
  }
1751
1877
  const executor = dbos_executor_1.DBOSExecutor.globalInstance;
1752
1878
  const workflowIDs = await (0, scheduler_1.backfillSchedule)(executor.systemDatabase, executor.serializer, name, start, end);
1753
- return workflowIDs.map((id) => new workflow_1.RetrievedHandle(id));
1879
+ const shardKey = await (0, scheduler_1.scheduleShardKey)(executor.systemDatabase, name);
1880
+ return workflowIDs.map((id) => new workflow_1.RetrievedHandle(id, shardKey));
1754
1881
  }
1755
1882
  // ==================== Application Versions ====================
1756
1883
  /**