@donkeylabs/server 2.0.31 → 2.0.32

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.
package/docs/workflows.md CHANGED
@@ -97,9 +97,16 @@ Limit concurrent instances per workflow name:
97
97
  const server = new AppServer({
98
98
  db,
99
99
  workflows: {
100
- concurrentWorkflows: 1, // 0 = unlimited
100
+ concurrentWorkflows: 1, // default for all workflows (0 = unlimited)
101
+ concurrentWorkflowsByName: {
102
+ testWorkflow: 1,
103
+ ingestionWorkflow: 1,
104
+ },
101
105
  },
102
106
  });
107
+
108
+ // Or per-register override
109
+ ctx.core.workflows.register(orderWorkflow, { maxConcurrent: 1 });
103
110
  ```
104
111
 
105
112
  ### 3. Track Progress
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donkeylabs/server",
3
- "version": "2.0.31",
3
+ "version": "2.0.32",
4
4
  "type": "module",
5
5
  "description": "Type-safe plugin system for building RPC-style APIs with Bun",
6
6
  "main": "./src/index.ts",
@@ -766,8 +766,10 @@ export interface WorkflowsConfig {
766
766
  sqlitePragmas?: SqlitePragmaConfig;
767
767
  /** Disable in-process watchdog timers (use external watchdog instead) */
768
768
  useWatchdog?: boolean;
769
- /** Max concurrent instances per workflow name (0 = unlimited, default: 0) */
769
+ /** Default max concurrent instances per workflow name (0 = unlimited, default: 0) */
770
770
  concurrentWorkflows?: number;
771
+ /** Per-workflow concurrency overrides */
772
+ concurrentWorkflowsByName?: Record<string, number>;
771
773
  /** Resume strategy for orphaned workflows (default: "blocking") */
772
774
  resumeStrategy?: WorkflowResumeStrategy;
773
775
  }
@@ -796,6 +798,8 @@ export interface WorkflowRegisterOptions {
796
798
  * workflows.register(myWorkflow, { modulePath: import.meta.url });
797
799
  */
798
800
  modulePath?: string;
801
+ /** Max concurrent instances for this workflow (overrides defaults) */
802
+ maxConcurrent?: number;
799
803
  }
800
804
 
801
805
  export interface Workflows {
@@ -874,6 +878,8 @@ class WorkflowsImpl implements Workflows {
874
878
  private sqlitePragmas?: SqlitePragmaConfig;
875
879
  private useWatchdog: boolean;
876
880
  private concurrentWorkflows: number;
881
+ private concurrentWorkflowsByName: Record<string, number>;
882
+ private workflowConcurrencyOverrides = new Map<string, number>();
877
883
  private resumeStrategy!: WorkflowResumeStrategy;
878
884
  private workflowModulePaths = new Map<string, string>();
879
885
  private isolatedProcesses = new Map<string, IsolatedProcessInfo>();
@@ -912,6 +918,7 @@ class WorkflowsImpl implements Workflows {
912
918
  this.sqlitePragmas = config.sqlitePragmas;
913
919
  this.useWatchdog = config.useWatchdog ?? false;
914
920
  this.concurrentWorkflows = config.concurrentWorkflows ?? 0;
921
+ this.concurrentWorkflowsByName = config.concurrentWorkflowsByName ?? {};
915
922
  this.resumeStrategy = config.resumeStrategy ?? "blocking";
916
923
  }
917
924
 
@@ -1002,6 +1009,10 @@ class WorkflowsImpl implements Workflows {
1002
1009
  }
1003
1010
 
1004
1011
  this.definitions.set(definition.name, definition);
1012
+
1013
+ if (options?.maxConcurrent !== undefined) {
1014
+ this.workflowConcurrencyOverrides.set(definition.name, options.maxConcurrent);
1015
+ }
1005
1016
  }
1006
1017
 
1007
1018
  async start<T = any>(workflowName: string, input: T): Promise<string> {
@@ -1010,12 +1021,13 @@ class WorkflowsImpl implements Workflows {
1010
1021
  throw new Error(`Workflow "${workflowName}" is not registered`);
1011
1022
  }
1012
1023
 
1013
- if (this.concurrentWorkflows > 0) {
1024
+ const limit = this.getConcurrencyLimit(workflowName);
1025
+ if (limit > 0) {
1014
1026
  const running = await this.adapter.getInstancesByWorkflow(workflowName, "running");
1015
1027
  const pending = await this.adapter.getInstancesByWorkflow(workflowName, "pending");
1016
- if (running.length + pending.length >= this.concurrentWorkflows) {
1028
+ if (running.length + pending.length >= limit) {
1017
1029
  throw new Error(
1018
- `Workflow "${workflowName}" has reached its concurrency limit (${this.concurrentWorkflows})`
1030
+ `Workflow "${workflowName}" has reached its concurrency limit (${limit})`
1019
1031
  );
1020
1032
  }
1021
1033
  }
@@ -2005,6 +2017,16 @@ class WorkflowsImpl implements Workflows {
2005
2017
  await this.adapter.updateInstance(instanceId, { metadata });
2006
2018
  }
2007
2019
 
2020
+ private getConcurrencyLimit(workflowName: string): number {
2021
+ if (this.workflowConcurrencyOverrides.has(workflowName)) {
2022
+ return this.workflowConcurrencyOverrides.get(workflowName) ?? 0;
2023
+ }
2024
+ if (this.concurrentWorkflowsByName[workflowName] !== undefined) {
2025
+ return this.concurrentWorkflowsByName[workflowName] ?? 0;
2026
+ }
2027
+ return this.concurrentWorkflows;
2028
+ }
2029
+
2008
2030
  private async markOrphanedAsFailed(
2009
2031
  instances: WorkflowInstance[],
2010
2032
  reason: string