@donkeylabs/server 2.0.30 → 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 +20 -0
- package/package.json +1 -1
- package/src/core/workflows.ts +36 -0
package/docs/workflows.md
CHANGED
|
@@ -89,6 +89,26 @@ const instanceId = await ctx.core.workflows.start("process-order", {
|
|
|
89
89
|
});
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
+
### Concurrency Guard
|
|
93
|
+
|
|
94
|
+
Limit concurrent instances per workflow name:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const server = new AppServer({
|
|
98
|
+
db,
|
|
99
|
+
workflows: {
|
|
100
|
+
concurrentWorkflows: 1, // default for all workflows (0 = unlimited)
|
|
101
|
+
concurrentWorkflowsByName: {
|
|
102
|
+
testWorkflow: 1,
|
|
103
|
+
ingestionWorkflow: 1,
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Or per-register override
|
|
109
|
+
ctx.core.workflows.register(orderWorkflow, { maxConcurrent: 1 });
|
|
110
|
+
```
|
|
111
|
+
|
|
92
112
|
### 3. Track Progress
|
|
93
113
|
|
|
94
114
|
```typescript
|
package/package.json
CHANGED
package/src/core/workflows.ts
CHANGED
|
@@ -766,6 +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
|
+
/** Default max concurrent instances per workflow name (0 = unlimited, default: 0) */
|
|
770
|
+
concurrentWorkflows?: number;
|
|
771
|
+
/** Per-workflow concurrency overrides */
|
|
772
|
+
concurrentWorkflowsByName?: Record<string, number>;
|
|
769
773
|
/** Resume strategy for orphaned workflows (default: "blocking") */
|
|
770
774
|
resumeStrategy?: WorkflowResumeStrategy;
|
|
771
775
|
}
|
|
@@ -794,6 +798,8 @@ export interface WorkflowRegisterOptions {
|
|
|
794
798
|
* workflows.register(myWorkflow, { modulePath: import.meta.url });
|
|
795
799
|
*/
|
|
796
800
|
modulePath?: string;
|
|
801
|
+
/** Max concurrent instances for this workflow (overrides defaults) */
|
|
802
|
+
maxConcurrent?: number;
|
|
797
803
|
}
|
|
798
804
|
|
|
799
805
|
export interface Workflows {
|
|
@@ -871,6 +877,9 @@ class WorkflowsImpl implements Workflows {
|
|
|
871
877
|
private killGraceMs: number;
|
|
872
878
|
private sqlitePragmas?: SqlitePragmaConfig;
|
|
873
879
|
private useWatchdog: boolean;
|
|
880
|
+
private concurrentWorkflows: number;
|
|
881
|
+
private concurrentWorkflowsByName: Record<string, number>;
|
|
882
|
+
private workflowConcurrencyOverrides = new Map<string, number>();
|
|
874
883
|
private resumeStrategy!: WorkflowResumeStrategy;
|
|
875
884
|
private workflowModulePaths = new Map<string, string>();
|
|
876
885
|
private isolatedProcesses = new Map<string, IsolatedProcessInfo>();
|
|
@@ -908,6 +917,8 @@ class WorkflowsImpl implements Workflows {
|
|
|
908
917
|
this.killGraceMs = config.killGraceMs ?? 5000;
|
|
909
918
|
this.sqlitePragmas = config.sqlitePragmas;
|
|
910
919
|
this.useWatchdog = config.useWatchdog ?? false;
|
|
920
|
+
this.concurrentWorkflows = config.concurrentWorkflows ?? 0;
|
|
921
|
+
this.concurrentWorkflowsByName = config.concurrentWorkflowsByName ?? {};
|
|
911
922
|
this.resumeStrategy = config.resumeStrategy ?? "blocking";
|
|
912
923
|
}
|
|
913
924
|
|
|
@@ -998,6 +1009,10 @@ class WorkflowsImpl implements Workflows {
|
|
|
998
1009
|
}
|
|
999
1010
|
|
|
1000
1011
|
this.definitions.set(definition.name, definition);
|
|
1012
|
+
|
|
1013
|
+
if (options?.maxConcurrent !== undefined) {
|
|
1014
|
+
this.workflowConcurrencyOverrides.set(definition.name, options.maxConcurrent);
|
|
1015
|
+
}
|
|
1001
1016
|
}
|
|
1002
1017
|
|
|
1003
1018
|
async start<T = any>(workflowName: string, input: T): Promise<string> {
|
|
@@ -1006,6 +1021,17 @@ class WorkflowsImpl implements Workflows {
|
|
|
1006
1021
|
throw new Error(`Workflow "${workflowName}" is not registered`);
|
|
1007
1022
|
}
|
|
1008
1023
|
|
|
1024
|
+
const limit = this.getConcurrencyLimit(workflowName);
|
|
1025
|
+
if (limit > 0) {
|
|
1026
|
+
const running = await this.adapter.getInstancesByWorkflow(workflowName, "running");
|
|
1027
|
+
const pending = await this.adapter.getInstancesByWorkflow(workflowName, "pending");
|
|
1028
|
+
if (running.length + pending.length >= limit) {
|
|
1029
|
+
throw new Error(
|
|
1030
|
+
`Workflow "${workflowName}" has reached its concurrency limit (${limit})`
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1009
1035
|
const instance = await this.adapter.createInstance({
|
|
1010
1036
|
workflowName,
|
|
1011
1037
|
status: "pending",
|
|
@@ -1991,6 +2017,16 @@ class WorkflowsImpl implements Workflows {
|
|
|
1991
2017
|
await this.adapter.updateInstance(instanceId, { metadata });
|
|
1992
2018
|
}
|
|
1993
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
|
+
|
|
1994
2030
|
private async markOrphanedAsFailed(
|
|
1995
2031
|
instances: WorkflowInstance[],
|
|
1996
2032
|
reason: string
|