@dbos-inc/dbos-sdk 5.4.10-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 +7 -4
  20. package/dist/src/dbos-executor.d.ts.map +1 -1
  21. package/dist/src/dbos-executor.js +56 -30
  22. package/dist/src/dbos-executor.js.map +1 -1
  23. package/dist/src/dbos.d.ts +30 -10
  24. package/dist/src/dbos.d.ts.map +1 -1
  25. package/dist/src/dbos.js +136 -64
  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 +1 -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
@@ -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;
@@ -259,8 +260,29 @@ class DBOS {
259
260
  * @param systemDatabaseUrl - The system database to create or migrate
260
261
  * @param options.schemaName - The schema holding the DBOS system tables (default: `dbos`)
261
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)
262
264
  */
263
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
+ }
264
286
  await (0, system_database_1.migrateSystemDatabase)(systemDatabaseUrl, new logs_1.GlobalLogger(), options.schemaName ?? 'dbos', options.applicationRole);
265
287
  }
266
288
  /**
@@ -289,7 +311,11 @@ class DBOS {
289
311
  }
290
312
  (0, decorators_1.finalizeClassRegistrations)();
291
313
  // Every enterprise feature requires Conductor to report the org entitled to it, and the package.
292
- 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
+ }
293
319
  const entitlementOptions = {
294
320
  enterpriseKey: config.transactEnterpriseKey,
295
321
  conductorKey: options?.conductorKey,
@@ -304,7 +330,7 @@ class DBOS {
304
330
  : undefined;
305
331
  // Workflows can also enroll for auto-upgrade individually, which the config flag's check above does not cover.
306
332
  const autoUpgradableWorkflowNames = (0, auto_upgrade_1.autoUpgradableWorkflows)(internalConfig.autoUpgrade);
307
- if (autoUpgradableWorkflowNames.length > 0 && !internalConfig.autoUpgrade) {
333
+ if (autoUpgradableWorkflowNames.length > 0 && !internalConfig.autoUpgrade && !internalConfig.sharding) {
308
334
  await enterprise.requireEnterprise([enterprise.AUTO_UPGRADE], entitlementOptions);
309
335
  loadedEnterprise ??= enterprise.load(enterprise.AUTO_UPGRADE.description);
310
336
  }
@@ -395,7 +421,11 @@ class DBOS {
395
421
  executor.conductor.start();
396
422
  }
397
423
  // Start the auto-upgrade loop if any workflow takes part.
398
- 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) {
399
429
  (0, node_assert_1.default)(loadedEnterprise, 'the enterprise package is loaded whenever a workflow enrolls in auto-upgrade');
400
430
  const upgradePass = new loadedEnterprise.AutoUpgradePass(executor, {
401
431
  graceMs: internalConfig.autoUpgradeGraceMs ?? auto_upgrade_1.DEFAULT_AUTO_UPGRADE_GRACE_MS,
@@ -459,7 +489,7 @@ class DBOS {
459
489
  executor.autoUpgrader = undefined;
460
490
  }
461
491
  await executor.awaitRunningWorkflows(drainTimeoutMS);
462
- await executor.stopWorkflowTimeoutSweep();
492
+ await executor.stopBackgroundJobs();
463
493
  }
464
494
  // Stop the conductor
465
495
  if (executor?.conductor) {
@@ -541,6 +571,11 @@ class DBOS {
541
571
  static get workflowID() {
542
572
  return (0, context_1.getCurrentContextStore)()?.workflowId;
543
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
+ }
544
579
  /** Use portable serialization by default? */
545
580
  static get #defaultSerializationType() {
546
581
  return (0, context_1.getCurrentContextStore)()?.serializationType;
@@ -603,21 +638,23 @@ class DBOS {
603
638
  * @param workflowID - ID of the workflow
604
639
  * @returns status of the workflow as `WorkflowStatus`, or `null` if there is no workflow with `workflowID`
605
640
  */
606
- static getWorkflowStatus(workflowID) {
641
+ static async getWorkflowStatus(workflowID, options) {
607
642
  (0, decorators_1.ensureDBOSIsLaunched)('getWorkflowStatus');
608
- if (DBOS.isWithinWorkflow()) {
609
- if (DBOS.isInStep()) {
610
- // OK to use directly
611
- return DBOS.#executor.getWorkflowStatus(workflowID);
612
- }
613
- else if (DBOS.isInWorkflow()) {
614
- return DBOS.#executor.getWorkflowStatus(workflowID, DBOS.workflowID, (0, context_1.functionIDGetIncrement)());
615
- }
616
- else {
617
- 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
+ }
618
655
  }
619
- }
620
- return DBOS.#executor.getWorkflowStatus(workflowID);
656
+ return DBOS.#executor.getWorkflowStatus(workflowID);
657
+ });
621
658
  }
622
659
  /**
623
660
  * Get the workflow result, given a workflow ID
@@ -634,7 +671,8 @@ class DBOS {
634
671
  // Reserve the function ID synchronously, before any await.
635
672
  timerFuncID = (0, context_1.functionIDGetIncrement)();
636
673
  }
637
- 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));
638
676
  }
639
677
  /**
640
678
  * Wait for any one of the given workflow handles to complete and return it.
@@ -658,9 +696,9 @@ class DBOS {
658
696
  handleMap.set(handle.workflowID, handle);
659
697
  }
660
698
  const workflowIds = [...handleMap.keys()];
661
- const completedId = await runInternalStep(async () => {
699
+ const completedId = await (0, shard_keys_1.bindShardKeys)(handles.map((h) => [h.workflowID, h.shardKey]), () => runInternalStep(async () => {
662
700
  return await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.awaitFirstWorkflowId(workflowIds, DBOS.workflowID, pollingIntervalMs);
663
- }, 'DBOS.waitFirst');
701
+ }, 'DBOS.waitFirst'));
664
702
  return handleMap.get(completedId);
665
703
  }
666
704
  /**
@@ -677,9 +715,9 @@ class DBOS {
677
715
  }
678
716
  const pollingIntervalMs = resolvePollingIntervalMs(options);
679
717
  const workflowIds = [...new Set(handles.map((handle) => handle.workflowID))];
680
- await runInternalStep(async () => {
718
+ await (0, shard_keys_1.bindShardKeys)(handles.map((h) => [h.workflowID, h.shardKey]), () => runInternalStep(async () => {
681
719
  await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.awaitWorkflowIds(workflowIds, DBOS.workflowID, pollingIntervalMs);
682
- }, 'DBOS.waitAll');
720
+ }, 'DBOS.waitAll'));
683
721
  return handles;
684
722
  }
685
723
  /**
@@ -689,15 +727,14 @@ class DBOS {
689
727
  * @param workflowID - ID of the workflow
690
728
  * @returns `WorkflowHandle` that can be used to poll for the status or result of any workflow with `workflowID`
691
729
  */
692
- static retrieveWorkflow(workflowID) {
730
+ static retrieveWorkflow(workflowID, options) {
693
731
  (0, decorators_1.ensureDBOSIsLaunched)('retrieveWorkflow');
694
- if (DBOS.isWithinWorkflow()) {
695
- if (!DBOS.isInWorkflow()) {
696
- throw new error_1.DBOSInvalidWorkflowTransitionError('Invalid call to `retrieveWorkflow` inside a `transaction` or `step`');
697
- }
698
- 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`');
699
734
  }
700
- 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);
701
738
  }
702
739
  /**
703
740
  * Query the system database for all workflows matching the provided predicate
@@ -727,14 +764,14 @@ class DBOS {
727
764
  * @param workflowID - ID of the workflow
728
765
  * @param priority - Priority value (0 to 2,147,483,647). Lower values are dequeued first.
729
766
  */
730
- static async setWorkflowPriority(workflowID, priority) {
767
+ static async setWorkflowPriority(workflowID, priority, options) {
731
768
  (0, decorators_1.ensureDBOSIsLaunched)('setWorkflowPriority');
732
769
  if (priority < dbos_executor_1.DBOS_QUEUE_MIN_PRIORITY || priority > dbos_executor_1.DBOS_QUEUE_MAX_PRIORITY) {
733
770
  throw new error_1.DBOSInvalidQueuePriorityError(priority, dbos_executor_1.DBOS_QUEUE_MIN_PRIORITY, dbos_executor_1.DBOS_QUEUE_MAX_PRIORITY);
734
771
  }
735
- return runInternalStep(async () => {
772
+ return (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], () => runInternalStep(async () => {
736
773
  return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.setWorkflowPriority(workflowID, priority);
737
- }, 'DBOS.setWorkflowPriority');
774
+ }, 'DBOS.setWorkflowPriority'));
738
775
  }
739
776
  /**
740
777
  * Update the delay on a DELAYED workflow.
@@ -746,9 +783,10 @@ class DBOS {
746
783
  static async setWorkflowDelay(workflowID, options) {
747
784
  (0, decorators_1.ensureDBOSIsLaunched)('setWorkflowDelay');
748
785
  const delayUntilEpochMS = resolveDelayEpochMS(options);
749
- return runInternalStep(async () => {
786
+ const shardKey = typeof options === 'object' ? options.shardKey : undefined;
787
+ return (0, shard_keys_1.bindShardKeys)([[workflowID, shardKey]], () => runInternalStep(async () => {
750
788
  return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.setWorkflowDelay(workflowID, delayUntilEpochMS);
751
- }, 'DBOS.setWorkflowDelay');
789
+ }, 'DBOS.setWorkflowDelay'));
752
790
  }
753
791
  /**
754
792
  * Retrieve the steps of a workflow
@@ -758,9 +796,9 @@ class DBOS {
758
796
  */
759
797
  static async listWorkflowSteps(workflowID, options) {
760
798
  (0, decorators_1.ensureDBOSIsLaunched)('listWorkflowSteps');
761
- return await runInternalStep(async () => {
799
+ return await (0, shard_keys_1.bindShardKeys)([[workflowID, options?.shardKey]], () => runInternalStep(async () => {
762
800
  return await DBOS.#executor.listWorkflowSteps(workflowID, true, options);
763
- }, 'DBOS.listWorkflowSteps');
801
+ }, 'DBOS.listWorkflowSteps'));
764
802
  }
765
803
  /**
766
804
  * Cancel a workflow given its ID.
@@ -779,9 +817,9 @@ class DBOS {
779
817
  */
780
818
  static async cancelWorkflows(workflowIDs, options) {
781
819
  (0, decorators_1.ensureDBOSIsLaunched)('cancelWorkflows');
782
- return runInternalStep(async () => {
820
+ return (0, shard_keys_1.bindShardKeys)(workflowIDs.map((wid) => [wid, options?.shardKey]), () => runInternalStep(async () => {
783
821
  return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.cancelWorkflows(workflowIDs, options?.cancelChildren);
784
- }, 'DBOS.cancelWorkflow');
822
+ }, 'DBOS.cancelWorkflow'));
785
823
  }
786
824
  /**
787
825
  * Resume a workflow given its ID.
@@ -789,8 +827,11 @@ class DBOS {
789
827
  * @throws DBOSNonExistentWorkflowError if the workflow does not exist
790
828
  */
791
829
  static async resumeWorkflow(workflowID, options) {
792
- const handles = await this.resumeWorkflows([workflowID], options);
793
- 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
+ });
794
835
  }
795
836
  /**
796
837
  * Resume multiple workflows given their IDs.
@@ -800,10 +841,12 @@ class DBOS {
800
841
  */
801
842
  static async resumeWorkflows(workflowIDs, options) {
802
843
  (0, decorators_1.ensureDBOSIsLaunched)('resumeWorkflows');
803
- await runInternalStep(async () => {
804
- return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.resumeWorkflows(workflowIDs, options?.queueName);
805
- }, 'DBOS.resumeWorkflow');
806
- 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
+ });
807
850
  }
808
851
  /**
809
852
  * Delete a workflow and optionally all its child workflows.
@@ -814,8 +857,8 @@ class DBOS {
814
857
  * @param workflowID - ID of the workflow to delete
815
858
  * @param deleteChildren - If true, also delete all child workflows recursively (default: false)
816
859
  */
817
- static async deleteWorkflow(workflowID, deleteChildren = false) {
818
- return this.deleteWorkflows([workflowID], deleteChildren);
860
+ static async deleteWorkflow(workflowID, deleteChildren = false, options) {
861
+ return this.deleteWorkflows([workflowID], deleteChildren, options);
819
862
  }
820
863
  /**
821
864
  * Delete multiple workflows and optionally all their child workflows.
@@ -825,11 +868,11 @@ class DBOS {
825
868
  * @param workflowIDs - IDs of the workflows to delete
826
869
  * @param deleteChildren - If true, also delete all child workflows recursively (default: false)
827
870
  */
828
- static async deleteWorkflows(workflowIDs, deleteChildren = false) {
871
+ static async deleteWorkflows(workflowIDs, deleteChildren = false, options) {
829
872
  (0, decorators_1.ensureDBOSIsLaunched)('deleteWorkflows');
830
- return runInternalStep(async () => {
873
+ return (0, shard_keys_1.bindShardKeys)(workflowIDs.map((wid) => [wid, options?.shardKey]), () => runInternalStep(async () => {
831
874
  return dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.deleteWorkflows(workflowIDs, deleteChildren);
832
- }, 'DBOS.deleteWorkflow');
875
+ }, 'DBOS.deleteWorkflow'));
833
876
  }
834
877
  /**
835
878
  * Fork a workflow given its ID.
@@ -841,10 +884,14 @@ class DBOS {
841
884
  */
842
885
  static async forkWorkflow(workflowID, startStep, options) {
843
886
  (0, decorators_1.ensureDBOSIsLaunched)('forkWorkflow');
844
- const forkedID = await runInternalStep(async () => {
845
- return await DBOS.#executor.forkWorkflow(workflowID, startStep, options);
846
- }, 'DBOS.forkWorkflow');
847
- 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
+ });
848
895
  }
849
896
  /**
850
897
  * Rewind a workflow given its ID: drop its recorded history from `startStep` onwards
@@ -868,10 +915,13 @@ class DBOS {
868
915
  static async rewindWorkflow(workflowID, options) {
869
916
  (0, decorators_1.ensureDBOSIsLaunched)('rewindWorkflow');
870
917
  const startStep = options?.startStep ?? 0;
871
- await runInternalStep(async () => {
872
- return await DBOS.#executor.rewindWorkflow(workflowID, startStep, options ?? {});
873
- }, 'DBOS.rewindWorkflow');
874
- 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
+ });
875
925
  }
876
926
  /**
877
927
  * Sleep for the specified amount of time.
@@ -986,6 +1036,13 @@ class DBOS {
986
1036
  (0, decorators_1.ensureDBOSIsLaunched)('workflows');
987
1037
  return DBOS.#withTopContext({ workflowTimeoutMS: timeoutMS }, callback);
988
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
+ }
989
1046
  static async #withTopContext(options, callback) {
990
1047
  const pctx = (0, context_1.getCurrentContextStore)();
991
1048
  if (pctx) {
@@ -1073,16 +1130,16 @@ class DBOS {
1073
1130
  // calls in the same workflow don't race for IDs.
1074
1131
  const functionID = (0, context_1.functionIDGetIncrement)();
1075
1132
  const sermsg = await (0, serialization_1.serializeValue)(message, DBOS.#executor.serializer, options?.serializationType ?? DBOS.#defaultSerializationType);
1076
- 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);
1077
1134
  }
1078
1135
  else if (DBOS.isInStep()) {
1079
1136
  // Inside a step: not recorded, but fenced on the enclosing workflow's ownership.
1080
1137
  const sermsg = await (0, serialization_1.serializeValue)(message, DBOS.#executor.serializer, options?.serializationType ?? DBOS.#defaultSerializationType);
1081
- 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);
1082
1139
  }
1083
1140
  else {
1084
1141
  const sermsg = await (0, serialization_1.serializeValue)(message, DBOS.#executor.serializer, options?.serializationType ?? DBOS.#defaultSerializationType);
1085
- 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);
1086
1143
  }
1087
1144
  }
1088
1145
  /**
@@ -1150,6 +1207,10 @@ class DBOS {
1150
1207
  */
1151
1208
  static async getEvent(workflowID, key, options) {
1152
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) {
1153
1214
  const timeoutSeconds = resolveTimeoutSeconds(options);
1154
1215
  const pollingIntervalMs = resolvePollingIntervalMs(options);
1155
1216
  if (DBOS.isWithinWorkflow()) {
@@ -1227,6 +1288,7 @@ class DBOS {
1227
1288
  timeoutMS: resolveStreamTimeoutMS(options),
1228
1289
  functionName: system_database_1.DBOS_FUNCNAME_READSTREAM,
1229
1290
  checkpoint: true,
1291
+ shardKey: options.shardKey,
1230
1292
  });
1231
1293
  }
1232
1294
  /**
@@ -1250,6 +1312,7 @@ class DBOS {
1250
1312
  timeoutMS: resolveStreamTimeoutMS(options),
1251
1313
  functionName: system_database_1.DBOS_FUNCNAME_READSTREAMOFFSET,
1252
1314
  checkpoint: true,
1315
+ shardKey: options.shardKey,
1253
1316
  });
1254
1317
  }
1255
1318
  //////
@@ -1306,6 +1369,8 @@ class DBOS {
1306
1369
  const ppctx = (0, context_1.getCurrentContextStore)();
1307
1370
  const queueName = params.queueName ?? ppctx?.queueAssignedForWorkflows;
1308
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);
1309
1374
  const instance = $this === undefined || typeof $this === 'function' ? undefined : $this;
1310
1375
  if (instance && !(instance instanceof decorators_1.ConfiguredInstance)) {
1311
1376
  throw new error_1.DBOSInvalidWorkflowTransitionError('Attempt to call a `workflow` function on an object that is not a `ConfiguredInstance`');
@@ -1343,7 +1408,7 @@ class DBOS {
1343
1408
  if (isChild) {
1344
1409
  const pwfid = ppctx.workflowId;
1345
1410
  const wfParams = {
1346
- workflowUUID: wfId || pwfid + '-' + funcId,
1411
+ workflowUUID: wfId || (0, context_1.childWorkflowID)(pwfid, funcId, ppctx?.shardKey, shardKey),
1347
1412
  configuredInstance: instance,
1348
1413
  queueName,
1349
1414
  timeoutMS,
@@ -1355,6 +1420,7 @@ class DBOS {
1355
1420
  workflowAttributes: params.workflowAttributes,
1356
1421
  authenticatedUser: params.authenticatedUser,
1357
1422
  authenticatedRoles: params.authenticatedRoles,
1423
+ shardKey,
1358
1424
  };
1359
1425
  return await invokeRegOp(wfParams, pwfid, funcId);
1360
1426
  }
@@ -1370,6 +1436,7 @@ class DBOS {
1370
1436
  workflowAttributes: params.workflowAttributes,
1371
1437
  authenticatedUser: params.authenticatedUser,
1372
1438
  authenticatedRoles: params.authenticatedRoles,
1439
+ shardKey,
1373
1440
  };
1374
1441
  return await invokeRegOp(wfParams, undefined, undefined);
1375
1442
  }
@@ -1396,6 +1463,8 @@ class DBOS {
1396
1463
  // Reserve the parent's child-funcID once, before any await. Each loop iteration passes it
1397
1464
  // through so the return-existing retry loop consumes a single funcID instead of one per try.
1398
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)());
1399
1468
  // Resolve the workflow ID once: if the caller supplied one explicitly or via
1400
1469
  // `withNextWorkflowID`, reuse it on every iteration.
1401
1470
  const resolvedWorkflowID = (0, context_1.getNextWFID)(params.workflowID);
@@ -1416,7 +1485,7 @@ class DBOS {
1416
1485
  const now = Date.now();
1417
1486
  await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.recordOperationResult(DBOS.workflowID, childFuncId, regOP.name, true, now, now, { childWorkflowID: existingID });
1418
1487
  }
1419
- return new workflow_1.RetrievedHandle(existingID);
1488
+ return new workflow_1.RetrievedHandle(existingID, shardKey);
1420
1489
  }
1421
1490
  // The prior workflow's deduplication_id was cleared between our INSERT and
1422
1491
  // the lookup (it completed or was cancelled). Loop and try to claim the slot.
@@ -1700,6 +1769,7 @@ class DBOS {
1700
1769
  cronTimezone: options.options?.cronTimezone ?? null,
1701
1770
  queueName: options.options?.queueName ?? null,
1702
1771
  applicationName: utils_1.globalParams.appName,
1772
+ shardKey: options.options?.shardKey,
1703
1773
  };
1704
1774
  await runTransactionalInternalStep((client) => dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.createSchedule(schedInternal, client), 'DBOS.createSchedule');
1705
1775
  }
@@ -1785,6 +1855,7 @@ class DBOS {
1785
1855
  automaticBackfill: sched.automaticBackfill ?? false,
1786
1856
  cronTimezone: sched.cronTimezone ?? null,
1787
1857
  queueName: sched.queueName ?? null,
1858
+ shardKey: sched.shardKey,
1788
1859
  });
1789
1860
  }
1790
1861
  await dbos_executor_1.DBOSExecutor.globalInstance.systemDatabase.applySchedules(internals);
@@ -1796,7 +1867,7 @@ class DBOS {
1796
1867
  }
1797
1868
  const executor = dbos_executor_1.DBOSExecutor.globalInstance;
1798
1869
  const workflowID = await (0, scheduler_1.triggerSchedule)(executor.systemDatabase, executor.serializer, name);
1799
- return new workflow_1.RetrievedHandle(workflowID);
1870
+ return new workflow_1.RetrievedHandle(workflowID, await (0, scheduler_1.scheduleShardKey)(executor.systemDatabase, name));
1800
1871
  }
1801
1872
  static async backfillSchedule(name, start, end) {
1802
1873
  (0, decorators_1.ensureDBOSIsLaunched)('backfillSchedule');
@@ -1805,7 +1876,8 @@ class DBOS {
1805
1876
  }
1806
1877
  const executor = dbos_executor_1.DBOSExecutor.globalInstance;
1807
1878
  const workflowIDs = await (0, scheduler_1.backfillSchedule)(executor.systemDatabase, executor.serializer, name, start, end);
1808
- 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));
1809
1881
  }
1810
1882
  // ==================== Application Versions ====================
1811
1883
  /**