Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/content/changelog/workflows/2025-04-07-workflows-ga.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ For example, if you wanted to implement a human-in-the-loop approval process, yo
<TypeScriptExample>

```ts
import { Workflow, WorkflowEvent } from "cloudflare:workflows";
import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from "cloudflare:workers";

export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// Other steps in your Workflow
let event = await step.waitForEvent<IncomingStripeWebhook>("receive invoice paid webhook from Stripe", { type: "stripe-webhook", timeout: "1 hour" })
let stripeEvent = await step.waitForEvent<IncomingStripeWebhook>("receive invoice paid webhook from Stripe", { type: "stripe-webhook", timeout: "1 hour" })
// Rest of your Workflow
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/content/docs/workflows/build/events-and-parameters.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ For example, to wait for billing webhook:
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// Other steps in your Workflow
let event = await step.waitForEvent<IncomingStripeWebhook>(
let stripeEvent = await step.waitForEvent<IncomingStripeWebhook>(
"receive invoice paid webhook from Stripe",
{ type: "stripe-webhook", timeout: "1 hour" },
);
Expand Down Expand Up @@ -223,13 +223,13 @@ export class MyWorkflow extends WorkflowEntrypoint {
// Pass your type as a type parameter to WorkflowEvent
// The 'payload' property will have the type of your parameter.
async run(event: WorkflowEvent<YourEventType>, step: WorkflowStep) {
let state = step.do("my first step", async () => {
let state = await step.do("my first step", async () => {
// Access your properties via event.payload
let userEmail = event.payload.userEmail
let createdTimestamp = event.payload.createdTimestamp
})

step.do("my second step", async () => { /* your code here */ )
await step.do("my second step", async () => { /* your code here */ })
}
}
```
Expand Down
58 changes: 29 additions & 29 deletions src/content/docs/workflows/build/rules-of-workflows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
// timeout policies for each granular step - you might not to want to overload http.cat in
// case of it being down.
const httpCat = await step.do("get cutest cat from KV", async () => {
return await env.KV.get("cutest-http-cat");
return await this.env.KV.get("cutest-http-cat");
});

const image = await step.do("fetch cat image from http.cat", async () => {
Expand Down Expand Up @@ -111,7 +111,7 @@
// some extra calls to the first service in case the second one fails, and in some cases, makes
// the step non-idempotent altogether
const image = await step.do("get cutest cat from KV", async () => {
const httpCat = await env.KV.get("cutest-http-cat");
const httpCat = await this.env.KV.get("cutest-http-cat");
return fetch(`https://http.cat/${httpCat}`);
});
}
Expand Down Expand Up @@ -142,15 +142,15 @@
const imageList: string[] = [];

await step.do("get first cutest cat from KV", async () => {
const httpCat = await env.KV.get("cutest-http-cat-1");
const httpCat = await this.env.KV.get("cutest-http-cat-1");

imageList.append(httpCat);
imageList.push(httpCat);
});

await step.do("get second cutest cat from KV", async () => {
const httpCat = await env.KV.get("cutest-http-cat-2");
const httpCat = await this.env.KV.get("cutest-http-cat-2");

imageList.append(httpCat);
imageList.push(httpCat);
});

// A long sleep can (and probably will) hibernate the engine which means that the first engine lifetime ends here
Expand Down Expand Up @@ -188,11 +188,11 @@
// multiple engine lifetimes, imageList will be built accordingly
const imageList: string[] = await Promise.all([
step.do("get first cutest cat from KV", async () => {
return await env.KV.get("cutest-http-cat-1");
return await this.env.KV.get("cutest-http-cat-1");
}),

step.do("get second cutest cat from KV", async () => {
return await env.KV.get("cutest-http-cat-2");
return await this.env.KV.get("cutest-http-cat-2");
}),
]);

Expand Down Expand Up @@ -221,7 +221,7 @@

For example, a `console.log()` outside of workflow steps may cause the logs to print twice when the engine restarts.

However, logic involving non-serializable resources, like a database connection, should be executed outside of steps. Operations ouside of a `step.do` might be repeated more than once, due to the nature of the Workflows' instance lifecycle.
However, logic involving non-serializable resources, like a database connection, should be executed outside of steps. Operations outside of a `step.do` might be repeated more than once, due to the nature of the Workflows' instance lifecycle.

<TypeScriptExample filename="index.ts">

Expand All @@ -230,14 +230,14 @@
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// 🔴 Bad: creating instances outside of steps
// This might get called more than once creating more instances than expected
const myNewInstance = await this.env.ANOTHER_WORKFLOW.create();
const badInstance = await this.env.ANOTHER_WORKFLOW.create();

// 🔴 Bad: using non-deterministic functions outside of steps
// this will produce different results if the instance has to restart, different runs of the same instance
// might go through different paths
const myRandom = Math.random();
const badRandom = Math.random();

if (myRandom > 0) {
if (badRandom > 0) {
// do some stuff
}

Expand All @@ -253,21 +253,21 @@

// ✅ Good: wrap non-deterministic function in a step
// after running successfully will not run again
const myRandom = await step.do("create a random number", async () => {
const goodRandom = await step.do("create a random number", async () => {
return Math.random();
});

// ✅ Good: calls that have no side effects can be done outside of steps
const db = createDBConnection(this.env.DB_URL, this.env.DB_TOKEN);

// ✅ Good: run funtions with side effects inside of a step
// ✅ Good: run functions with side effects inside of a step
// after running successfully will not run again
const myNewInstance = await step.do(
const goodInstance = await step.do(
"good step that returns state",
async () => {
const myNewInstance = await this.env.ANOTHER_WORKFLOW.create();
const instance = await this.env.ANOTHER_WORKFLOW.create();

return myNewInstance;
return instance;
},
);
}
Expand All @@ -294,14 +294,14 @@
// This will not be persisted across steps and `event.payload` will
// take on its original value.
await step.do("bad step that mutates the incoming event", async () => {
let userData = await env.KV.get(event.payload.user);
let userData = await this.env.KV.get(event.payload.user);
event.payload = userData;
});

// ✅ Good: persist data by returning it as state from your step
// Use that state in subsequent steps
let userData = await step.do("good step that returns state", async () => {
return await env.KV.get(event.payload.user);
return await this.env.KV.get(event.payload.user);
});

let someOtherData = await step.do(
Expand Down Expand Up @@ -329,16 +329,16 @@
// 🔴 Bad: Naming the step non-deterministically prevents it from being cached
// This will cause the step to be re-run if subsequent steps fail.
await step.do(`step #1 running at: ${Date.now()}`, async () => {
let userData = await env.KV.get(event.payload.user);
let userData = await this.env.KV.get(event.payload.user);
// Do not mutate event.payload
event.payload = userData;
});

// ✅ Good: give steps a deterministic name.
// Return dynamic values in your state, or log them instead.
let state = await step.do("fetch user data from KV", async () => {
let userData = await env.KV.get(event.payload.user);
console.log(`fetched at ${Date.now}`);
let userData = await this.env.KV.get(event.payload.user);
console.log(`fetched at ${Date.now()}`);
return userData;
});

Expand All @@ -347,12 +347,12 @@
// traversed in a deterministic fashion (no shuffles or random accesses) so,
// it's fine to dynamically name steps (e.g: create a step per list entry).
let catList = await step.do("get cat list from KV", async () => {
return await env.KV.get("cat-list");
return await this.env.KV.get("cat-list");
});

for (const cat of catList) {
await step.do(`get cat: ${cat}`, async () => {
return await env.KV.get(cat);
return await this.env.KV.get(cat);
});
}
}
Expand Down Expand Up @@ -490,14 +490,14 @@
export class MyWorkflow extends WorkflowEntrypoint {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// 🔴 Bad: The step isn't await'ed, and any state or errors is swallowed before it returns.
const issues = step.do(`fetch issues from GitHub`, async () => {
const badIssues = step.do(`fetch issues from GitHub`, async () => {
// The step will return before this call is done
let issues = await getIssues(event.payload.repoName);
return issues;
});

// ✅ Good: The step is correctly await'ed.
const issues = await step.do(`fetch issues from GitHub`, async () => {
const goodIssues = await step.do(`fetch issues from GitHub`, async () => {
let issues = await getIssues(event.payload.repoName);
return issues;
});
Expand Down Expand Up @@ -557,7 +557,7 @@

### Batch multiple Workflow invocations

When creating multiple Workflow instances, use the [`createBatch`](/workflows/build/workers-api/#createBatch) method to batch the invocations together. This allows you to create multiple Workflow instances in a single request, which will reduce the number of requests made to the Workflows API. However, each individual instance in the batch will still count towards the [creation rate limit](/workflows/reference/limits/).
When creating multiple Workflow instances, use the [`createBatch`](/workflows/build/workers-api/#createBatch) method to batch the invocations together. This allows you to create multiple Workflow instances in a single request, which will reduce the number of requests made to the Workflows API. However, each individual instance in the batch will still count towards the [creation rate limit](/workflows/reference/limits/). Unlike `create`, `createBatch` is idempotent: if an existing instance with the same ID is still within its [retention limit](/workflows/reference/limits/), it will be skipped and excluded from the returned array.

<TypeScriptExample filename="index.ts">

Expand All @@ -566,7 +566,7 @@
async fetch(req: Request, env: Env): Promise<Response> {
let instances = [
{ id: "user1", params: { name: "John" } },
{ id: "user2", params: { name: "Jane" } },

Check warning on line 569 in src/content/docs/workflows/build/rules-of-workflows.mdx

View workflow job for this annotation

GitHub Actions / Semgrep

semgrep.style-guide-potential-date-month

Potential month found. Documentation should strive to represent universal truth, not something time-bound. (add [skip style guide checks] to commit message to skip)
{ id: "user3", params: { name: "Alice" } },
{ id: "user4", params: { name: "Bob" } },
];
Expand All @@ -581,8 +581,8 @@

// ✅ Good: Batch calls together
// This improves throughput.
let instances = await env.MY_WORKFLOW.createBatch(instances);
return Response.json({ instances });
let createdInstances = await env.MY_WORKFLOW.createBatch(instances);
return Response.json({ instances: createdInstances });
},
};
```
Expand Down
6 changes: 3 additions & 3 deletions src/content/docs/workflows/build/sleeping-and-retrying.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

```ts
// sleepUntil accepts a Date object as its second argument
const workflowsLaunchDate = Date.parse("24 Oct 2024 13:00:00 UTC");

Check warning on line 47 in src/content/docs/workflows/build/sleeping-and-retrying.mdx

View workflow job for this annotation

GitHub Actions / Semgrep

semgrep.style-guide-potential-date-year

Potential year found. Documentation should strive to represent universal truth, not something time-bound. (add [skip style guide checks] to commit message to skip)
await step.sleepUntil("sleep until X times out", workflowsLaunchDate)
```

Expand Down Expand Up @@ -77,14 +77,14 @@
For example, to limit a step to 10 retries and have it apply an exponential delay (starting at 10 seconds) between each attempt, you would pass the following configuration as an optional object to `step.do`:

```ts
let someState = step.do("call an API", {
let someState = await step.do("call an API", {
retries: {
limit: 10, // The total number of attempts
delay: "10 seconds", // Delay between each retry
backoff: "exponential" // Any of "constant" | "linear" | "exponential";
},
timeout: "30 minutes",
}, async () => { /* Step code goes here /* }
}, async () => { /* Step code goes here */ })
```

## Force a Workflow instance to fail
Expand Down Expand Up @@ -132,7 +132,7 @@
// work not to be retried
throw new NonRetryableError('oh no');
});
} catch(e as Error) {
} catch (e) {
console.log(`Step failed: ${e.message}`);
await step.do('clean-up-task', async () => {
// Clean up code here
Expand Down
4 changes: 2 additions & 2 deletions src/content/docs/workflows/build/workers-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ Objects that include `Function` or `Symbol` types, and objects with circular ref
{/* prettier-ignore */}
- <code>step.sleepUntil(name: string, timestamp: Date | number): Promise&lt;void&gt;</code>
- `name` - the name of the step.
- `timestamp` - a JavaScript `Date` object or seconds from the Unix epoch to sleep the Workflow instance until.
- `timestamp` - a JavaScript `Date` object or milliseconds from the Unix epoch to sleep the Workflow instance until.

:::note

Expand All @@ -122,7 +122,7 @@ More information about the limits imposed on Workflow can be found in the [Workf
export class MyWorkflow extends WorkflowEntrypoint<Env, Params> {
async run(event: WorkflowEvent<Params>, step: WorkflowStep) {
// Other steps in your Workflow
let event = await step.waitForEvent<IncomingStripeWebhook>(
let stripeEvent = await step.waitForEvent<IncomingStripeWebhook>(
"receive invoice paid webhook from Stripe",
{ type: "stripe-webhook", timeout: "1 hour" },
);
Expand Down
Loading