Skip to content

Commit c8182ec

Browse files
authored
feat(cloudflare): add new steps limit option for Workflows (#1355)
1 parent 84da24e commit c8182ec

File tree

7 files changed

+166
-63
lines changed

7 files changed

+166
-63
lines changed

alchemy-web/src/content/docs/providers/cloudflare/workflow.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ await Worker("my-worker", {
5353
});
5454
```
5555

56+
## Workflow Step Limits
57+
58+
Set the maximum number of steps per workflow instance with `limits.steps`.
59+
60+
```ts
61+
import { Workflow } from "alchemy/cloudflare";
62+
63+
const workflow = Workflow("order-processing", {
64+
workflowName: "order-processing",
65+
className: "OrderProcessingWorkflow",
66+
limits: {
67+
steps: 25_000,
68+
},
69+
});
70+
```
71+
5672
## Rename Class Name
5773

5874
Alchemy takes care of migrations automatically when you rename the class name.

alchemy-web/src/content/docs/providers/cloudflare/wrangler.json.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,33 @@ const worker = await Worker("cron", {
7575
await WranglerJson({ worker });
7676
```
7777

78+
## With Workflow Step Limits
79+
80+
If the Worker binds to a Workflow with `limits.steps`, that value is written to
81+
the `workflows[].limits.steps` field in `wrangler.json`.
82+
83+
```ts
84+
import { Worker, Workflow, WranglerJson } from "alchemy/cloudflare";
85+
86+
const workflow = Workflow("order-processing", {
87+
workflowName: "order-processing",
88+
className: "OrderProcessingWorkflow",
89+
limits: {
90+
steps: 25_000,
91+
},
92+
});
93+
94+
const worker = await Worker("orders", {
95+
name: "orders-worker",
96+
entrypoint: "./src/worker.ts",
97+
bindings: {
98+
ORDER_WORKFLOW: workflow,
99+
},
100+
});
101+
102+
await WranglerJson({ worker });
103+
```
104+
78105
## With Transform Hook
79106

80107
The transform hook allows you to customize the wrangler.json configuration. For example, adding a custom environment variable:

alchemy/src/cloudflare/workflow.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,20 @@ export interface WorkflowProps {
2525
* @default - bound worker script
2626
*/
2727
scriptName?: string;
28+
/**
29+
* Limits for the workflow instance
30+
*/
31+
limits?: {
32+
/**
33+
* Maximum number of steps per workflow instance.
34+
*
35+
* Workers Free: 1,024 (not configurable)
36+
* Workers Paid: 10,000 default, configurable up to 25,000
37+
*
38+
* @see https://developers.cloudflare.com/workflows/reference/limits/
39+
*/
40+
steps?: number;
41+
};
2842
dev?: {
2943
/**
3044
* Whether to run the workflow remotely instead of locally
@@ -45,6 +59,9 @@ export type Workflow<PARAMS = unknown> = {
4559
workflowName: string;
4660
className: string;
4761
scriptName?: string;
62+
limits?: {
63+
steps?: number;
64+
};
4865
};
4966

5067
export function isWorkflow(binding: Binding): binding is Workflow {
@@ -77,6 +94,7 @@ export function Workflow<PARAMS = unknown>(
7794
workflowName,
7895
className,
7996
scriptName: props.scriptName,
97+
limits: props.limits,
8098
};
8199
}
82100

alchemy/src/cloudflare/wrangler.json.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ async function processBindings(
403403
binding: bindingName,
404404
class_name: binding.className,
405405
script_name: binding.scriptName,
406+
...(binding.limits ? { limits: binding.limits } : {}),
406407
});
407408
} else if (binding.type === "d1") {
408409
const id =

alchemy/test/cloudflare/wrangler-json.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,51 @@ describe("WranglerJson Resource", () => {
382382
}
383383
});
384384

385+
test("with workflow step limits", async (scope) => {
386+
const name = `${BRANCH_PREFIX}-test-worker-wf-limits`;
387+
const tempDir = path.join(".out", "alchemy-wf-limits-test");
388+
const entrypoint = path.join(tempDir, "worker.ts");
389+
390+
try {
391+
await fs.rm(tempDir, { recursive: true, force: true });
392+
await fs.mkdir(tempDir, { recursive: true });
393+
await fs.writeFile(entrypoint, wfWorkerScript);
394+
395+
const workflow = Workflow("test-workflow-limits", {
396+
className: "TestWorkflow",
397+
workflowName: "test-workflow-limits",
398+
limits: {
399+
steps: 25000,
400+
},
401+
});
402+
403+
const worker = await Worker(name, {
404+
name,
405+
format: "esm",
406+
entrypoint,
407+
bindings: {
408+
WF: workflow,
409+
},
410+
adopt: true,
411+
});
412+
413+
const { spec } = await WranglerJson({ worker });
414+
415+
expect(spec.workflows).toHaveLength(1);
416+
expect(spec.workflows?.[0]).toMatchObject({
417+
name: "test-workflow-limits",
418+
binding: "WF",
419+
class_name: "TestWorkflow",
420+
limits: {
421+
steps: 25000,
422+
},
423+
});
424+
} finally {
425+
await fs.rm(tempDir, { recursive: true, force: true });
426+
await destroy(scope);
427+
}
428+
});
429+
385430
test("with cron triggers", async (scope) => {
386431
const name = `${BRANCH_PREFIX}-test-worker-cron-json`;
387432
const tempDir = path.join(".out", "alchemy-cron-json-test");

0 commit comments

Comments
 (0)