From 2cb3bf2ee82313dfd08d302a108c2e12b16f534c Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 07:42:38 +0100 Subject: [PATCH 01/54] commit --- src/content/changelogs/workflows.yaml | 15 + .../docs/workflows/build/dynamic-steps.mdx | 35 ++ src/content/docs/workflows/build/index.mdx | 11 + src/content/docs/workflows/build/retrying.mdx | 9 + .../docs/workflows/build/rules-of-steps.mdx | 26 ++ .../workflows/build/trigger-workflows.mdx | 86 ++++ .../docs/workflows/build/typescript.mdx | 9 + .../docs/workflows/build/workers-api.mdx | 59 +++ .../examples/automate-lifecycle-emails.mdx | 15 + src/content/docs/workflows/examples/index.mdx | 15 + .../workflows/examples/post-process-r2.mdx | 15 + .../workflows/get-started/cli-quick-start.mdx | 124 ++++++ .../docs/workflows/get-started/guide.mdx | 384 ++++++++++++++++++ .../docs/workflows/get-started/index.mdx | 11 + src/content/docs/workflows/index.mdx | 103 +++++ .../docs/workflows/observability/index.mdx | 11 + .../observability/metrics-analytics.mdx | 29 ++ .../docs/workflows/reference/changelog.mdx | 15 + .../docs/workflows/reference/glossary.mdx | 13 + .../reference/how-workflows-works.mdx | 27 ++ .../docs/workflows/reference/index.mdx | 11 + .../docs/workflows/reference/limits.mdx | 33 ++ .../docs/workflows/reference/pricing.mdx | 44 ++ .../workflows/reference/storage-options.mdx | 8 + .../workflows/reference/wrangler-commands.mdx | 8 + src/content/docs/workflows/workflows-api.mdx | 8 + src/content/glossary/workflows.yaml | 22 + src/content/products/workflows.yaml | 33 ++ 28 files changed, 1179 insertions(+) create mode 100644 src/content/changelogs/workflows.yaml create mode 100644 src/content/docs/workflows/build/dynamic-steps.mdx create mode 100644 src/content/docs/workflows/build/index.mdx create mode 100644 src/content/docs/workflows/build/retrying.mdx create mode 100644 src/content/docs/workflows/build/rules-of-steps.mdx create mode 100644 src/content/docs/workflows/build/trigger-workflows.mdx create mode 100644 src/content/docs/workflows/build/typescript.mdx create mode 100644 src/content/docs/workflows/build/workers-api.mdx create mode 100644 src/content/docs/workflows/examples/automate-lifecycle-emails.mdx create mode 100644 src/content/docs/workflows/examples/index.mdx create mode 100644 src/content/docs/workflows/examples/post-process-r2.mdx create mode 100644 src/content/docs/workflows/get-started/cli-quick-start.mdx create mode 100644 src/content/docs/workflows/get-started/guide.mdx create mode 100644 src/content/docs/workflows/get-started/index.mdx create mode 100644 src/content/docs/workflows/index.mdx create mode 100644 src/content/docs/workflows/observability/index.mdx create mode 100644 src/content/docs/workflows/observability/metrics-analytics.mdx create mode 100644 src/content/docs/workflows/reference/changelog.mdx create mode 100644 src/content/docs/workflows/reference/glossary.mdx create mode 100644 src/content/docs/workflows/reference/how-workflows-works.mdx create mode 100644 src/content/docs/workflows/reference/index.mdx create mode 100644 src/content/docs/workflows/reference/limits.mdx create mode 100644 src/content/docs/workflows/reference/pricing.mdx create mode 100644 src/content/docs/workflows/reference/storage-options.mdx create mode 100644 src/content/docs/workflows/reference/wrangler-commands.mdx create mode 100644 src/content/docs/workflows/workflows-api.mdx create mode 100644 src/content/glossary/workflows.yaml create mode 100644 src/content/products/workflows.yaml diff --git a/src/content/changelogs/workflows.yaml b/src/content/changelogs/workflows.yaml new file mode 100644 index 000000000000000..e68ac49becfe398 --- /dev/null +++ b/src/content/changelogs/workflows.yaml @@ -0,0 +1,15 @@ +--- +link: "/workflows/platform/changelog/" +productName: Workflows +productLink: "/workflows/" +productArea: Developer platform +productAreaLink: /workflows/ +entries: + - publish_date: "2038-01-19" + title: Workflows is now in public beta. + description: |- + Workflows, a new product for building reliable, multi-step workflows using Cloudflare Workers, is now in public beta. The public beta is avaiable to any user with a [free or paid Workers plan](/workers/platform/pricing/). + + A Workflow allows you to define multiple, independent steps that encapsulate errors, automatically retry, persist state, and can run for seconds, minutes, hours or even days. A Workflow can be useful for post-processing data from R2 buckets before querying it, automating a [Workers AI RAG pipeline](/workers-ai/tutorials/build-a-retrieval-augmented-generation-ai/), or managing user signup flows and lifecycle emails. + + You can learn more about Workflows in [our announcement blog], or start building in our [get started guide]. diff --git a/src/content/docs/workflows/build/dynamic-steps.mdx b/src/content/docs/workflows/build/dynamic-steps.mdx new file mode 100644 index 000000000000000..ec8550fc6f93405 --- /dev/null +++ b/src/content/docs/workflows/build/dynamic-steps.mdx @@ -0,0 +1,35 @@ +--- +title: Dynamic steps +pcx_content_type: concept +sidebar: + order: 5 + +--- + +A Workflow does not have to define all of its steps statically: steps can be created programmatically and/or conditionally. + +This allows you to not only trigger steps based on specific input parameters, but to also name steps dynamically, set the retry configuration for a step + + +## Example + +Steps can be created on-the-fly, allowing you create a step for each parameter passed to your Workflow, for each file you want to read from storage, or for calls to third-party APIs. + +For example, you can loop over each event, label the step dynamically, and have the step operate only over that `event`: + +```ts +export class MyWorkflow extends Workflow { + async run(events: WorkflowEvent[], step: WorkflowStep) { + // Dynamically create a step for each event passed to our workflow + // ... or for every file we want to read from R2 storage + // ... or each API call we need to make based on an incoming request + for (const event of events) { + await step.do(`processing ${event.id}`, async () => { + // Step logic for one event goes here + // You can also specify a StepConfig for each step, just as you + // would for any other step + }) + } + } +} +``` \ No newline at end of file diff --git a/src/content/docs/workflows/build/index.mdx b/src/content/docs/workflows/build/index.mdx new file mode 100644 index 000000000000000..b992b0ca9c67547 --- /dev/null +++ b/src/content/docs/workflows/build/index.mdx @@ -0,0 +1,11 @@ +--- +title: Build with Workflows +pcx_content_type: navigation +sidebar: + order: 1 + +--- + +import { DirectoryListing } from "~/components" + + diff --git a/src/content/docs/workflows/build/retrying.mdx b/src/content/docs/workflows/build/retrying.mdx new file mode 100644 index 000000000000000..1b6e6428efc0a2b --- /dev/null +++ b/src/content/docs/workflows/build/retrying.mdx @@ -0,0 +1,9 @@ +--- +title: Retrying +pcx_content_type: concept +sidebar: + order: 4 + +--- + +TODO \ No newline at end of file diff --git a/src/content/docs/workflows/build/rules-of-steps.mdx b/src/content/docs/workflows/build/rules-of-steps.mdx new file mode 100644 index 000000000000000..b912d4fa7b51b1e --- /dev/null +++ b/src/content/docs/workflows/build/rules-of-steps.mdx @@ -0,0 +1,26 @@ +--- +title: Rules of Steps +pcx_content_type: concept +sidebar: + order: 10 + +--- + +A Workflow step is self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow is comprised of one or more steps. + +### Ensure API calls are idempotent + +TODO + +### Don't rely on state outside of a step + +TODO + +### Set sensible retry parameters + +TODO + +### Name your steps clearly + +TODO + diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx new file mode 100644 index 000000000000000..5d82cddbffb5c14 --- /dev/null +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -0,0 +1,86 @@ +--- +title: Trigger Workflows +pcx_content_type: concept +sidebar: + order: 3 + +--- + +TODO - intro + +## Workers API (Bindings) + +You can interact with Workflows programmatically from any Worker script by creating a binding to a Workflow. A Worker can bind to multiple Workflows, including Workflows defined in other Workers projects (scripts) within your account. + +You can interact with a Workflow: + +* Directly over HTTP via the [`fetch`](/workers/runtime-apis/handlers/fetch/) handler +* From a [Queue consumer](/queues/configuration/javascript-apis/#consumer) inside a `queue` handler +* From a [Cron Trigger](/workers/configuration/cron-triggers/) inside a `scheduled` handler +* Within a [Durable Object](/durable-objects/). + +:::note + +New to Workflows? Start with the [Workflows tutorial](/workflows/get-started/guide/) to deploy your first Workflow and familiarize yourself with Workflows concepts. + +::: + +To bind to a Workflow from your Workers code, you need to define a [binding](/workers/wrangler/configuration/) to a specific Workflow. For example, to bind to the Workflow defined in the [get started guide](/workflows/get-started/guide/), you would configure a `wrangler.toml` with the below: + +```toml title="wrangler.toml" +name = "workflows-tutorial" +main = "src/index.ts" +compatibility_date = "2024-10-15" + +[[workflows]] +# The name of the Workflow +name = "workflows-tutorial" +# The binding name, which must be a valid JavaScript variable name. This will +# be how you call (run) your Workflow from your other Workers handlers or +# scripts. +binding = "MY_WORKFLOW" + # script_name is required during for the beta. + # Must match the "name" of your Worker at the top of wrangler.toml +script_name = "workflows-tutorial" +# Must match the class defined in your code that extends the Workflow class +class_name = "MyWorkflow" +``` + +The `binding = "MY_WORKFLOW"` line defines the JavaScript variable that our Workflow methods are accessible on, including `create` (which triggers a new instance) or `get` (which returns the status of an existing instance). + +```ts title="src/index.ts" +interface Env { + MY_WORKFLOW: Workflow; +} + +export default { + async fetch(req: Request, env: Env) { + // + const instanceId = new URL(req.url).searchParams.get("instanceId") + + // If an ?instanceId= query parameter is provided, fetch the status + // of an existing Workflow by its ID. + if (instanceId) { + let instance = await env.MY_WORKFLOW.get(id); + return Response.json({ + status: await instance.status(), + }); + } + + // Else, create a new instance of our Workflow, passing in any (optional) params + // and return the ID. + const newId = await crypto.randomUUID(); + let instance = await env.MY_WORKFLOW.create(newId, {}); + return Response.json({ + id: instance.id, + details: await instance.status(), + }); + + return Response.json({ result }); + }, +}; +``` + +## REST API (HTTP) + +TODO diff --git a/src/content/docs/workflows/build/typescript.mdx b/src/content/docs/workflows/build/typescript.mdx new file mode 100644 index 000000000000000..854fd9811f5bcd2 --- /dev/null +++ b/src/content/docs/workflows/build/typescript.mdx @@ -0,0 +1,9 @@ +--- +title: Dynamic steps +pcx_content_type: concept +sidebar: + order: 6 + +--- + +TODO - outline how to use TypeScript and \ No newline at end of file diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx new file mode 100644 index 000000000000000..a431f53d249d6d2 --- /dev/null +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -0,0 +1,59 @@ +--- +title: Workers API +pcx_content_type: concept +sidebar: + order: 2 + +--- + +TODO + + +## Interacting with Workflows + +* Running a Workflow with the `run()` method +* `StepConfig` +* `NonRetriableError` +* TODO on TypeScript type params + +## Workflow + +* run(events: WorkflowEvent<T>[], step: WorkflowStep): Promise<T> + +TODO + +```ts +export class MyWorkflow extends Workflow { + async run(events: WorkflowEvent[], step: WorkflowStep) { + // TODO + } +} +``` + + +## WorkflowStep + +### step + +TODO - into to steps + +* step.do(name: string, callback: (): RpcSerializable, config?: StepConfig): Promise<T> + + * TODO - describe each param + * TODO - + +TODO - show an example +TODO - show an example of dynamically creating a step + +* step.sleep(name: string, duration: WorkflowDuration): Promise<void> + + * TODO - + +TODO - show an example + + +## StepConfig + +TODO + +## NonRetriableError \ No newline at end of file diff --git a/src/content/docs/workflows/examples/automate-lifecycle-emails.mdx b/src/content/docs/workflows/examples/automate-lifecycle-emails.mdx new file mode 100644 index 000000000000000..f6457998b11e626 --- /dev/null +++ b/src/content/docs/workflows/examples/automate-lifecycle-emails.mdx @@ -0,0 +1,15 @@ +--- +type: example +summary: Automate lifecycle emails +tags: + - Workflows + - Email +pcx_content_type: configuration +title: Automate lifecycle emails +sidebar: + order: 3 +description: Automate lifecycle emails using Workflows and Resend + +--- + +import { TabItem, Tabs } from "~/components" diff --git a/src/content/docs/workflows/examples/index.mdx b/src/content/docs/workflows/examples/index.mdx new file mode 100644 index 000000000000000..b7030341362eabd --- /dev/null +++ b/src/content/docs/workflows/examples/index.mdx @@ -0,0 +1,15 @@ +--- +type: overview +hideChildren: false +pcx_content_type: navigation +title: Examples +sidebar: + order: 6 + +--- + +import { GlossaryTooltip, ListExamples } from "~/components" + +Explore the following examples for D1. + + diff --git a/src/content/docs/workflows/examples/post-process-r2.mdx b/src/content/docs/workflows/examples/post-process-r2.mdx new file mode 100644 index 000000000000000..8e79f7b54f477cf --- /dev/null +++ b/src/content/docs/workflows/examples/post-process-r2.mdx @@ -0,0 +1,15 @@ +--- +type: example +summary: Post-process files from R2 +tags: + - Workflows + - R2 +pcx_content_type: configuration +title: Post-process files from R2 +sidebar: + order: 3 +description: Post-process files from R2 object storage using Workflows + +--- + +import { TabItem, Tabs } from "~/components" diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx new file mode 100644 index 000000000000000..ece4c6f34e26f74 --- /dev/null +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -0,0 +1,124 @@ +--- +title: CLI quick start +pcx_content_type: get-started +updated: 2038-01-19 +sidebar: + order: 3 + +--- + +import { Render, PackageManagers } from "~/components" + +:::note + +Workflows is in **public beta**, and any developer with a [free or paid Workers plan](/workers/platform/pricing/#workers) can start using Workflows immediately. + +To learn more about Workflows, read [the beta announcement blog](https://blog.cloudflare.com/THE-BLOG-SLUG-WILL-GO-HERE) and the [How Workflows Works](/workflows/build/how-workflows-works/) developer documentation. + +::: + +Workflows allow you to build durable, multi-step applications using the Workers platform. A Workflow can automatically retry, persist state, run for hours or days, and coordinate between third-party APIs. + +You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger [user lifecycle emails](TODO) using your favorite email API. + +## Prerequisites + +:::note + +This guide is for users who are already familiar with Cloudflare Workers and [durable execution](/workflows/how-workflows-works/) programming models. + +If you're new to either, we recommend the [introduction to Workflows](/workflows/get-started/guide/) guide, which steps through how a Workflow is defined, how to persist state, deploy and manage Workflows. + +::: + +Before you continue, ensure you: + +1. Sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages) if you have not already. +2. Install [`npm`](https://docs.npmjs.com/getting-started). +3. Install [`Node.js`](https://nodejs.org/en/). Use a Node version manager like [Volta](https://volta.sh/) or [nvm](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](/workers/wrangler/install-and-update/) requires a Node version of `16.17.0` or later. + +## 1. Create a Workflow + +Workflows are defined as part of a Worker script. + +To create a Workflow, use the `create cloudflare` (C3) CLI tool, specifying the Workflows starter template: + +```sh +npm create cloudflare@latest workflows-tutorial -- --template "cloudflare:workflows-tutorial" +``` + +This will create a new folder called `workflows-tutorial`, which contains two files: + +* `src/index.ts` - this is where your Worker script, including your Workflows definition, is defined. +* `wrangler.toml` - the configuration for your Workers project and your Workflow. + +Open the `src/index.ts` file in your text editor. This file contains the following code, which is the most basic instance of a Workflow definition: + +```ts title="src/index.ts" +// Import the Workflow definition +import { Workflow, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" + +// Create your own class that implements a Workflow +export class MyFirstWorkflow implements Workflow { + // Define a run() method + async run(event: WorkflowEvent, step: WorkflowStep) { + // Define one or more steps that optionally return state. + let state = step.do("my first step", async () => { + + }) + + step.do("my second step", async () => { + + }) + } +} +``` + +Specifically: + +1. TODO - describe the Workflow class +2. TODO - how params are injected +3. TODO - how state is persisted +4. TODO - retries and config +5. TODO - triggering from a fetch handler + +```ts title="src/index.ts" + + +``` + +## 2. Deploy a Workflow + +Workflows are deployed via [`wrangler](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Workers scripts, and deployed the same way. + +```sh +npx wrangler@latest deploy +``` +```sh output +TODO +``` + +## 3. Running a Workflow + +TODO - basic trigger steps +```sh +npx wrangler@latest workflows trigger workflows-tutorial --params={"hello":"world"} +``` + +TODO - trigger from a Worker + +## 4. Managing Workflows + +TODO - managing via the CLI + +* Listing +* Showing running instances +* Inspecting an instances state + +## Next steps + +* TODO +* ALSO TODO +* ALSO ALSO TODO + +If you have any feature requests or notice any bugs, share your feedback directly with the Cloudflare team by joining the [Cloudflare Developers community on Discord](https://discord.cloudflare.com). diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx new file mode 100644 index 000000000000000..c01b22c68d4accd --- /dev/null +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -0,0 +1,384 @@ +--- +title: Guide +pcx_content_type: get-started +updated: 2038-01-19 +sidebar: + order: 1 + +--- + +import { Render, PackageManagers } from "~/components" + +:::note + +Workflows is in **public beta**, and any developer with a [free or paid Workers plan](/workers/platform/pricing/#workers) can start using Workflows immediately. + +To learn more about Workflows, read [the beta announcement blog](https://blog.cloudflare.com/THE-BLOG-SLUG-WILL-GO-HERE) and the [How Workflows Works](/workflows/build/how-workflows-works/) developer documentation. + +::: + +Workflows allow you to build durable, multi-step applications using the Workers platform. A Workflow can automatically retry, persist state, run for hours or days, and coordinate between third-party APIs. + +You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger [user lifecycle emails](TODO) using your favorite email API. + +This guide will instruct you through: + +* Defining your first Workflow and publishing it +* Deploying the Workflow to your Cloudflare account +* Running (triggering) your Workflow and observing its output + +At the end of this guide, you should be able to author, deploy and debug your own Workflows applications. + +## Prerequisites + +To continue: + +1. Sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages) if you have not already. +2. Install [`npm`](https://docs.npmjs.com/getting-started). +3. Install [`Node.js`](https://nodejs.org/en/). Use a Node version manager like [Volta](https://volta.sh/) or [nvm](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](/workers/wrangler/install-and-update/) requires a Node version of `16.17.0` or later. + +## 1. Define your Workflow + +To create your first Workflow, use the `create cloudflare` (C3) CLI tool, specifying the Workflows starter template: + +```sh +npx create cloudflare@latest workflows-tutorial -- --template "cloudflare:workflows-tutorial" +``` + +This will create a new folder called `workflows-tutorial`. + +Open the `src/index.ts` file in your text editor. This file contains the following code, which is the most basic instance of a Workflow definition: + +```ts title="src/index.ts" +// Import the Workflow definition +import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" + +// Create your own class that implements a Workflow +export class MyWorkflow implements WorkflowEntrypoint { + // Define a run() method + async run(event: WorkflowEvent, step: WorkflowStep) { + // Define one or more steps that optionally return state. + const state = await step.do('my first step', async () => { + return {} + }); + + await step.sleep('wait on something', '1 minute'); + + await step.do( + 'make a call to write that could maybe, just might, fail', + async () => { + // Do stuff here, with access to 'state' from the previous step + if (Math.random() > 0.5) { + throw new Error('API call to $STORAGE_SYSTEM failed'); + } + } + ); + } +} +``` + +A Workflow definition: + +1. Defines a `run` method that contains the primary logic for your workflow. +2. Has at least one or more calls to `step.do` that encapsulates the logic of your Workflow. +3. Allows steps to return (optional) state, allowing a Workflow to continue execution even if subsequent steps fail, without having to re-run all previous steps. + +A single Worker application can contain multiple Workflow definitions, as long as each Workflow has a unique class name. This can be useful for code re-use or to define Workflows are related to each other conceptually. + +Each Workflow is otherwise entirely independent: a Worker that defines multiple Workflows is no different from a set of Workers that define one Workflow each. + +## 2. Create your Workflows steps + +Each `step` in a Workflow is an independently retriable function. + +A `step` is what makes a Workflow powerful, as you can encapsulate errors and persist state as your Workflow progresses from step to step, avoiding your application from having to start from scratch on failure and ultimately build more reliable applications. + +* A step can execute code (`step.do`) or sleep a Workflow (`step.sleep`) +* If a step fails (throws an exception), it will be automatically be retried based on your retry logic. +* Ia step succeeds, any state it returns will be persisted within the Workflow. + +At it's most basic, a step looks like this: + +```ts title="src/index.ts" +// Import the Workflow definition +import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" + +// Create your own class that implements a Workflow +export class MyWorkflow implements WorkflowEntrypoint { + // Define a run() method + async run(event: WorkflowEvent, step: WorkflowStep) { + // Define one or more steps that optionally return state. + let state = step.do("my first step", async () => { + + }) + + step.do("my second step", async () => { + + }) + } +} +``` + +Each call to `step.do` accepts three arguments: + +1. (Required) A step name, which identifies the step in logs and telemetry +2. (Required) A callback function that contains the code to run for your step, and any state you want the Workflow to persist +3. (Optional) A `StepConfig` that defines the retry configuration (max retries, delay, and backoff algorithm) for the step. + +When trying to decide whether to break code up into more than one step, a good rule of thumb is to ask "do I want _all_ of this code to run again if just one part of it fails?". In many cases, you do _not_ want to repeatedly call an API if the following data processing stage fails, or if you get an error when attempting to send a completion or welcome email. + +For example, each of the below tasks is ideally encapsulated in its own step, so that any failure β€” such as a file not existing, a third-party API being down or rate limited β€” does not cause your entire program to fail. + +* Reading or writing files from R2 +* Running an AI task using [Workers AI](/workers-ai/) +* Querying a D1 database or a database via [Hyperdrive](/hyperdrive/) +* Calling a third-party API + +If a subsequent step fails, your Workflow can retry from that step, using any state returned from a previous step. This can also help you avoid unnecessarily querying a database or calling an paid API repeatedly for data you've already fetched. + +:::note + +The term "Durable Execution" is widely used to describe this programming model. + +"Durable" describes the ability of the program (application) to implicitly persist state without you having to write to an external store or serialize program state manual + +::: + +## 3. Configure your Workflow + +Before you can deploy a Workflow, you need to configure it. + +Open the `wrangler.toml` file at the root of your `workflows-tutorial` folder, which contains the following `[[workflows]]` configuration: + +```toml title="wrangler.toml" +name = "workflows-tutorial" +main = "src/index.ts" +compatibility_date = "2024-08-20" + +[[workflows]] +# The name of the Workflow +name = "workflows-tutorial" +# The binding name, which must be a valid JavaScript variable name. This will +# be how you call (run) your Workflow from your other Workers handlers or +# scripts. +binding = "MY_WORKFLOW" + # script_name is required during for the beta. + # Must match the "name" of your Worker at the top of wrangler.toml +script_name = "workflows-tutorial" +# Must match the class defined in your code that extends the Workflow class +class_name = "MyWorkflow" +``` + +:::note + +If you have changed the name of the Workflow in your wrangler commands, the JavaScript class name, or the name of the project you created, ensure that you update the values above to match. + +::: + +This configuration tells the Workers platform which JavaScript class represents your Workflow, and sets a `binding` name that allows you to run the Workflow from other handlers or to call into Workflows from other Workers scripts. + +## 4. Bind to your Workflow + +We have a very basic Workflow definition, but now need to provide a way to call it from within our code. A Workflow can be triggered by: + +1. External HTTP requests via a `fetch()` handler +2. Messages from a [Queue](/queues/) +3. A schedule via [Cron Trigger](/workers/configuration/cron-triggers/) +4. Via the [Workflows REST API](TODO) or [wrangler CLI](/workers/wrangler/commands/#workflows) + +Return to the `src/index.ts` file we created in the previous step and add a `fetch` handler that _binds_ to our Workflow. This binding allows us to create new Workflow instances, fetch the status of an existing Workflow, pause and/or terminate a Workflow. + +```ts title="src/index.ts" +// This can be in the same file as our Workflow definition + +export default { + async fetch(req: Request, env: Env) { + // + const instanceId = new URL(req.url).searchParams.get("instanceId") + + // If an ?instanceId= query parameter is provided, fetch the status + // of an existing Workflow by its ID. + if (instanceId) { + let instance = await env.MYWORKFLOW.get(id); + return Response.json({ + status: await instance.status(), + }); + } + + // Else, create a new instance of our Workflow, passing in any (optional) params + // and return the ID. + const newId = await crypto.randomUUID(); + let instance = await env.MYWORKFLOW.create(newId, {}); + return Response.json({ + id: instance.id, + details: await instance.status(), + }); + + return Response.json({ result }); + }, +}; +``` + +The code here exposes a HTTP endpoint that generates a random ID and runs the Workflow, returning the ID and the Workflow status. It also accepts an optional `instanceId` query parameter that retrieves the status of a Workflow instance by its ID. + +:::note + +In a production application, you might choose to put authentication in front of your endpoint so that only authorized users can run a Workflow. Alternatively, you could pass messages to a Workflow [from a Queue consumer](/queues/reference/how-queues-works/#consumers) in order to allow for long-running tasks. + +::: + +## 6. Deploy your Workflow + +Deploying a Workflow is identical to deploying a Worker. + +```sh +npx wrangler deploy +``` +```sh output +TODO +``` + +A Worker with a valid Workflow definition will be automatically registered by Workflows. You can list your current Workflows using wrangler: + +```sh +npx wrangler workflows list +``` +```sh output +TODO +``` + +TODO - describe the output, Workflows, instances, etc. + + +## 6. Run and observe your Workflow + +With your Workflow deployed, you can now run it. + +1. A Workflow can run in parallel: each unique invocation of a Workflow is an _instance_ of that Workflow. +2. An instance will run to completion (success or failure) +3. Deploying newer versions of a Workflow will cause all instances after that point to run the newest Workflow code. + +:::note + +Because Workflows can be long running, it's possible to have running instances that represent different versions of your Workflow code over time. + +::: + + + +To trigger our Workflow, we will use the `wrangler` CLI and pass in an optional `--payload`. The `payload` will be passed to your Workflow's `run` method handler as an `Event` + +```sh +npx wrangler workflows trigger my-workflow '{"hello":"world"}' +``` +```sh output +# Workflow instance "12dc179f-9f77-4a37-b973-709dca4189ba" has been queued successfully +``` + +To inspect the current status of the Workflow instance we just triggered, we can either reference it by ID or by using the keyword `latest`: + +```sh +npx wrangler@workflows workflows instances describe my-workflow latest +# Or by ID: +# npx wrangler@workflows workflows instances describe my-workflow 12dc179f-9f77-4a37-b973-709dca4189ba +``` +```sh output +Workflow Name: my-workflow +Instance Id: f72c1648-dfa3-45ea-be66-b43d11d216f8 +Version Id: cedc33a0-11fa-4c26-8a8e-7d28d381a291 +Status: βœ… Completed +Trigger: 🌎 API +Queued: 10/15/2024, 1:55:31 PM +Success: βœ… Yes +Start: 10/15/2024, 1:55:31 PM +End: 10/15/2024, 1:56:32 PM +Duration: 1 minute +Last Successful Step: make a call to write that could maybe, just might, fail-1 +Steps: + + Name: my first step-1 + Type: 🎯 Step + Start: 10/15/2024, 1:55:31 PM + End: 10/15/2024, 1:55:31 PM + Duration: 0 seconds + Success: βœ… Yes + Output: "{\"inputParams\":[{\"timestamp\":\"2024-10-15T13:55:29.363Z\",\"payload\":{\"hello\":\"world\"}}],\"files\":[\"doc_7392_rev3.pdf\",\"report_x29_final.pdf\",\"memo_2024_05_12.pdf\",\"file_089_update.pdf\",\"proj_alpha_v2.pdf\",\"data_analysis_q2.pdf\",\"notes_meeting_52.pdf\",\"summary_fy24_draft.pdf\",\"plan_2025_outline.pdf\"]}" +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 10/15/2024, 1:55:31 PM β”‚ 10/15/2024, 1:55:31 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + + Name: some other step-1 + Type: 🎯 Step + Start: 10/15/2024, 1:55:31 PM + End: 10/15/2024, 1:55:31 PM + Duration: 0 seconds + Success: βœ… Yes + Output: "{\"result\":{\"ipv4_cidrs\":[\"173.245.48.0/20\",\"103.21.244.0/22\",\"103.22.200.0/22\",\"103.31.4.0/22\",\"141.101.64.0/18\",\"108.162.192.0/18\",\"190.93.240.0/20\",\"188.114.96.0/20\",\"197.234.240.0/22\",\"198.41.128.0/17\",\"162.158.0.0/15\",\"104.16.0.0/13\",\"104.24.0.0/14\",\"172.64.0.0/13\",\"131.0.72.0/22\"],\"ipv6_cidrs\":[\"2400:cb00::/32\",\"2606:4700::/32\",\"2803:f800::/32\",\"2405:b500::/32\",\"2405:8100::/32\",\"2a06:98c0::/29\",\"2c0f:f248::/32\"],\"etag\":\"38f79d050aa027e3be3865e495dcc9bc\"},\"success\":true,\"errors\":[],\"messages\":[]}" +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 10/15/2024, 1:55:31 PM β”‚ 10/15/2024, 1:55:31 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + + Name: wait on something-1 + Type: πŸ’€ Sleeping + Start: 10/15/2024, 1:55:31 PM + End: 10/15/2024, 1:56:31 PM + Duration: 1 minute + + Name: make a call to write that could maybe, just might, fail-1 + Type: 🎯 Step + Start: 10/15/2024, 1:56:31 PM + End: 10/15/2024, 1:56:32 PM + Duration: 1 second + Success: βœ… Yes + Output: null +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ Error β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 10/15/2024, 1:56:31 PM β”‚ 10/15/2024, 1:56:31 PM β”‚ 0 seconds β”‚ ❌ Error β”‚ Error: API call to $STORAGE_SYSTEM failed β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 10/15/2024, 1:56:32 PM β”‚ 10/15/2024, 1:56:32 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +From the output above, we can inspect: + +* The status (success, failure, running) of each step +* Any state emitted by the step +* Any `sleep` state, including when the Workflow will wake up +* Retries associated with each step. +* Errors, including exception messages + +:::note + +You do not have to wait for a Workflow instance to finish executing to inspect its current status. The `wrangler workflows instances describe` sub-command will show the status of an in-progress instance, including any persisted state, if it is sleeping, and any errors or retries. This can be especially useful when debugging a Workflow during development. + +::: + + +In the previous step, we also bound a Workers script to our Workflow. You can trigger a Workflow by visiting the (deployed) Workers script in a browser or with any HTTP client. + +```sh +# This must match the URL provided in step 6 +curl -s https://workflows-tutorial.YOUR_WORKERS_SUBDOMAIN.workers.dev/ +``` +```sh output +{"id":"16ac31e5-db9d-48ae-a58f-95b95422d0fa","details":{"status":"queued","error":null,"output":null}} +``` + +## 7. (Optional) Clean up + +TODO - delete the Workflow. + +--- + +## Next steps + +- learn more +- observability +- trigger workflows on a schedule or from a Queue + +If you have any feature requests or notice any bugs, share your feedback directly with the Cloudflare team by joining the [Cloudflare Developers community on Discord](https://discord.cloudflare.com). diff --git a/src/content/docs/workflows/get-started/index.mdx b/src/content/docs/workflows/get-started/index.mdx new file mode 100644 index 000000000000000..f318b640b29cb8d --- /dev/null +++ b/src/content/docs/workflows/get-started/index.mdx @@ -0,0 +1,11 @@ +--- +title: Get started +pcx_content_type: navigation +sidebar: + order: 1 + +--- + +import { DirectoryListing } from "~/components" + + diff --git a/src/content/docs/workflows/index.mdx b/src/content/docs/workflows/index.mdx new file mode 100644 index 000000000000000..cdf4ebab1499587 --- /dev/null +++ b/src/content/docs/workflows/index.mdx @@ -0,0 +1,103 @@ +--- +title: Overview +order: 0 +type: overview +pcx_content_type: overview +sidebar: + order: 1 +head: + - tag: title + content: Cloudflare D1 + +--- + +import { CardGrid, Description, Feature, LinkTitleCard, Plan, RelatedProduct } from "~/components" + + + + +Create new serverless SQL databases to query from your Workers and Pages projects. + + + + + + +D1 is Cloudflare’s native serverless database. D1 allows you to build applications that handle large amounts of users at no extra cost. With D1, you can restore your database to any minute within the last 30 days. + +Create your first D1 database by [following the Get started guide](/d1/get-started/), learn how to [import data into a database](/d1/build-with-d1/import-export-data/), and how to [interact with your database](/d1/build-with-d1/d1-client-api/) directly from [Workers](/workers/) or [Pages](/pages/functions/bindings/#d1-databases). + +*** + +## Features + + + +Create your first D1 database, establish a schema, import data and query D1 directly from an application [built with Workers](/workers/). + + + + + + +Execute SQL with SQLite's SQL compatibility and D1 Client API. + + + + + + +Time Travel is D1’s approach to backups and point-in-time-recovery, and allows you to restore a database to any minute within the last 30 days. + + + + +*** + +## Related products + + + +Build serverless applications and deploy instantly across the globe for exceptional performance, reliability, and scale. + + + + + + +Deploy dynamic front-end applications in record time. + + + + +*** + +## More resources + + + + +Learn about D1's pricing and how to estimate your usage. + + + +Learn about what limits D1 has and how to work within them. + + + +Browse what developers are building with D1. + + + +Learn more about the storage and database options you can build on with Workers. + + + +Connect with the Workers community on Discord to ask questions, show what you are building, and discuss the platform with other developers. + + + +Follow @CloudflareDev on Twitter to learn about product announcements, and what is new in Cloudflare Developer Platform. + + + diff --git a/src/content/docs/workflows/observability/index.mdx b/src/content/docs/workflows/observability/index.mdx new file mode 100644 index 000000000000000..a1d9cc8345878d0 --- /dev/null +++ b/src/content/docs/workflows/observability/index.mdx @@ -0,0 +1,11 @@ +--- +title: Observability +pcx_content_type: navigation +sidebar: + order: 5 + +--- + +import { DirectoryListing } from "~/components" + + diff --git a/src/content/docs/workflows/observability/metrics-analytics.mdx b/src/content/docs/workflows/observability/metrics-analytics.mdx new file mode 100644 index 000000000000000..b67c0160cb13e30 --- /dev/null +++ b/src/content/docs/workflows/observability/metrics-analytics.mdx @@ -0,0 +1,29 @@ +--- +pcx_content_type: concept +title: Metrics and analytics +sidebar: + order: 10 + +--- + +D1 exposes database analytics that allow you to inspect query volume, query latency, and storage size across all and/or each database in your account. + +The metrics displayed in the [Cloudflare dashboard](https://dash.cloudflare.com/) charts are queried from Cloudflare’s [GraphQL Analytics API](/analytics/graphql-api/). You can access the metrics [programmatically](#query-via-the-graphql-api) via GraphQL or HTTP client. + +## Metrics + +D1 currently exports the below metrics: + +| Metric | GraphQL Field Name | Description | +| ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| Read Queries (qps) | `readQueries` | The number of read queries issued against a database. This is the raw number of read queries, and is not used for billing. | +| Write Queries (qps) | `writeQueries` | The number of write queries issued against a database. This is the raw number of write queries, and is not used for billing. | +| Rows read (count) | `rowsRead` | The number of rows read (scanned) across your queries. See [Pricing](/d1/platform/pricing/) for more details on how rows are counted. | +| Rows written (count) | `rowsWritten` | The number of rows written across your queries. | +| Query Response (bytes) | `queryBatchResponseBytes` | The total response size of the serialized query response, including any/all column names, rows and metadata. Reported in bytes. | +| Query Latency (ms) | `queryBatchTimeMs` | The total query response time, including response serialization, on the server-side. Reported in milliseconds. | +| Storage (Bytes) | `databaseSizeBytes` | Maximum size of a database. Reported in bytes. | + +Metrics can be queried (and are retained) for the past 31 days. + +### Row count \ No newline at end of file diff --git a/src/content/docs/workflows/reference/changelog.mdx b/src/content/docs/workflows/reference/changelog.mdx new file mode 100644 index 000000000000000..1e63decb2fa0897 --- /dev/null +++ b/src/content/docs/workflows/reference/changelog.mdx @@ -0,0 +1,15 @@ +--- +pcx_content_type: changelog +title: Changelog +changelog_file_name: + - workflows +sidebar: + order: 99 + +--- + +import { ProductChangelog } from "~/components" + +{/* */} + + diff --git a/src/content/docs/workflows/reference/glossary.mdx b/src/content/docs/workflows/reference/glossary.mdx new file mode 100644 index 000000000000000..a00fee9cc25fbf9 --- /dev/null +++ b/src/content/docs/workflows/reference/glossary.mdx @@ -0,0 +1,13 @@ +--- +title: Glossary +pcx_content_type: glossary +sidebar: + order: 10 + +--- + +import { Glossary } from "~/components" + +Review the definitions for terms used across Cloudflare's Workflows documentation. + + diff --git a/src/content/docs/workflows/reference/how-workflows-works.mdx b/src/content/docs/workflows/reference/how-workflows-works.mdx new file mode 100644 index 000000000000000..43aad05f0a3b1ea --- /dev/null +++ b/src/content/docs/workflows/reference/how-workflows-works.mdx @@ -0,0 +1,27 @@ +--- +title: How Workflows Works +pcx_content_type: concept +sidebar: + order: 1 + +--- + +TODO + +## Data model + +- Workflow +- Version +- Instances + +## Workflow + +TODO + +## Version + +TODO + +## Instance + +TODO \ No newline at end of file diff --git a/src/content/docs/workflows/reference/index.mdx b/src/content/docs/workflows/reference/index.mdx new file mode 100644 index 000000000000000..8516bf0f828bd96 --- /dev/null +++ b/src/content/docs/workflows/reference/index.mdx @@ -0,0 +1,11 @@ +--- +pcx_content_type: navigation +title: Platform +sidebar: + order: 8 + +--- + +import { DirectoryListing } from "~/components" + + diff --git a/src/content/docs/workflows/reference/limits.mdx b/src/content/docs/workflows/reference/limits.mdx new file mode 100644 index 000000000000000..d376d97bf53cac1 --- /dev/null +++ b/src/content/docs/workflows/reference/limits.mdx @@ -0,0 +1,33 @@ +--- +pcx_content_type: concept +title: Limits +sidebar: + order: 4 + +--- + +import { Render } from "~/components" + +Limits that apply to authoring, deploying and running Workflows are detailed below. + +Many limits are inherited from those applied to Workers scripts and as documented in the [Workers limits](/workers/platform/limits/) documentation. + +| Feature | Workers Free | Workers Paid | +| ----------------------------------------- | ----------------------- | --------------------- | +| Workflow class definitions per script | 1MB max script size per [Worker size limits](/workers/platform/limits/#account-plan-limits) | 1MB max script size per [Worker size limits](/workers/platform/limits/#account-plan-limits) +| Total scripts per account | 100 | 500 (shared with [Worker script limits](/workers/platform/limits/#account-plan-limits) | +| Compute time per Workflow | 10 seconds | 30 seconds of [active CPU time](/workers/platform/limits/#cpu-time) | +| Duration (wall clock) per `step` | Unlimited | Unlimited - e.g. waiting on network I/O calls or querying a database | +| Maximum persisted state per step | 1MB (1e6 bytes) | 1MB (1e6 bytes) | +| Maximum state that can be persisted per Workflow instance | 100MB | 1GB | +| Maximum `step.sleep` duration | 365 days (1 year) beta | +| Maximum steps per Workflow | 256 beta | +| Maximum Workflow executions | 100,000 per day [shared with Workers daily limit](/workers/platform/limits/#worker-limits) | Unlimited | +| Concurrent Workflow instances (executions) | 25 | 1,000 beta | +| Retention limit for completed Workflow state | 3 days | 30 days 1 | + +beta This limit will be reviewed and revised during the open beta for Workflows, and we'll update this page and our changelog. + +1 Workflow state and logs will be retained for 3 days on the Workers Free plan and 30 days on the Workers Paid plan. + + diff --git a/src/content/docs/workflows/reference/pricing.mdx b/src/content/docs/workflows/reference/pricing.mdx new file mode 100644 index 000000000000000..520fdc921bc603c --- /dev/null +++ b/src/content/docs/workflows/reference/pricing.mdx @@ -0,0 +1,44 @@ +--- +pcx_content_type: concept +title: Pricing +sidebar: + order: 2 + +--- + +import { Render } from "~/components" + +# Pricing + +:::note + +Workflows is included in both the Free and Paid [Workers plans](/workers/platform/pricing/#workers). + +::: + +Workflows pricing is identical to [Workers Standard pricing](/workers/platform/pricing/#workers) and are billed on two dimensions: + +* **CPU time**: the total amount of compute (measured in milliseconds) consumed by a given Workflow. +* **Requests** (invocations): the number of Workflow invocations. [Subrequests](/workers/platform/limits/#subrequests) made from a Workflow do not incur additional request costs. + +A Workflow that is waiting on a response to an API call, paused as a result of calling `step.sleep`, or otherwise idle does not incur CPU time. + +## Frequently Asked Questions + +Frequently asked questions related to Workflows pricing: + +- Are there additional costs for Workflows? + +No. Workflows are priced based on the same compute (CPU time) and requests (invocations) as Workers. + +- Is Workflows available on the [Workers Free](/workers/platform/pricing/#workers) plan? + +Yes. + +- How does Workflows show up on my bill? + +Workflows are billed as Workers, and share the same CPU time and request SKUs. + +- Are there any limits to Workflows? + +Refer to the published [limits](/workflows/reference/limits/) documentation. diff --git a/src/content/docs/workflows/reference/storage-options.mdx b/src/content/docs/workflows/reference/storage-options.mdx new file mode 100644 index 000000000000000..312ac6fdd1cbdd3 --- /dev/null +++ b/src/content/docs/workflows/reference/storage-options.mdx @@ -0,0 +1,8 @@ +--- +pcx_content_type: navigation +title: Choose a data or storage product +external_link: /workers/platform/storage-options/ +sidebar: + order: 90 + +--- diff --git a/src/content/docs/workflows/reference/wrangler-commands.mdx b/src/content/docs/workflows/reference/wrangler-commands.mdx new file mode 100644 index 000000000000000..7253dd6bea29f10 --- /dev/null +++ b/src/content/docs/workflows/reference/wrangler-commands.mdx @@ -0,0 +1,8 @@ +--- +pcx_content_type: navigation +title: Wrangler commands +external_link: /workers/wrangler/commands/#workflows +sidebar: + order: 80 + +--- diff --git a/src/content/docs/workflows/workflows-api.mdx b/src/content/docs/workflows/workflows-api.mdx new file mode 100644 index 000000000000000..a61fb4f3c00ebff --- /dev/null +++ b/src/content/docs/workflows/workflows-api.mdx @@ -0,0 +1,8 @@ +--- +pcx_content_type: navigation +title: Workflows REST API +external_link: /api/operations/cloudflare-d1-create-database +sidebar: + order: 10 + +--- diff --git a/src/content/glossary/workflows.yaml b/src/content/glossary/workflows.yaml new file mode 100644 index 000000000000000..67692867cd73eef --- /dev/null +++ b/src/content/glossary/workflows.yaml @@ -0,0 +1,22 @@ +--- +productName: Workflows +entries: +- term: "Workflow" + general_definition: |- + The named Workflow definition, associated with a single Workers script. + +- term: "instance" + general_definition: |- + A specific instance (running, paused, errored) of a Workflow. A Workflow can have a potentially infinite number of instances. + +- term: "Step" + general_definition: |- + A step is self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow can have one or more steps up to the [step limit](/workflows/reference/limits/). + +- term: "Event" + general_definition: |- + The event that triggered the Workflow instance. A `WorkflowEvent` may contain optional parameters (data) that a Workflow can operate on. + +- term: "Durable Execution" + general_definition: |- + "Durable Execution" is a programming model that allows applications to execute reliably, automatically persist state, retry, and be resistant to errors caused by API, network or even machine/infrastructure failures. Cloudflare Workflows provides a way to build and deploy applications that align with this model. diff --git a/src/content/products/workflows.yaml b/src/content/products/workflows.yaml new file mode 100644 index 000000000000000..3ca59672433c4a6 --- /dev/null +++ b/src/content/products/workflows.yaml @@ -0,0 +1,33 @@ +name: Workflows + +product: + title: Workflows + url: /workflows/ + group: Developer platform + preview_tryout: true + +meta: + title: Cloudflare Workflows docs + description: Build durable, multi-step applications using the Workers platform + author: '@cloudflare' + +resources: + community: https://community.cloudflare.com/c/developers/workers/40 + dashboard_link: https://dash.cloudflare.com/?to=/:account/workers + learning_center: https://www.cloudflare.com/learning/serverless/what-is-serverless/ + discord: https://discord.com/invite/cloudflaredev + +externals: + - title: Workers home + url: https://workers.cloudflare.com + - title: Playground + url: https://workers.cloudflare.com/playground + - title: Pricing + url: https://workers.cloudflare.com/#plans + - title: Discord + url: https://discord.cloudflare.com + +algolia: + index: developers-cloudflare2 + apikey: 4edb0a6cef3338ff4bcfbc6b3d2db56b + product: workflows From a93db6e6019a3bb427a051a440bdb0a024a2687a Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 11:18:49 +0100 Subject: [PATCH 02/54] icon --- src/icons/workflows.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/icons/workflows.svg diff --git a/src/icons/workflows.svg b/src/icons/workflows.svg new file mode 100644 index 000000000000000..8bd10c77fe52bcf --- /dev/null +++ b/src/icons/workflows.svg @@ -0,0 +1 @@ + \ No newline at end of file From 1e77cce49c1254763269a3ada5793f189804759c Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 11:31:20 +0100 Subject: [PATCH 03/54] fix link --- src/content/docs/workflows/get-started/cli-quick-start.mdx | 2 +- src/content/docs/workflows/get-started/guide.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index ece4c6f34e26f74..325db6ffb30b5ce 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -25,7 +25,7 @@ You can build Workflows to post-process file uploads to [R2 object storage](/r2/ :::note -This guide is for users who are already familiar with Cloudflare Workers and [durable execution](/workflows/how-workflows-works/) programming models. +This guide is for users who are already familiar with Cloudflare Workers and [durable execution](/workflows/reference/how-workflows-works/) programming models. If you're new to either, we recommend the [introduction to Workflows](/workflows/get-started/guide/) guide, which steps through how a Workflow is defined, how to persist state, deploy and manage Workflows. diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index c01b22c68d4accd..86ac4ee4c33f87e 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -13,7 +13,7 @@ import { Render, PackageManagers } from "~/components" Workflows is in **public beta**, and any developer with a [free or paid Workers plan](/workers/platform/pricing/#workers) can start using Workflows immediately. -To learn more about Workflows, read [the beta announcement blog](https://blog.cloudflare.com/THE-BLOG-SLUG-WILL-GO-HERE) and the [How Workflows Works](/workflows/build/how-workflows-works/) developer documentation. +To learn more about Workflows, read [the beta announcement blog](https://blog.cloudflare.com/THE-BLOG-SLUG-WILL-GO-HERE) and the [How Workflows Works](/workflows/reference/how-workflows-works/) developer documentation. ::: From 2e6f4fa4f1e905628f095f435a1303cc91ddd314 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 11:48:07 +0100 Subject: [PATCH 04/54] rename to workflows-starter --- .../docs/workflows/get-started/guide.mdx | 37 +++++++++---------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 86ac4ee4c33f87e..a571031a2db9e7b 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -42,10 +42,10 @@ To continue: To create your first Workflow, use the `create cloudflare` (C3) CLI tool, specifying the Workflows starter template: ```sh -npx create cloudflare@latest workflows-tutorial -- --template "cloudflare:workflows-tutorial" +npx create cloudflare@latest workflows-starter -- --template "cloudflare:workflows-starter" ``` -This will create a new folder called `workflows-tutorial`. +This will create a new folder called `workflows-starter`. Open the `src/index.ts` file in your text editor. This file contains the following code, which is the most basic instance of a Workflow definition: @@ -148,25 +148,24 @@ The term "Durable Execution" is widely used to describe this programming model. Before you can deploy a Workflow, you need to configure it. -Open the `wrangler.toml` file at the root of your `workflows-tutorial` folder, which contains the following `[[workflows]]` configuration: +Open the `wrangler.toml` file at the root of your `workflows-starter` folder, which contains the following `[[workflows]]` configuration: ```toml title="wrangler.toml" -name = "workflows-tutorial" +#:schema node_modules/wrangler/config-schema.json +name = "workflows-starter" main = "src/index.ts" -compatibility_date = "2024-08-20" +compatibility_date = "2024-10-16" [[workflows]] -# The name of the Workflow -name = "workflows-tutorial" -# The binding name, which must be a valid JavaScript variable name. This will -# be how you call (run) your Workflow from your other Workers handlers or -# scripts. +# name of your workflow +name = "workflows-starter" +# binding name env.MYWORKFLOW binding = "MY_WORKFLOW" - # script_name is required during for the beta. - # Must match the "name" of your Worker at the top of wrangler.toml -script_name = "workflows-tutorial" -# Must match the class defined in your code that extends the Workflow class +# this is class that extends the Workflow class in src/index.ts class_name = "MyWorkflow" +# script_name is required during for the beta. + # Must match the "name" of your Worker at the top of wrangler.toml +script_name = "workflows-starter" ``` :::note @@ -269,7 +268,7 @@ Because Workflows can be long running, it's possible to have running instances t To trigger our Workflow, we will use the `wrangler` CLI and pass in an optional `--payload`. The `payload` will be passed to your Workflow's `run` method handler as an `Event` ```sh -npx wrangler workflows trigger my-workflow '{"hello":"world"}' +npx wrangler workflows trigger workflows-starter '{"hello":"world"}' ``` ```sh output # Workflow instance "12dc179f-9f77-4a37-b973-709dca4189ba" has been queued successfully @@ -278,12 +277,12 @@ npx wrangler workflows trigger my-workflow '{"hello":"world"}' To inspect the current status of the Workflow instance we just triggered, we can either reference it by ID or by using the keyword `latest`: ```sh -npx wrangler@workflows workflows instances describe my-workflow latest +npx wrangler@workflows workflows instances describe workflows-starter latest # Or by ID: -# npx wrangler@workflows workflows instances describe my-workflow 12dc179f-9f77-4a37-b973-709dca4189ba +# npx wrangler@workflows workflows instances describe workflows-starter 12dc179f-9f77-4a37-b973-709dca4189ba ``` ```sh output -Workflow Name: my-workflow +Workflow Name: workflows-starter Instance Id: f72c1648-dfa3-45ea-be66-b43d11d216f8 Version Id: cedc33a0-11fa-4c26-8a8e-7d28d381a291 Status: βœ… Completed @@ -363,7 +362,7 @@ In the previous step, we also bound a Workers script to our Workflow. You can tr ```sh # This must match the URL provided in step 6 -curl -s https://workflows-tutorial.YOUR_WORKERS_SUBDOMAIN.workers.dev/ +curl -s https://workflows-starter.YOUR_WORKERS_SUBDOMAIN.workers.dev/ ``` ```sh output {"id":"16ac31e5-db9d-48ae-a58f-95b95422d0fa","details":{"status":"queued","error":null,"output":null}} From 61869e61eb18550ad969e089e2f467e26ede3d1c Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 11:50:43 +0100 Subject: [PATCH 05/54] fix limit --- src/content/docs/workflows/reference/limits.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/reference/limits.mdx b/src/content/docs/workflows/reference/limits.mdx index d376d97bf53cac1..352d61393c3d82a 100644 --- a/src/content/docs/workflows/reference/limits.mdx +++ b/src/content/docs/workflows/reference/limits.mdx @@ -14,7 +14,7 @@ Many limits are inherited from those applied to Workers scripts and as documente | Feature | Workers Free | Workers Paid | | ----------------------------------------- | ----------------------- | --------------------- | -| Workflow class definitions per script | 1MB max script size per [Worker size limits](/workers/platform/limits/#account-plan-limits) | 1MB max script size per [Worker size limits](/workers/platform/limits/#account-plan-limits) +| Workflow class definitions per script | 1MB max script size per [Worker size limits](/workers/platform/limits/#account-plan-limits) | 10MB max script size per [Worker size limits](/workers/platform/limits/#account-plan-limits) | Total scripts per account | 100 | 500 (shared with [Worker script limits](/workers/platform/limits/#account-plan-limits) | | Compute time per Workflow | 10 seconds | 30 seconds of [active CPU time](/workers/platform/limits/#cpu-time) | | Duration (wall clock) per `step` | Unlimited | Unlimited - e.g. waiting on network I/O calls or querying a database | From 26db15e9da57aa2a75c85b348d130720cf4574d6 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 11:52:32 +0100 Subject: [PATCH 06/54] mebibyte --- src/content/docs/workflows/reference/limits.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/reference/limits.mdx b/src/content/docs/workflows/reference/limits.mdx index 352d61393c3d82a..5a9f7c22f3723a1 100644 --- a/src/content/docs/workflows/reference/limits.mdx +++ b/src/content/docs/workflows/reference/limits.mdx @@ -18,7 +18,7 @@ Many limits are inherited from those applied to Workers scripts and as documente | Total scripts per account | 100 | 500 (shared with [Worker script limits](/workers/platform/limits/#account-plan-limits) | | Compute time per Workflow | 10 seconds | 30 seconds of [active CPU time](/workers/platform/limits/#cpu-time) | | Duration (wall clock) per `step` | Unlimited | Unlimited - e.g. waiting on network I/O calls or querying a database | -| Maximum persisted state per step | 1MB (1e6 bytes) | 1MB (1e6 bytes) | +| Maximum persisted state per step | 1MiB (2^20 bytes) | 1MiB (2^20 bytes) | | Maximum state that can be persisted per Workflow instance | 100MB | 1GB | | Maximum `step.sleep` duration | 365 days (1 year) beta | | Maximum steps per Workflow | 256 beta | From 7badf0d6f28954a77a6b960928bb712afad3bfaf Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 15:09:01 +0100 Subject: [PATCH 07/54] events and params --- .../workflows/build/events-and-parameters.mdx | 104 ++++++++++++++++++ .../docs/workflows/build/rules-of-steps.mdx | 1 + .../docs/workflows/build/typescript.mdx | 9 -- 3 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 src/content/docs/workflows/build/events-and-parameters.mdx delete mode 100644 src/content/docs/workflows/build/typescript.mdx diff --git a/src/content/docs/workflows/build/events-and-parameters.mdx b/src/content/docs/workflows/build/events-and-parameters.mdx new file mode 100644 index 000000000000000..2c85e6d000db03f --- /dev/null +++ b/src/content/docs/workflows/build/events-and-parameters.mdx @@ -0,0 +1,104 @@ +--- +title: Events and parameters +pcx_content_type: concept +sidebar: + order: 10 + +--- + +When a Workflow is triggered, it can receive an optional event. This event can include data that your Workflow can act on, including request details, user data fetched from your database (such as D1 or KV) or from a webhook, or messages from a Queue consumer. + +Events are a powerful part of a Workflow, as you often want a Workflow to act on data. Because a given Workflow instance executes durably, events are a useful way to provide a Workflow with data that should be immutable (not changing) and/or represents data the Workflow needs to operate on at that point in time. + +## Passing parameters + +You can pass parameters to a Workflow in two ways: + +* As an optional argument to the `create` method on a [Workflow binding] when triggering a Workflow from a Worker. +* Via the `--params` flag when using the `wrangler` CLI to trigger a Workflow. + +You can pass any JSON-serializable object as a parameter. + +:::caution + +A `WorkflowEvent` and its associated `payload` property are effectively _immutable_: any changes to an event are not persisted across the steps of a Workflow. This includes both cases when a Workflow is progressing normally, and in cases where a Workflow has to be restarted due to a failure. + +Store state durably by returning it from your `step.do` callbacks. + +::: + +```ts +export default { + async fetch(req: Request, env: Env) { + let someEvent = { url: req.url, createdTimestamp: Date.now() } + // Trigger our Workflow + // Pass our event as the second parameter to the `create` method + // on our Workflow binding. + let instance = await env.MYWORKFLOW.create(await crypto.randomUUID(), someEvent); + + return Response.json({ + id: instance.id, + details: await instance.status(), + }); + + return Response.json({ result }); + }, +}; +``` + +To pass parameters via the `wrangler` command-line interface, pass a JSON string as the second parameter to the `workflows trigger` sub-command: + +```sh +npx wrangler@latest workflows trigger workflows-starter '{"some":"data"}' +``` +```sh output +πŸš€ Workflow instance "57c7913b-8e1d-4a78-a0dd-dce5a0b7aa30" has been queued successfully +``` + +## TypeScript and typing events + +By default, the `WorkflowEvent` passed to the `run` method of your Workflow definition has a type that conforms to the following, with `payload` (your data) and `timestamp` properties: + +```ts +export type WorkflowEvent = { + // The data passed as the parameter when the Workflow instance was triggered + payload: T; + // The timestamp that the Workflow was triggered + timestamp: Date; +}; +``` + +You can optionally type these events by defining your own type and passing it as a [type parameter](https://www.typescriptlang.org/docs/handbook/2/generics.html#working-with-generic-type-variables) to the `WorkflowEvent`: + +```ts +// Define a type that conforms to the events your Workflow instance is +// instantiated with +interface YourEventType { + userEmail: string; + createdTimestamp: number; + metadata?: Record; +} +``` + +When we pass our `YourEventType` to `WorkflowEvent` as a type parameter, the `event.payload` property now has the type `YourEventType` throughout our workflow definition: + +```ts title="src/index.ts" +// Import the Workflow definition +import { Workflow, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" + +export class MyWorkflow implements Workflow { + // Pass your type as a type parameter to WorkflowEvent + // The 'payload' property will have the type of your parameter. + async run(event: WorkflowEvent, step: WorkflowStep) { + let state = 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 */ ) + } +} +``` + +Note that type parameters do not _validate_ that the incoming event matches your type definition. Properties (fields) that do not exist or conform to the type you provided will be dropped. If you need to validate incoming events, we recommend a library such as [zod](https://zod.dev/) or your own validator logic. diff --git a/src/content/docs/workflows/build/rules-of-steps.mdx b/src/content/docs/workflows/build/rules-of-steps.mdx index b912d4fa7b51b1e..0dec4cebe693c52 100644 --- a/src/content/docs/workflows/build/rules-of-steps.mdx +++ b/src/content/docs/workflows/build/rules-of-steps.mdx @@ -24,3 +24,4 @@ TODO TODO +### Don't mutate your incoming events diff --git a/src/content/docs/workflows/build/typescript.mdx b/src/content/docs/workflows/build/typescript.mdx deleted file mode 100644 index 854fd9811f5bcd2..000000000000000 --- a/src/content/docs/workflows/build/typescript.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Dynamic steps -pcx_content_type: concept -sidebar: - order: 6 - ---- - -TODO - outline how to use TypeScript and \ No newline at end of file From e0c3786f940830296c68f2e5313ee50d1d346acd Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 15:11:31 +0100 Subject: [PATCH 08/54] npx -> npm --- src/content/docs/workflows/build/rules-of-steps.mdx | 4 ++++ src/content/docs/workflows/get-started/guide.mdx | 10 +++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/content/docs/workflows/build/rules-of-steps.mdx b/src/content/docs/workflows/build/rules-of-steps.mdx index 0dec4cebe693c52..57c965d27e3e42f 100644 --- a/src/content/docs/workflows/build/rules-of-steps.mdx +++ b/src/content/docs/workflows/build/rules-of-steps.mdx @@ -8,6 +8,10 @@ sidebar: A Workflow step is self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow is comprised of one or more steps. +### Make your steps granular + +TODO - step is a transaction, should be a self-contained logic. If you have multiple API calls, seperate them into their own steps. + ### Ensure API calls are idempotent TODO diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index a571031a2db9e7b..4bd9c66f78e998f 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -42,7 +42,7 @@ To continue: To create your first Workflow, use the `create cloudflare` (C3) CLI tool, specifying the Workflows starter template: ```sh -npx create cloudflare@latest workflows-starter -- --template "cloudflare:workflows-starter" +npm create cloudflare@latest workflows-starter -- --template "cloudflare:workflows-starter" ``` This will create a new folder called `workflows-starter`. @@ -91,7 +91,7 @@ Each Workflow is otherwise entirely independent: a Worker that defines multiple Each `step` in a Workflow is an independently retriable function. -A `step` is what makes a Workflow powerful, as you can encapsulate errors and persist state as your Workflow progresses from step to step, avoiding your application from having to start from scratch on failure and ultimately build more reliable applications. +A `step` is what makes a Workflow powerful, as you can encapsulate errors and persist state as your Workflow progresses from step to step, avoiding your application from having to start from scratch on failure and ultimately build more reliable applications. * A step can execute code (`step.do`) or sleep a Workflow (`step.sleep`) * If a step fails (throws an exception), it will be automatically be retried based on your retry logic. @@ -165,7 +165,7 @@ binding = "MY_WORKFLOW" class_name = "MyWorkflow" # script_name is required during for the beta. # Must match the "name" of your Worker at the top of wrangler.toml -script_name = "workflows-starter" +script_name = "workflows-starter" ``` :::note @@ -192,7 +192,7 @@ Return to the `src/index.ts` file we created in the previous step and add a `fet export default { async fetch(req: Request, env: Env) { - // + // const instanceId = new URL(req.url).searchParams.get("instanceId") // If an ?instanceId= query parameter is provided, fetch the status @@ -343,7 +343,7 @@ Steps: β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -From the output above, we can inspect: +From the output above, we can inspect: * The status (success, failure, running) of each step * Any state emitted by the step From 0fc510516406e0df09246a92655b3c1dc822eb1a Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 16 Oct 2024 18:35:44 +0100 Subject: [PATCH 09/54] more --- .../workflows/build/events-and-parameters.mdx | 4 +-- src/content/docs/workflows/build/index.mdx | 2 +- .../workflows/get-started/cli-quick-start.mdx | 34 ++++++++++++------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/content/docs/workflows/build/events-and-parameters.mdx b/src/content/docs/workflows/build/events-and-parameters.mdx index 2c85e6d000db03f..300574a6e4430d1 100644 --- a/src/content/docs/workflows/build/events-and-parameters.mdx +++ b/src/content/docs/workflows/build/events-and-parameters.mdx @@ -10,7 +10,7 @@ When a Workflow is triggered, it can receive an optional event. This event can i Events are a powerful part of a Workflow, as you often want a Workflow to act on data. Because a given Workflow instance executes durably, events are a useful way to provide a Workflow with data that should be immutable (not changing) and/or represents data the Workflow needs to operate on at that point in time. -## Passing parameters +## Passing parameters to a Workflow You can pass parameters to a Workflow in two ways: @@ -55,7 +55,7 @@ npx wrangler@latest workflows trigger workflows-starter '{"some":"data"}' πŸš€ Workflow instance "57c7913b-8e1d-4a78-a0dd-dce5a0b7aa30" has been queued successfully ``` -## TypeScript and typing events +## TypeScript and type parameters By default, the `WorkflowEvent` passed to the `run` method of your Workflow definition has a type that conforms to the following, with `payload` (your data) and `timestamp` properties: diff --git a/src/content/docs/workflows/build/index.mdx b/src/content/docs/workflows/build/index.mdx index b992b0ca9c67547..5c03da2fec55c8b 100644 --- a/src/content/docs/workflows/build/index.mdx +++ b/src/content/docs/workflows/build/index.mdx @@ -2,7 +2,7 @@ title: Build with Workflows pcx_content_type: navigation sidebar: - order: 1 + order: 2 --- diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 325db6ffb30b5ce..79a5c401494aa98 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -23,11 +23,11 @@ You can build Workflows to post-process file uploads to [R2 object storage](/r2/ ## Prerequisites -:::note +:::caution This guide is for users who are already familiar with Cloudflare Workers and [durable execution](/workflows/reference/how-workflows-works/) programming models. -If you're new to either, we recommend the [introduction to Workflows](/workflows/get-started/guide/) guide, which steps through how a Workflow is defined, how to persist state, deploy and manage Workflows. +If you're new to either, we recommend the [introduction to Workflows](/workflows/get-started/guide/) guide, which steps through how a Workflow is defined, how to persist state, and how to deploy and run your first Workflow. ::: @@ -44,7 +44,7 @@ Workflows are defined as part of a Worker script. To create a Workflow, use the `create cloudflare` (C3) CLI tool, specifying the Workflows starter template: ```sh -npm create cloudflare@latest workflows-tutorial -- --template "cloudflare:workflows-tutorial" +npm create cloudflare@latest workflows-starter -- --template "cloudflare:workflows-starter" ``` This will create a new folder called `workflows-tutorial`, which contains two files: @@ -56,20 +56,28 @@ Open the `src/index.ts` file in your text editor. This file contains the followi ```ts title="src/index.ts" // Import the Workflow definition -import { Workflow, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" +import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" // Create your own class that implements a Workflow -export class MyFirstWorkflow implements Workflow { +export class MyWorkflow implements WorkflowEntrypoint { // Define a run() method async run(event: WorkflowEvent, step: WorkflowStep) { // Define one or more steps that optionally return state. - let state = step.do("my first step", async () => { - - }) - - step.do("my second step", async () => { - - }) + const state = await step.do('my first step', async () => { + return {} + }); + + await step.sleep('wait on something', '1 minute'); + + await step.do( + 'make a call to write that could maybe, just might, fail', + async () => { + // Do stuff here, with access to 'state' from the previous step + if (Math.random() > 0.5) { + throw new Error('API call to $STORAGE_SYSTEM failed'); + } + } + ); } } ``` @@ -81,7 +89,7 @@ Specifically: 3. TODO - how state is persisted 4. TODO - retries and config 5. TODO - triggering from a fetch handler - + ```ts title="src/index.ts" From a21c2a0d1fe97140d3c097f28d2876c647a406f3 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 17 Oct 2024 12:11:37 +0100 Subject: [PATCH 10/54] wrangler --- .../docs/workers/wrangler/commands.mdx | 144 +++++++++++++++++- .../observability/metrics-analytics.mdx | 57 +++++-- 2 files changed, 190 insertions(+), 11 deletions(-) diff --git a/src/content/docs/workers/wrangler/commands.mdx b/src/content/docs/workers/wrangler/commands.mdx index 3346194f55b6e1c..ceae99b920b68dd 100644 --- a/src/content/docs/workers/wrangler/commands.mdx +++ b/src/content/docs/workers/wrangler/commands.mdx @@ -28,6 +28,7 @@ Wrangler offers a number of commands to manage your Cloudflare Workers. - [`r2 object`](#r2-object) - Manage Workers R2 objects. - [`secret`](#secret) - Manage the secret variables for a Worker. - [`secret:bulk`](#secretbulk) - Manage multiple secret variables for a Worker. +- [`workflows`](#workflows) - Manage and configure Workflows. - [`tail`](#tail) - Start a session to livestream logs from a deployed Worker. - [`pages`](#pages) - Configure Cloudflare Pages. - [`queues`](#queues) - Configure Workers Queues. @@ -138,10 +139,10 @@ wrangler docs [] ## `init` -:::note +:::note -The `init` command will be removed in a future version. Please use `npm create cloudflare@latest` instead. +The `init` command will be removed in a future version. Please use `npm create cloudflare@latest` instead. ::: @@ -1244,6 +1245,145 @@ Finished processing secrets JSON file: 🚨 1 secrets failed to upload ``` +## `workflows` + +Manage and configure [Workflows](/workflows/). + +### `list` + +Lists the registered Workflows for this account. + +```sh +wrangler workflows list +``` + +- `--page` + - Show a sepecific page from the listing, can configure page size using "per-page" +- `--per-page` + - Configure the maximum number of workflows to show per page. + +### `instances` + +Manage and interact with specific instances of a Workflow. + +### `instances list` + +List Workflow instances. + +```sh +wrangler workflows instances list [OPTIONS] +``` + +- `WORKFLOW_NAME` + - The name of a registered Workflow. + +### `instances describe` + +Describe a specific instance of a Workflow, including its current status, any persisted state, and per-step outputs. + +```sh +wrangler workflows instances describe [OPTIONS] +``` + +- `WORKFLOW_NAME` + - The name of a registered Workflow. +- `ID` + - The ID of a Workflow instance. You can optionally provide `latest` to refer to the most recently created instance of a Workflow. + +```sh +# Passing `latest` instead of an explicit ID will describe the most recently queued instance +wrangler workflows instances describe my-workflow latest +``` +```sh output +Workflow Name: my-workflow +Instance Id: 51c73fc8-7fd5-47d9-bd82-9e301506ee72 +Version Id: cedc33a0-11fa-4c26-8a8e-7d28d381a291 +Status: βœ… Completed +Trigger: 🌎 API +Queued: 10/16/2024, 2:00:39 PM +Success: βœ… Yes +Start: 10/16/2024, 2:00:39 PM +End: 10/16/2024, 2:01:40 PM +Duration: 1 minute +# Remaining output truncated +``` + +### `instances terminate` + +Terminate (permanently stop) a Workflow instance. + +```sh +wrangler workflows instances terminate [OPTIONS] +``` + +- `WORKFLOW_NAME` + - The name of a registered Workflow. +- `ID` + - The ID of a Workflow instance. + +### `instances pause` + +Pause (until resumed) a Workflow instance. + +```sh +wrangler workflows instances pause [OPTIONS] +``` + +- `WORKFLOW_NAME` + - The name of a registered Workflow. +- `ID` + - The ID of a Workflow instance. + +### `instances resume` + +Resume a paused Workflow instance. + +```sh +wrangler workflows instances resume [OPTIONS] +``` + +- `WORKFLOW_NAME` + - The name of a registered Workflow. +- `ID` + - The ID of a Workflow instance. + +### `describe` + +```sh +wrangler workflows describe [OPTIONS] +``` + +- `WORKFLOW_NAME` + - The name of a registered Workflow. + +### `trigger` + +Trigger (create) a Workflow instance. +```sh +wrangler workflows describe [OPTIONS] +``` + +- `WORKFLOW_NAME` + - The name of a registered Workflow. +- `PARAMS` + - The parameters to pass to the Workflow as an event. Must be a JSON-encoded string. + + ```sh + # Pass optional params to the Workflow. + wrangler workflows instances trigger my-workflow '{"hello":"world"}' + ``` + +### `delete` + +Delete (unregister) a Workflow. + +```sh +wrangler workflows delete [OPTIONS] +``` + +- `WORKFLOW_NAME` + - The name of a registered Workflow. + ## `tail` Start a session to livestream logs from a deployed Worker. diff --git a/src/content/docs/workflows/observability/metrics-analytics.mdx b/src/content/docs/workflows/observability/metrics-analytics.mdx index b67c0160cb13e30..16146df03fd0ac7 100644 --- a/src/content/docs/workflows/observability/metrics-analytics.mdx +++ b/src/content/docs/workflows/observability/metrics-analytics.mdx @@ -6,24 +6,63 @@ sidebar: --- -D1 exposes database analytics that allow you to inspect query volume, query latency, and storage size across all and/or each database in your account. +Workflows exposes metrics that allow you to inspect and measure Workflow execution, error rates, steps and total duration across each (and all) of your Workflows. The metrics displayed in the [Cloudflare dashboard](https://dash.cloudflare.com/) charts are queried from Cloudflare’s [GraphQL Analytics API](/analytics/graphql-api/). You can access the metrics [programmatically](#query-via-the-graphql-api) via GraphQL or HTTP client. ## Metrics -D1 currently exports the below metrics: +Workflows currently exports the below metrics: | Metric | GraphQL Field Name | Description | | ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | Read Queries (qps) | `readQueries` | The number of read queries issued against a database. This is the raw number of read queries, and is not used for billing. | -| Write Queries (qps) | `writeQueries` | The number of write queries issued against a database. This is the raw number of write queries, and is not used for billing. | -| Rows read (count) | `rowsRead` | The number of rows read (scanned) across your queries. See [Pricing](/d1/platform/pricing/) for more details on how rows are counted. | -| Rows written (count) | `rowsWritten` | The number of rows written across your queries. | -| Query Response (bytes) | `queryBatchResponseBytes` | The total response size of the serialized query response, including any/all column names, rows and metadata. Reported in bytes. | -| Query Latency (ms) | `queryBatchTimeMs` | The total query response time, including response serialization, on the server-side. Reported in milliseconds. | -| Storage (Bytes) | `databaseSizeBytes` | Maximum size of a database. Reported in bytes. | Metrics can be queried (and are retained) for the past 31 days. -### Row count \ No newline at end of file +## View metrics in the dashboard + +Per-Workflow and instance analytics for Workflows are available in the Cloudflare dashboard. To view current and historical metrics for a database: + +1. Log in to the [Cloudflare dashboard](https://dash.cloudflare.com) and select your account. +2. Go to [**Workers & Pages** > **Workflows**](https://dash.cloudflare.com/?to=/:account/workers/workflows). +3. Select a Workflow to view its metrics. + +You can optionally select a time window to query. This defaults to the last 24 hours. + +## Query via the GraphQL API + +You can programmatically query analytics for your Workflows via the [GraphQL Analytics API](/analytics/graphql-api/). This API queries the same datasets as the Cloudflare dashboard, and supports GraphQL [introspection](/analytics/graphql-api/features/discovery/introspection/). + +Workflows GraphQL datasets require an `accountTag` filter with your Cloudflare account ID and includes the `workflowsAdaptive` and `workflowsAdaptiveGroups` datasets. + +### Examples + +To query the sum of `readQueries`, `writeQueries` for a given `$databaseId`, grouping by `databaseId` and `date`: + +```graphql +query { + viewer { + accounts(filter: { accountTag: $accountId }) { + d1AnalyticsAdaptiveGroups( + limit: 10000 + filter: { + date_geq: $startDate + date_leq: $endDate + databaseId: $databaseId + } + orderBy: [date_DESC] + ) { + sum { + readQueries + writeQueries + } + dimensions { + date + databaseId + } + } + } + } +} +``` From 10721868c835e6752b829bfac87231e928a6f321 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 17 Oct 2024 14:11:01 +0100 Subject: [PATCH 11/54] cron triggers --- src/content/docs/workers/configuration/cron-triggers.mdx | 6 ++++++ src/content/docs/workflows/build/workers-api.mdx | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/content/docs/workers/configuration/cron-triggers.mdx b/src/content/docs/workers/configuration/cron-triggers.mdx index f905ad44ea8b5f1..8fd35c98942abec 100644 --- a/src/content/docs/workers/configuration/cron-triggers.mdx +++ b/src/content/docs/workers/configuration/cron-triggers.mdx @@ -11,6 +11,12 @@ Cron Triggers allow users to map a cron expression to a Worker using a [`schedul Cron Triggers are ideal for running periodic jobs, such as for maintenance or calling third-party APIs to collect up-to-date data. Workers scheduled by Cron Triggers will run on underutilized machines to make the best use of Cloudflare's capacity and route traffic efficiently. +:::note + +Cron Triggers can also be combined with [Workflows](/workflows/) to trigger multi-step, long-running tasks. You can [bind to a Workflow](/workflows/build/workers-api/) from directly from your Cron Trigger to execute a Workflow on a schedule. + +::: + Cron Triggers execute on UTC time. ## Add a Cron Trigger diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index a431f53d249d6d2..adc9ed42c084bdc 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -8,6 +8,9 @@ sidebar: TODO +## Binding + +TODO ## Interacting with Workflows @@ -40,14 +43,14 @@ TODO - into to steps * step.do(name: string, callback: (): RpcSerializable, config?: StepConfig): Promise<T> * TODO - describe each param - * TODO - + * TODO - TODO - show an example TODO - show an example of dynamically creating a step * step.sleep(name: string, duration: WorkflowDuration): Promise<void> - * TODO - + * TODO - TODO - show an example @@ -56,4 +59,4 @@ TODO - show an example TODO -## NonRetriableError \ No newline at end of file +## NonRetriableError From 3f6db1d61aa163e168314362481becf12c802c28 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 17 Oct 2024 18:39:39 +0100 Subject: [PATCH 12/54] more docs --- src/content/docs/workflows/build/retrying.mdx | 9 -- .../workflows/build/sleeping-and-retrying.mdx | 31 +++++++ .../workflows/get-started/cli-quick-start.mdx | 8 +- .../docs/workflows/get-started/guide.mdx | 92 ++++++++++++++++++- 4 files changed, 129 insertions(+), 11 deletions(-) delete mode 100644 src/content/docs/workflows/build/retrying.mdx create mode 100644 src/content/docs/workflows/build/sleeping-and-retrying.mdx diff --git a/src/content/docs/workflows/build/retrying.mdx b/src/content/docs/workflows/build/retrying.mdx deleted file mode 100644 index 1b6e6428efc0a2b..000000000000000 --- a/src/content/docs/workflows/build/retrying.mdx +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: Retrying -pcx_content_type: concept -sidebar: - order: 4 - ---- - -TODO \ No newline at end of file diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx new file mode 100644 index 000000000000000..28fae3c52b641e2 --- /dev/null +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -0,0 +1,31 @@ +--- +title: Sleeping and retrying +pcx_content_type: concept +sidebar: + order: 4 + +--- + +TODO + +## Sleeping + +TODO + +:::note + +A Workflow instance that is resuming from sleep will take priority over newly scheduled (queued) instances. This helps ensure that older Workflow instances can run to completion and are not blocked by newer instances. + +::: + +### Explicitly pausing a Workflow + +TODO + +### Resuming a Workflow + +TODO + +## Retrying steps + +TODO diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 79a5c401494aa98..61bd17c635fdcac 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -44,7 +44,7 @@ Workflows are defined as part of a Worker script. To create a Workflow, use the `create cloudflare` (C3) CLI tool, specifying the Workflows starter template: ```sh -npm create cloudflare@latest workflows-starter -- --template "cloudflare:workflows-starter" +npm create cloudflare@latest workflows-starter -- --template "cloudflare/workflows-starter" ``` This will create a new folder called `workflows-tutorial`, which contains two files: @@ -109,12 +109,18 @@ TODO ## 3. Running a Workflow TODO - basic trigger steps + +### CLI + ```sh npx wrangler@latest workflows trigger workflows-tutorial --params={"hello":"world"} ``` +### Worker binding + TODO - trigger from a Worker + ## 4. Managing Workflows TODO - managing via the CLI diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 4bd9c66f78e998f..4006142b1aeeb02 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -64,7 +64,7 @@ export class MyWorkflow implements WorkflowEntrypoint { await step.sleep('wait on something', '1 minute'); - await step.do( + await await step.do( 'make a call to write that could maybe, just might, fail', async () => { // Do stuff here, with access to 'state' from the previous step @@ -226,6 +226,96 @@ In a production application, you might choose to put authentication in front of ::: +### Review your Workflow code + +Before you deploy, you can review the full Workflows code and the `fetch` handler that will allow you to trigger your Workflow over HTTP: + +```ts +import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers'; + +type Env = { + // Add your bindings here, e.g. KV, D1, AI, etc. + MY_WORKFLOW: Workflow; +}; + +// User-defined params passed to your workflow +type Params = { + email: string; + metadata: Record; +}; + +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // Can access bindings on `this.env` + // Can access params on `event.params` + + const files = await step.do('my first step', async () => { + // Fetch a list of files from $SOME_SERVICE + return { + inputParams: event, + files: [ + 'doc_7392_rev3.pdf', + 'report_x29_final.pdf', + 'memo_2024_05_12.pdf', + 'file_089_update.pdf', + 'proj_alpha_v2.pdf', + 'data_analysis_q2.pdf', + 'notes_meeting_52.pdf', + 'summary_fy24_draft.pdf', + ], + }; + }); + + const apiResponse = await step.do('some other step', async () => { + let resp = await fetch('https://api.cloudflare.com/client/v4/ips'); + return await resp.json(); + }); + + await step.sleep('wait on something', '1 minute'); + + await step.do( + 'make a call to write that could maybe, just might, fail', + // { + // retries: { + // limit: 5, + // delay: '5 second', + // backoff: 'exponential', + // }, + // timeout: '15 minutes', + // }, + async () => { + // Do stuff here, with access to my_value! + if (Math.random() > 0.5) { + throw new Error('API call to $STORAGE_SYSTEM failed'); + } + } + ); + } +} + +export default { + async fetch(req: Request, env: Env): Promise { + let id = new URL(req.url).searchParams.get('instanceId'); + + // Get the status of an existing instance, if provided + if (id) { + let instance = await env.MYWORKFLOW.get(id); + return Response.json({ + status: await instance.status(), + }); + } + + // Spawn a new instance and return the ID and status + const newId = await crypto.randomUUID(); + let instance = await env.MYWORKFLOW.create(newId, {}); + return Response.json({ + id: instance.id, + details: await instance.status(), + }); + }, +}; +``` + ## 6. Deploy your Workflow Deploying a Workflow is identical to deploying a Worker. From 6d2439f979c00b5330dc27d1bcfe0b04ee719cc8 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Sat, 19 Oct 2024 10:48:30 -0400 Subject: [PATCH 13/54] rules of steps --- .../docs/workflows/build/rules-of-steps.mdx | 181 +++++++++++++++++- .../workflows/get-started/cli-quick-start.mdx | 62 ++++++ .../docs/workflows/get-started/guide.mdx | 10 +- 3 files changed, 246 insertions(+), 7 deletions(-) diff --git a/src/content/docs/workflows/build/rules-of-steps.mdx b/src/content/docs/workflows/build/rules-of-steps.mdx index 57c965d27e3e42f..7e70acb40453cd6 100644 --- a/src/content/docs/workflows/build/rules-of-steps.mdx +++ b/src/content/docs/workflows/build/rules-of-steps.mdx @@ -3,22 +3,193 @@ title: Rules of Steps pcx_content_type: concept sidebar: order: 10 - --- A Workflow step is self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow is comprised of one or more steps. +This is a small guidebook on how to build more resilient and correct Workflows. + +### Ensure API/Binding calls are idempotent + +Because a step might be retried multiple times, your steps should (ideally) be idempotent. For context, idempotency is a logical property where the operation (in this case a step), +can be applied multiple times without changing the result beyond the intial application. + +As an example, let's assume you have a Workflow that charges your customers and you really don't want to charge them twice by accident, before charging them, you should +check if they were already charged: + +```ts +export class MyWorkflow extends Workflow { + async run(events: WorkflowEvent[], step: WorkflowStep) { + const customer_id = 123456; + // βœ… Good: Non-idempotent API/Binding calls are always done **after** checking if the operation is + // still needed. + await step.do( + `charge ${customer_id} for it's montly subscription`, + async () => { + // API call to check if customer was already charged + const subscription = await fetch( + `https://payment.processor/subscriptions/${customer_id}`, + ).then((res) => res.json()); + + // return early if the customer was already charged, this can happen if the destination service dies + // in the middle of the request but still commits it, or if the Workflows Engine restarts. + if (subscription.charged) { + return; + } + + // non-idempotent call, this operation can fail and retry but still commit in the payment + // processor - which means that, on retry, it would mischarge the customer again if the above checks + // were not in place. + return await fetch( + `https://payment.processor/subscriptions/${customer_id}`, + { + method: "POST", + body: JSON.stringify({ amount: 10.0 }), + }, + ); + }, + ); + } +} +``` + +:::note + +Guaranteeing idempotency might be optional in your specific use-case and implementaion, although we recommend it to always try to guarantee it. + +::: + ### Make your steps granular -TODO - step is a transaction, should be a self-contained logic. If you have multiple API calls, seperate them into their own steps. +Steps should be as self-contained as possible, this allows your own logic to be more durable in case of failures in third-party APIs, network errors, and so on. +You can also think of it as a transaction, or a unit of work. -### Ensure API calls are idempotent +- βœ… Minimize the number of API/binding calls per step (unless you need multiple calls to prove idempotency). -TODO +```ts +export class MyWorkflow extends Workflow { + async run(events: WorkflowEvent[], step: WorkflowStep) { + // βœ… Good: Unrelated API/Binding calls are self-contained, so that in case one of them fails + // it can retry them individually. It also has an extra advantage: you can control retry or + // 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"); + }); + + const image = await step.do("fetch cat image from http.cat", async () => { + return await fetch(`https://http.cat/${httpCat}`); + }); + } +} +``` + +Otherwise your entire workflow might not be as durable as you might think, and encounter into some undefined behaviour and you can avoid them by: + +- πŸ”΄ Do not encapsulate your entire logic in one single step. +- πŸ”΄ Do not call seperate services in the same step (unless you need it to prove idempotency) +- πŸ”΄ Do not make too many service calls in the same step (unless you need it to prove idempotency) +- πŸ”΄ Do not do too much CPU-intensive work inside of a single step - sometimes engine might have to restart and it will start over from that step. + +```ts +export class MyWorkflow extends Workflow { + async run(events: WorkflowEvent[], step: WorkflowStep) { + // πŸ”΄ Bad: you're calling two seperate services from within the same step. This might cause + // 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"); + return fetch(`https://http.cat/${httpCat}`); + }); + } +} +``` ### Don't rely on state outside of a step -TODO +Sometimes, our Engine will hibernate and lose all in-memory state - this will happen when engine detects that there's no pending work and can hibernate +until it needs to wake-up (because of a sleep, retry or event). This means that you can't do something like this: + +```ts +function getRandomInt(min, max) { + const minCeiled = Math.ceil(min); + const maxFloored = Math.floor(max); + return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive +} + +export class MyWorkflow extends Workflow { + async run(events: WorkflowEvent[], step: WorkflowStep) { + // πŸ”΄ Bad: `imageList` will be not persisted across engine's lifetimes. Which means that after hibernation, + // `imageList` will be empty again, even though the following two steps have already ran. + const imageList: string[] = []; + + await step.do("get first cutest cat from KV", async () => { + const httpCat = await env.KV.get("cutest-http-cat-1"); + + imageList.append(httpCat); + }); + + await step.do("get second cutest cat from KV", async () => { + const httpCat = await env.KV.get("cutest-http-cat-2"); + + imageList.append(httpCat); + }); + + // A long sleep can (and probably will) hibernate the engine which means that the first engine lifetime ends here + await step.sleep("πŸ’€πŸ’€πŸ’€πŸ’€", "3 hours"); + + // When this runs, it will be on the second engine lifetime - which means `imageList` will be empty. + await step.do( + "choose a random cat from the list and download it", + async () => { + const randomCat = imageList.at(getRandomInt(0, imageList.length)); + // this will fail since `randomCat` is undefined because `imageList` is empty + return await fetch(`https://http.cat/${randomCat}`); + }, + ); + } +} +``` + +Instead, you should build top-level state exclusively comprised of `step.do` returns: + +```ts +function getRandomInt(min, max) { + const minCeiled = Math.ceil(min); + const maxFloored = Math.floor(max); + return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive +} + +export class MyWorkflow extends Workflow { + async run(events: WorkflowEvent[], step: WorkflowStep) { + // βœ… Good: imageList state is exclusively comprised of step returns - this means that in the event of + // 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"); + }), + + step.do("get second cutest cat from KV", async () => { + return await env.KV.get("cutest-http-cat-2"); + }), + ]); + + // A long sleep can (and probably will) hibernate the engine which means that the first engine lifetime ends here + await step.sleep("πŸ’€πŸ’€πŸ’€πŸ’€", "3 hours"); + + // When this runs, it will be on the second engine lifetime - but this time, imageList will contain + // the two most cutest cats + await step.do( + "choose a random cat from the list and download it", + async () => { + const randomCat = imageList.at(getRandomInt(0, imageList.length)); + // this will eventually succeed since `randomCat` is defined + return await fetch(`https://http.cat/${randomCat}`); + }, + ); + } +} +``` ### Set sensible retry parameters diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 61bd17c635fdcac..52f06797c17920c 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -113,13 +113,75 @@ TODO - basic trigger steps ### CLI ```sh +# Trigger a Workflow from the CLI, and pass (optional) parameters as an event to the Workflow. npx wrangler@latest workflows trigger workflows-tutorial --params={"hello":"world"} ``` +Refer to the [events and parameters documentation](/workflows/build/events-and-parameters/) to understand how events are passed to Workflows. + ### Worker binding TODO - trigger from a Worker +```toml title="wrangler.toml" +[[workflows]] +# name of your workflow +name = "workflows-starter" +# binding name env.MYWORKFLOW +binding = "MY_WORKFLOW" +# this is class that extends the Workflow class in src/index.ts +class_name = "MyWorkflow" +# script_name is required during for the beta. +# Must match the "name" of your Worker at the top of wrangler.toml +script_name = "workflows-starter" +``` + +You can then invoke the methods on this binding directly from your Worker script. The `Workflow` type has methods for: + +* `create(newId, params)` - creating (triggering) a new instance of the Workflow, with optional +* `get(id)`- retrieve a Workflow instance by ID +* `status()` - get the current status of a unique Workflow instance. + +For example, the following Worker will fetch the status of an existing Workflow instance by ID (if supplied), else it will create a new Workflow instance and return its ID: + +```ts title="src/index.ts" +// Import the Workflow definition +import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" + +interface Env { + // Matches the binding definition in your wrangler.toml + MY_WORKFLOW: Workflow; +} + +export default { + async fetch(req: Request, env: Env) { + // + const instanceId = new URL(req.url).searchParams.get("instanceId") + + // If an ?instanceId= query parameter is provided, fetch the status + // of an existing Workflow by its ID. + if (instanceId) { + let instance = await env.MYWORKFLOW.get(id); + return Response.json({ + status: await instance.status(), + }); + } + + // Else, create a new instance of our Workflow, passing in any (optional) params + // and return the ID. + const newId = await crypto.randomUUID(); + let instance = await env.MYWORKFLOW.create(newId, {}); + return Response.json({ + id: instance.id, + details: await instance.status(), + }); + + return Response.json({ result }); + }, +}; +``` + +Refer to the [triggering Workflows] documentation for how to trigger a Workflow from other Workers handler functions. ## 4. Managing Workflows diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 4006142b1aeeb02..f7e8886050862ad 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -164,7 +164,7 @@ binding = "MY_WORKFLOW" # this is class that extends the Workflow class in src/index.ts class_name = "MyWorkflow" # script_name is required during for the beta. - # Must match the "name" of your Worker at the top of wrangler.toml +# Must match the "name" of your Worker at the top of wrangler.toml script_name = "workflows-starter" ``` @@ -228,9 +228,15 @@ In a production application, you might choose to put authentication in front of ### Review your Workflow code +:::note + +This is the full contents of the `src/index.ts` file pulled down when you used the `cloudflare/workflows-starter` template at the beginning of this guide. + +::: + Before you deploy, you can review the full Workflows code and the `fetch` handler that will allow you to trigger your Workflow over HTTP: -```ts +```ts title="src/index.ts" import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers'; type Env = { From 9c054b858f675f765b3a463992478b9f0bdf6266 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Sat, 19 Oct 2024 11:19:50 -0400 Subject: [PATCH 14/54] fix --- .../docs/workflows/build/rules-of-steps.mdx | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/content/docs/workflows/build/rules-of-steps.mdx b/src/content/docs/workflows/build/rules-of-steps.mdx index 7e70acb40453cd6..912c61bbcd67893 100644 --- a/src/content/docs/workflows/build/rules-of-steps.mdx +++ b/src/content/docs/workflows/build/rules-of-steps.mdx @@ -1,11 +1,11 @@ --- -title: Rules of Steps +title: Rules of Workflows pcx_content_type: concept sidebar: order: 10 --- -A Workflow step is self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow is comprised of one or more steps. +A Workflow contains one or more steps. Each step is a self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow is comprised of one or more steps. This is a small guidebook on how to build more resilient and correct Workflows. @@ -18,8 +18,8 @@ As an example, let's assume you have a Workflow that charges your customers and check if they were already charged: ```ts -export class MyWorkflow extends Workflow { - async run(events: WorkflowEvent[], step: WorkflowStep) { +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { const customer_id = 123456; // βœ… Good: Non-idempotent API/Binding calls are always done **after** checking if the operation is // still needed. @@ -55,20 +55,21 @@ export class MyWorkflow extends Workflow { :::note -Guaranteeing idempotency might be optional in your specific use-case and implementaion, although we recommend it to always try to guarantee it. +Guaranteeing idempotency might be optional in your specific use-case and implementation, although we recommend it to always try to guarantee it. ::: ### Make your steps granular Steps should be as self-contained as possible, this allows your own logic to be more durable in case of failures in third-party APIs, network errors, and so on. + You can also think of it as a transaction, or a unit of work. - βœ… Minimize the number of API/binding calls per step (unless you need multiple calls to prove idempotency). ```ts -export class MyWorkflow extends Workflow { - async run(events: WorkflowEvent[], step: WorkflowStep) { +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { // βœ… Good: Unrelated API/Binding calls are self-contained, so that in case one of them fails // it can retry them individually. It also has an extra advantage: you can control retry or // timeout policies for each granular step - you might not to want to overload http.cat in @@ -92,8 +93,8 @@ Otherwise your entire workflow might not be as durable as you might think, and e - πŸ”΄ Do not do too much CPU-intensive work inside of a single step - sometimes engine might have to restart and it will start over from that step. ```ts -export class MyWorkflow extends Workflow { - async run(events: WorkflowEvent[], step: WorkflowStep) { +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { // πŸ”΄ Bad: you're calling two seperate services from within the same step. This might cause // some extra calls to the first service in case the second one fails, and in some cases, makes // the step non-idempotent altogether @@ -107,8 +108,9 @@ export class MyWorkflow extends Workflow { ### Don't rely on state outside of a step -Sometimes, our Engine will hibernate and lose all in-memory state - this will happen when engine detects that there's no pending work and can hibernate -until it needs to wake-up (because of a sleep, retry or event). This means that you can't do something like this: +Workflows may hibernate and lose all in-memory state. This will happen when engine detects that there's no pending work and can hibernate until it needs to wake-up (because of a sleep, retry or event). + +This means that you should not store state outside of a step: ```ts function getRandomInt(min, max) { @@ -117,8 +119,8 @@ function getRandomInt(min, max) { return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive } -export class MyWorkflow extends Workflow { - async run(events: WorkflowEvent[], step: WorkflowStep) { +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { // πŸ”΄ Bad: `imageList` will be not persisted across engine's lifetimes. Which means that after hibernation, // `imageList` will be empty again, even though the following two steps have already ran. const imageList: string[] = []; @@ -160,8 +162,8 @@ function getRandomInt(min, max) { return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled); // The maximum is exclusive and the minimum is inclusive } -export class MyWorkflow extends Workflow { - async run(events: WorkflowEvent[], step: WorkflowStep) { +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { // βœ… Good: imageList state is exclusively comprised of step returns - this means that in the event of // multiple engine lifetimes, imageList will be built accordingly const imageList: string[] = await Promise.all([ @@ -200,3 +202,5 @@ TODO TODO ### Don't mutate your incoming events + +TODO From 7654b8c921e1915a33315f9fc270c3e31b0619f4 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Mon, 21 Oct 2024 09:34:11 -0400 Subject: [PATCH 15/54] CLI guide --- ...es-of-steps.mdx => rules-of-workflows.mdx} | 0 .../workflows/get-started/cli-quick-start.mdx | 214 ++++++++++++++---- .../docs/workflows/get-started/guide.mdx | 70 +++--- 3 files changed, 203 insertions(+), 81 deletions(-) rename src/content/docs/workflows/build/{rules-of-steps.mdx => rules-of-workflows.mdx} (100%) diff --git a/src/content/docs/workflows/build/rules-of-steps.mdx b/src/content/docs/workflows/build/rules-of-workflows.mdx similarity index 100% rename from src/content/docs/workflows/build/rules-of-steps.mdx rename to src/content/docs/workflows/build/rules-of-workflows.mdx diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 52f06797c17920c..e753c2367ff1b10 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -55,45 +55,98 @@ This will create a new folder called `workflows-tutorial`, which contains two fi Open the `src/index.ts` file in your text editor. This file contains the following code, which is the most basic instance of a Workflow definition: ```ts title="src/index.ts" -// Import the Workflow definition -import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" +import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers'; -// Create your own class that implements a Workflow -export class MyWorkflow implements WorkflowEntrypoint { - // Define a run() method - async run(event: WorkflowEvent, step: WorkflowStep) { - // Define one or more steps that optionally return state. - const state = await step.do('my first step', async () => { - return {} +type Env = { + // Add your bindings here, e.g. Workers KV, D1, Workers AI, etc. + MY_WORKFLOW: Workflow; +}; + +// User-defined params passed to your workflow +type Params = { + email: string; + metadata: Record; +}; + +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // Can access bindings on `this.env` + // Can access params on `event.params` + + const files = await step.do('my first step', async () => { + // Fetch a list of files from $SOME_SERVICE + return { + inputParams: event, + files: [ + 'doc_7392_rev3.pdf', + 'report_x29_final.pdf', + 'memo_2024_05_12.pdf', + 'file_089_update.pdf', + 'proj_alpha_v2.pdf', + 'data_analysis_q2.pdf', + 'notes_meeting_52.pdf', + 'summary_fy24_draft.pdf', + ], + }; + }); + + const apiResponse = await step.do('some other step', async () => { + let resp = await fetch('https://api.cloudflare.com/client/v4/ips'); + return await resp.json(); }); await step.sleep('wait on something', '1 minute'); await step.do( 'make a call to write that could maybe, just might, fail', + // Define a retry strategy + { + retries: { + limit: 5, + delay: '5 second', + backoff: 'exponential', + }, + timeout: '15 minutes', + }, async () => { - // Do stuff here, with access to 'state' from the previous step + // Do stuff here, with access to the state from our previous steps if (Math.random() > 0.5) { throw new Error('API call to $STORAGE_SYSTEM failed'); } - } + }, ); - } + } } -``` -Specifically: +export default { + async fetch(req: Request, env: Env): Promise { + let id = new URL(req.url).searchParams.get('instanceId'); -1. TODO - describe the Workflow class -2. TODO - how params are injected -3. TODO - how state is persisted -4. TODO - retries and config -5. TODO - triggering from a fetch handler + // Get the status of an existing instance, if provided + if (id) { + let instance = await env.MY_WORKFLOW.get(id); + return Response.json({ + status: await instance.status(), + }); + } -```ts title="src/index.ts" + // Spawn a new instance and return the ID and status + let instance = await env.MY_WORKFLOW.create(); + return Response.json({ + id: instance.id, + details: await instance.status(), + }); + }, +}; +``` +Specifically, the code above: -``` +1. Extends the Workflows base class (`WorkflowsEntrypoint`) and defines a `run` method for our Workflow. +2. Passes in our `Params` type as a [type parameter](/workflows/build/events-and-parameters/) so that events that trigger our Workflow are typed. +3. Defines several steps that return state. +4. Defines a custom retry configuration for a step. +5. Binds to the Workflow from a Worker's `fetch` handler so that we can create (trigger) instances of our Workflow via a HTTP call. ## 2. Deploy a Workflow @@ -108,7 +161,7 @@ TODO ## 3. Running a Workflow -TODO - basic trigger steps +You can run a Workflow via the `wrangler` CLI, via a Worker binding, or via the Workflows [REST API](). ### CLI @@ -138,7 +191,7 @@ script_name = "workflows-starter" You can then invoke the methods on this binding directly from your Worker script. The `Workflow` type has methods for: -* `create(newId, params)` - creating (triggering) a new instance of the Workflow, with optional +* `create()` - creating (triggering) a new instance of the Workflow, returning the ID. * `get(id)`- retrieve a Workflow instance by ID * `status()` - get the current status of a unique Workflow instance. @@ -154,47 +207,116 @@ interface Env { } export default { - async fetch(req: Request, env: Env) { - // - const instanceId = new URL(req.url).searchParams.get("instanceId") - - // If an ?instanceId= query parameter is provided, fetch the status - // of an existing Workflow by its ID. - if (instanceId) { - let instance = await env.MYWORKFLOW.get(id); + async fetch(req: Request, env: Env): Promise { + let id = new URL(req.url).searchParams.get('instanceId'); + + // Get the status of an existing instance, if provided + if (id) { + let instance = await env.MY_WORKFLOW.get(id); return Response.json({ status: await instance.status(), }); - } + } - // Else, create a new instance of our Workflow, passing in any (optional) params - // and return the ID. - const newId = await crypto.randomUUID(); - let instance = await env.MYWORKFLOW.create(newId, {}); + // Spawn a new instance and return the ID and status + let instance = await env.MY_WORKFLOW.create(); return Response.json({ id: instance.id, details: await instance.status(), }); - - return Response.json({ result }); }, }; ``` -Refer to the [triggering Workflows] documentation for how to trigger a Workflow from other Workers handler functions. +Refer to the [triggering Workflows](/workflows/build/trigger-workflows/) documentation for how to trigger a Workflow from other Workers handler functions. ## 4. Managing Workflows -TODO - managing via the CLI +:::note + +The `wrangler workflows` command requires wrangler version `3.82.0` or greater. Use `npx wrangler@latest` to always use the latest wrangler version when invoking commands. -* Listing -* Showing running instances -* Inspecting an instances state +::: + +The `wrangler workflows` command group has several sub-commands for managing and inspecting Workflows and their instances: + +* List Workflows: `wrangler workflows list` +* Inspect the instances of a Workflow: `wrangler workflows instances list YOUR_WORKFLOW_NAME` +* View the state of a running Workflow instance by its ID: `wrangler workflows instances describe YOUR_WORKFLOW_NAME WORKFLOW_ID` + +You can also view the state of the latest instance of a Workflow by using the `latest` keyword instead of an ID: + +```sh +npx wrangler@workflows workflows instances describe workflows-starter latest +# Or by ID: +# npx wrangler@workflows workflows instances describe workflows-starter 12dc179f-9f77-4a37-b973-709dca4189ba +``` +```sh output +Workflow Name: workflows-starter +Instance Id: f72c1648-dfa3-45ea-be66-b43d11d216f8 +Version Id: cedc33a0-11fa-4c26-8a8e-7d28d381a291 +Status: βœ… Completed +Trigger: 🌎 API +Queued: 10/15/2024, 1:55:31 PM +Success: βœ… Yes +Start: 10/15/2024, 1:55:31 PM +End: 10/15/2024, 1:56:32 PM +Duration: 1 minute +Last Successful Step: make a call to write that could maybe, just might, fail-1 +Steps: + + Name: my first step-1 + Type: 🎯 Step + Start: 10/15/2024, 1:55:31 PM + End: 10/15/2024, 1:55:31 PM + Duration: 0 seconds + Success: βœ… Yes + Output: "{\"inputParams\":[{\"timestamp\":\"2024-10-15T13:55:29.363Z\",\"payload\":{\"hello\":\"world\"}}],\"files\":[\"doc_7392_rev3.pdf\",\"report_x29_final.pdf\",\"memo_2024_05_12.pdf\",\"file_089_update.pdf\",\"proj_alpha_v2.pdf\",\"data_analysis_q2.pdf\",\"notes_meeting_52.pdf\",\"summary_fy24_draft.pdf\",\"plan_2025_outline.pdf\"]}" +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 10/15/2024, 1:55:31 PM β”‚ 10/15/2024, 1:55:31 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + + Name: some other step-1 + Type: 🎯 Step + Start: 10/15/2024, 1:55:31 PM + End: 10/15/2024, 1:55:31 PM + Duration: 0 seconds + Success: βœ… Yes + Output: "{\"result\":{\"ipv4_cidrs\":[\"173.245.48.0/20\",\"103.21.244.0/22\",\"103.22.200.0/22\",\"103.31.4.0/22\",\"141.101.64.0/18\",\"108.162.192.0/18\",\"190.93.240.0/20\",\"188.114.96.0/20\",\"197.234.240.0/22\",\"198.41.128.0/17\",\"162.158.0.0/15\",\"104.16.0.0/13\",\"104.24.0.0/14\",\"172.64.0.0/13\",\"131.0.72.0/22\"],\"ipv6_cidrs\":[\"2400:cb00::/32\",\"2606:4700::/32\",\"2803:f800::/32\",\"2405:b500::/32\",\"2405:8100::/32\",\"2a06:98c0::/29\",\"2c0f:f248::/32\"],\"etag\":\"38f79d050aa027e3be3865e495dcc9bc\"},\"success\":true,\"errors\":[],\"messages\":[]}" +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 10/15/2024, 1:55:31 PM β”‚ 10/15/2024, 1:55:31 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + + Name: wait on something-1 + Type: πŸ’€ Sleeping + Start: 10/15/2024, 1:55:31 PM + End: 10/15/2024, 1:56:31 PM + Duration: 1 minute + + Name: make a call to write that could maybe, just might, fail-1 + Type: 🎯 Step + Start: 10/15/2024, 1:56:31 PM + End: 10/15/2024, 1:56:32 PM + Duration: 1 second + Success: βœ… Yes + Output: null +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ Error β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 10/15/2024, 1:56:31 PM β”‚ 10/15/2024, 1:56:31 PM β”‚ 0 seconds β”‚ ❌ Error β”‚ Error: API call to $STORAGE_SYSTEM failed β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ 10/15/2024, 1:56:32 PM β”‚ 10/15/2024, 1:56:32 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` ## Next steps -* TODO -* ALSO TODO -* ALSO ALSO TODO +* Learn more about [how events are passed to a Workflow](/workflows/build/events-and-parameters/) +* Binding to and triggering Workflow instances using the [Workers API](/workflows/build/workers-api/) +* The [Rules of Workflows](/workflows/build/rules-of-workflows/) and best practices for building applications using Workflows. If you have any feature requests or notice any bugs, share your feedback directly with the Cloudflare team by joining the [Cloudflare Developers community on Discord](https://discord.cloudflare.com). diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index f7e8886050862ad..9c55386f8f45919 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -191,31 +191,26 @@ Return to the `src/index.ts` file we created in the previous step and add a `fet // This can be in the same file as our Workflow definition export default { - async fetch(req: Request, env: Env) { - // - const instanceId = new URL(req.url).searchParams.get("instanceId") - - // If an ?instanceId= query parameter is provided, fetch the status - // of an existing Workflow by its ID. - if (instanceId) { - let instance = await env.MYWORKFLOW.get(id); + async fetch(req: Request, env: Env): Promise { + let id = new URL(req.url).searchParams.get('instanceId'); + + // Get the status of an existing instance, if provided + if (id) { + let instance = await env.MY_WORKFLOW.get(id); return Response.json({ status: await instance.status(), }); - } + } - // Else, create a new instance of our Workflow, passing in any (optional) params - // and return the ID. - const newId = await crypto.randomUUID(); - let instance = await env.MYWORKFLOW.create(newId, {}); + // Spawn a new instance and return the ID and status + let instance = await env.MY_WORKFLOW.create(); return Response.json({ id: instance.id, details: await instance.status(), }); - - return Response.json({ result }); }, }; +; ``` The code here exposes a HTTP endpoint that generates a random ID and runs the Workflow, returning the ID and the Workflow status. It also accepts an optional `instanceId` query parameter that retrieves the status of a Workflow instance by its ID. @@ -240,7 +235,7 @@ Before you deploy, you can review the full Workflows code and the `fetch` handle import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers'; type Env = { - // Add your bindings here, e.g. KV, D1, AI, etc. + // Add your bindings here, e.g. Workers KV, D1, Workers AI, etc. MY_WORKFLOW: Workflow; }; @@ -281,20 +276,21 @@ export class MyWorkflow extends WorkflowEntrypoint { await step.do( 'make a call to write that could maybe, just might, fail', - // { - // retries: { - // limit: 5, - // delay: '5 second', - // backoff: 'exponential', - // }, - // timeout: '15 minutes', - // }, + // Define a retry strategy + { + retries: { + limit: 5, + delay: '5 second', + backoff: 'exponential', + }, + timeout: '15 minutes', + }, async () => { - // Do stuff here, with access to my_value! + // Do stuff here, with access to the state from our previous steps if (Math.random() > 0.5) { throw new Error('API call to $STORAGE_SYSTEM failed'); } - } + }, ); } } @@ -305,15 +301,14 @@ export default { // Get the status of an existing instance, if provided if (id) { - let instance = await env.MYWORKFLOW.get(id); + let instance = await env.MY_WORKFLOW.get(id); return Response.json({ status: await instance.status(), }); } // Spawn a new instance and return the ID and status - const newId = await crypto.randomUUID(); - let instance = await env.MYWORKFLOW.create(newId, {}); + let instance = await env.MY_WORKFLOW.create(); return Response.json({ id: instance.id, details: await instance.status(), @@ -359,8 +354,6 @@ Because Workflows can be long running, it's possible to have running instances t ::: - - To trigger our Workflow, we will use the `wrangler` CLI and pass in an optional `--payload`. The `payload` will be passed to your Workflow's `run` method handler as an `Event` ```sh @@ -453,7 +446,6 @@ You do not have to wait for a Workflow instance to finish executing to inspect i ::: - In the previous step, we also bound a Workers script to our Workflow. You can trigger a Workflow by visiting the (deployed) Workers script in a browser or with any HTTP client. ```sh @@ -468,12 +460,20 @@ curl -s https://workflows-starter.YOUR_WORKERS_SUBDOMAIN.workers.dev/ TODO - delete the Workflow. +You can optionally delete the Workflow, which will prevent the creation of any (all) instances by using `wrangler`: + +```sh +npx wrangler workflows delete my-workflow +``` + +Re-deploying the Workers script containing your Workflow code will re-create the Workflow. + --- ## Next steps -- learn more -- observability -- trigger workflows on a schedule or from a Queue +* Learn more about [how events are passed to a Workflow](/workflows/build/events-and-parameters/) +* Binding to and triggering Workflow instances using the [Workers API](/workflows/build/workers-api/) +* The [Rules of Workflows](/workflows/build/rules-of-workflows/) and best practices for building applications using Workflows. If you have any feature requests or notice any bugs, share your feedback directly with the Cloudflare team by joining the [Cloudflare Developers community on Discord](https://discord.cloudflare.com). From 05a635ab514c57de35010cc0a02b0cd1c000e469 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Mon, 21 Oct 2024 17:09:38 -0400 Subject: [PATCH 16/54] finish CLI starter --- .../workflows/get-started/cli-quick-start.mdx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index e753c2367ff1b10..132fce66df715f7 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -19,7 +19,7 @@ To learn more about Workflows, read [the beta announcement blog](https://blog.cl Workflows allow you to build durable, multi-step applications using the Workers platform. A Workflow can automatically retry, persist state, run for hours or days, and coordinate between third-party APIs. -You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger [user lifecycle emails](TODO) using your favorite email API. +You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger user lifecycle emails using your favorite email API. ## Prerequisites @@ -150,14 +150,11 @@ Specifically, the code above: ## 2. Deploy a Workflow -Workflows are deployed via [`wrangler](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Workers scripts, and deployed the same way. +Workflows are deployed via [`wrangler](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Workers scripts, and deployed the same way: ```sh npx wrangler@latest deploy ``` -```sh output -TODO -``` ## 3. Running a Workflow @@ -174,7 +171,9 @@ Refer to the [events and parameters documentation](/workflows/build/events-and-p ### Worker binding -TODO - trigger from a Worker +You can [bind to a Workflow](/workers/runtime-apis/bindings/#what-is-a-binding) from any handler in a Workers script, allowing you to programatically trigger and pass parameters to a Workflow instance from your own application code. + +To bind a Workflow to a Worker, you need to define a `[[workflows]]` binding in your `wrangler.toml` configuration: ```toml title="wrangler.toml" [[workflows]] @@ -189,10 +188,10 @@ class_name = "MyWorkflow" script_name = "workflows-starter" ``` -You can then invoke the methods on this binding directly from your Worker script. The `Workflow` type has methods for: +You can then invoke the methods on this binding directly from your Worker script's `env` parameter. The `Workflow` type has methods for: * `create()` - creating (triggering) a new instance of the Workflow, returning the ID. -* `get(id)`- retrieve a Workflow instance by ID +* `getByName()`- retrieve a Workflow instance by name (or `getById` by ID) * `status()` - get the current status of a unique Workflow instance. For example, the following Worker will fetch the status of an existing Workflow instance by ID (if supplied), else it will create a new Workflow instance and return its ID: From 09df5cf68eb044f98216d34abb45a3ac5a87f1bf Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Mon, 21 Oct 2024 17:23:25 -0400 Subject: [PATCH 17/54] update landing page --- .../runtime-apis/bindings/workflows.mdx | 10 +++++ .../workflows/build/trigger-workflows.mdx | 6 ++- src/content/docs/workflows/index.mdx | 37 +++++++------------ 3 files changed, 29 insertions(+), 24 deletions(-) create mode 100644 src/content/docs/workers/runtime-apis/bindings/workflows.mdx diff --git a/src/content/docs/workers/runtime-apis/bindings/workflows.mdx b/src/content/docs/workers/runtime-apis/bindings/workflows.mdx new file mode 100644 index 000000000000000..4c56e0d6fe75aa3 --- /dev/null +++ b/src/content/docs/workers/runtime-apis/bindings/workflows.mdx @@ -0,0 +1,10 @@ +--- +pcx_content_type: navigation +title: Vectorize +external_link: /workflows/ +head: [] +description: APIs available in Cloudflare Workers to interact with + Workflows. Workflows allow you to build durable, multi-step applications + using Workers. + +--- diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index 5d82cddbffb5c14..73d5a61b211dade 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -55,7 +55,7 @@ interface Env { export default { async fetch(req: Request, env: Env) { - // + // const instanceId = new URL(req.url).searchParams.get("instanceId") // If an ?instanceId= query parameter is provided, fetch the status @@ -84,3 +84,7 @@ export default { ## REST API (HTTP) TODO + +## Command line (CLI) + +Refer to the [CLI quick start](/workflows/get-started/cli-quick-start/) to learn more about how to manage and trigger Workflows via the command-line. diff --git a/src/content/docs/workflows/index.mdx b/src/content/docs/workflows/index.mdx index cdf4ebab1499587..a970a62011788f4 100644 --- a/src/content/docs/workflows/index.mdx +++ b/src/content/docs/workflows/index.mdx @@ -7,7 +7,7 @@ sidebar: order: 1 head: - tag: title - content: Cloudflare D1 + content: Cloudflare Workflows --- @@ -15,40 +15,35 @@ import { CardGrid, Description, Feature, LinkTitleCard, Plan, RelatedProduct } f - -Create new serverless SQL databases to query from your Workers and Pages projects. - +Build durable multi-step applications on Cloudflare Workers with Workflows. -D1 is Cloudflare’s native serverless database. D1 allows you to build applications that handle large amounts of users at no extra cost. With D1, you can restore your database to any minute within the last 30 days. +Workflows is a durable execution engine built on Cloudflare Workers. Workflows allow you to build multi-step applications that can automatically retry, persist state and run for minutes, hours, days or weeks. Workflows introduces a programming model that makes it easier to build reliable, long-running tasks, observe as they progress, and programatically trigger instances based on events across your services. -Create your first D1 database by [following the Get started guide](/d1/get-started/), learn how to [import data into a database](/d1/build-with-d1/import-export-data/), and how to [interact with your database](/d1/build-with-d1/d1-client-api/) directly from [Workers](/workers/) or [Pages](/pages/functions/bindings/#d1-databases). +Refer to the [get started guide](/workflows/get-started/guide/) to start building with Workflows *** ## Features - - -Create your first D1 database, establish a schema, import data and query D1 directly from an application [built with Workers](/workers/). + +Define your first Workflow, understand how to compose multi-steps, and deploy to production. - - -Execute SQL with SQLite's SQL compatibility and D1 Client API. + +Understand best practices when writing and building applications using Workflows. - - -Time Travel is D1’s approach to backups and point-in-time-recovery, and allows you to restore a database to any minute within the last 30 days. + +Learn how to trigger Workflows from your Workers applications, via the REST API, and the command-line. @@ -76,16 +71,12 @@ Deploy dynamic front-end applications in record time. - -Learn about D1's pricing and how to estimate your usage. - - - -Learn about what limits D1 has and how to work within them. + +Learn more about how Workflows is priced. - -Browse what developers are building with D1. + +Learn about what limits Workflows has and how to work within them. From a6c5e73240aadc6d3e34346142898dd014a0ea10 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Mon, 21 Oct 2024 17:27:45 -0400 Subject: [PATCH 18/54] fix numbering --- src/content/docs/workflows/get-started/guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 9c55386f8f45919..fdb2c60b483554e 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -317,7 +317,7 @@ export default { }; ``` -## 6. Deploy your Workflow +## 5. Deploy your Workflow Deploying a Workflow is identical to deploying a Worker. From 211b5528eeb33ed1c667c8d633e5a729624ccabc Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Mon, 21 Oct 2024 17:33:35 -0400 Subject: [PATCH 19/54] fix link formatting --- .../workflows/get-started/cli-quick-start.mdx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 132fce66df715f7..67cf235a41df0a6 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -150,7 +150,7 @@ Specifically, the code above: ## 2. Deploy a Workflow -Workflows are deployed via [`wrangler](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Workers scripts, and deployed the same way: +Workflows are deployed via [`wrangler`](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Workers scripts, and deployed the same way: ```sh npx wrangler@latest deploy @@ -312,6 +312,20 @@ Steps: β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` +From the output above, we can inspect: + +* The status (success, failure, running) of each step +* Any state emitted by the step +* Any `sleep` state, including when the Workflow will wake up +* Retries associated with each step. +* Errors, including exception messages + +:::note + +You do not have to wait for a Workflow instance to finish executing to inspect its current status. The `wrangler workflows instances describe` sub-command will show the status of an in-progress instance, including any persisted state, if it is sleeping, and any errors or retries. This can be especially useful when debugging a Workflow during development. + +::: + ## Next steps * Learn more about [how events are passed to a Workflow](/workflows/build/events-and-parameters/) From 1fdb2781d53329e6196f6f7408ea7d10cb927fdc Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Mon, 21 Oct 2024 17:37:25 -0400 Subject: [PATCH 20/54] fix --- src/content/docs/workflows/get-started/cli-quick-start.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 67cf235a41df0a6..05578d74aabb85c 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -148,6 +148,8 @@ Specifically, the code above: 4. Defines a custom retry configuration for a step. 5. Binds to the Workflow from a Worker's `fetch` handler so that we can create (trigger) instances of our Workflow via a HTTP call. +You can edit the Workflow by adding (or removing) additional `step` calls, changing the retry configuration, and/or making your own API calls. This Workflow template is designed to illustrate some of Workflows APIs. + ## 2. Deploy a Workflow Workflows are deployed via [`wrangler`](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Workers scripts, and deployed the same way: From 08afc80fd50f6a52227e3b85ac519b7b9454fc5e Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Tue, 22 Oct 2024 10:19:56 -0400 Subject: [PATCH 21/54] sleeping / retrying --- .../workflows/build/sleeping-and-retrying.mdx | 42 +++++++++++++++---- .../workflows/build/trigger-workflows.mdx | 42 +++++++++++++++++++ .../workflows/get-started/cli-quick-start.mdx | 2 +- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx index 28fae3c52b641e2..80d69d0a36b2b49 100644 --- a/src/content/docs/workflows/build/sleeping-and-retrying.mdx +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -18,14 +18,40 @@ A Workflow instance that is resuming from sleep will take priority over newly sc ::: -### Explicitly pausing a Workflow - -TODO - -### Resuming a Workflow - -TODO ## Retrying steps -TODO +Each call to `step.do` in a Workflow accepts an optional `StepConfig`, which allows you define the retry behaviour for that step. + +If you do not provide your own retry configuration, Workflows applies the following defaults: + +```ts +const defaultConfig: WorkflowStepConfig = { + retries: { + limit: 5, + delay: 1000, + backoff: "constant", + }, + timeout: "15 minutes", +}; +``` + +When providing your own `StepConfig`, you can configure: + +* The total number of attempts to make for a step +* The delay between attempts +* What backoff algorithm to apply between each attempt: any of `constant`, `linear`, or `exponential` +* When to timeout (in duration) before considering the step as failed (including during a retry attempt). + +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", { + 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 /* } +``` diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index 73d5a61b211dade..38df3a72338626e 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -81,6 +81,48 @@ export default { }; ``` +### Explicitly pausing a Workflow + +You can explicitly pause a Workflow instance (and later resume it) by calling `pause` against a specific instance ID. + +```ts +let instance = await env.MY_WORKFLOW.get("abc-123") +await instance.pause() // Returns Promise +``` + +### Resuming a Workflow + +You can resume a paused Workflow instance by calling `resume` against a specific instance ID. + +```ts +let instance = await env.MY_WORKFLOW.get("abc-123") +await instance.resume() // Returns Promise +``` + +Calling `resume` on an instance that is not currently paused will have no effect. + +### Stopping a Workflow + +You can stop a Workflow instance by calling `abort` against a specific instance ID. + +```ts +let instance = await env.MY_WORKFLOW.get("abc-123") +await instance.abort() // Returns Promise +``` + +Once stopped, the Workflow instance *cannot* be resumed. + +### Restarting a Workflow + +You can restart a Workflow instance by calling `restart` against a specific instance ID. + +```ts +let instance = await env.MY_WORKFLOW.get("abc-123") +await instance.restart() // Returns Promise +``` + +Restarting an instance will immediately cancel any in-progress steps, erase any intermediate state, and treat the Workflow as if it was run for the first time. + ## REST API (HTTP) TODO diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 05578d74aabb85c..c8a61e4a6c6315e 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -148,7 +148,7 @@ Specifically, the code above: 4. Defines a custom retry configuration for a step. 5. Binds to the Workflow from a Worker's `fetch` handler so that we can create (trigger) instances of our Workflow via a HTTP call. -You can edit the Workflow by adding (or removing) additional `step` calls, changing the retry configuration, and/or making your own API calls. This Workflow template is designed to illustrate some of Workflows APIs. +You can edit this Workflow by adding (or removing) additional `step` calls, changing the retry configuration, and/or making your own API calls. This Workflow template is designed to illustrate some of Workflows APIs. ## 2. Deploy a Workflow From e11c6f3614811b8b78339669d1c3ee8afc7e3c4b Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Tue, 22 Oct 2024 10:26:02 -0400 Subject: [PATCH 22/54] trigger --- .../workflows/build/trigger-workflows.mdx | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index 38df3a72338626e..3d939c03b16803e 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -48,6 +48,12 @@ class_name = "MyWorkflow" The `binding = "MY_WORKFLOW"` line defines the JavaScript variable that our Workflow methods are accessible on, including `create` (which triggers a new instance) or `get` (which returns the status of an existing instance). +The following example shows how to: + +* Retrieve the status of an existing Workflow instance by its ID +* Create (trigger) a new Workflow instance +* Return the status of a given instance ID. + ```ts title="src/index.ts" interface Env { MY_WORKFLOW: Workflow; @@ -81,6 +87,33 @@ export default { }; ``` +### Inspect a Workflow's status + +You can inspect the status of any running Workflow instance by calling `status` against a specific instance ID. This allows you to programmatically inspect whether an instance is queued (waiting to be scheduled), actively running, paused or errored. + +```ts +let instance = await env.MY_WORKFLOW.get("abc-123") +let status = await instance.status() // Returns an InstanceStatus +``` + +The possible values of status are as follows: + +```ts + status: + | "queued" // means that instance is waiting to be started (see concurrency limits) + | "running" + | "paused" + | "errored" + | "terminated" // user terminated the instance while it was running + | "complete" + | "waiting" // instance is hibernating and waiting for sleep or event to finish + | "waitingForPause" // instance is finishing the current work to pause + | "unknown"; + error?: string; + output?: object; +}; +``` + ### Explicitly pausing a Workflow You can explicitly pause a Workflow instance (and later resume it) by calling `pause` against a specific instance ID. @@ -125,7 +158,7 @@ Restarting an instance will immediately cancel any in-progress steps, erase any ## REST API (HTTP) -TODO +Refer to the [Workflows REST API documentation](/api/paths/accounts-account_id--workflows--workflow_name--instances/post). ## Command line (CLI) From f5b3aaf752ee6faacbdb87d8e74d750b1a7e3162 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Tue, 22 Oct 2024 10:27:47 -0400 Subject: [PATCH 23/54] wording --- src/content/docs/workflows/build/trigger-workflows.mdx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index 3d939c03b16803e..fc502e1f060ed67 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -48,11 +48,11 @@ class_name = "MyWorkflow" The `binding = "MY_WORKFLOW"` line defines the JavaScript variable that our Workflow methods are accessible on, including `create` (which triggers a new instance) or `get` (which returns the status of an existing instance). -The following example shows how to: +The following example shows how you can manage Workflows from within a Worker, including: -* Retrieve the status of an existing Workflow instance by its ID -* Create (trigger) a new Workflow instance -* Return the status of a given instance ID. +* Retrieving the status of an existing Workflow instance by its ID +* Creating (triggering) a new Workflow instance +* Returning the status of a given instance ID. ```ts title="src/index.ts" interface Env { From 2535e0ad3deb51ab6f1ce87a2797846e20ff3d59 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 07:36:21 -0400 Subject: [PATCH 24/54] wrap get-started guide --- .../docs/workflows/get-started/guide.mdx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index fdb2c60b483554e..d1460daaf42306f 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -19,7 +19,7 @@ To learn more about Workflows, read [the beta announcement blog](https://blog.cl Workflows allow you to build durable, multi-step applications using the Workers platform. A Workflow can automatically retry, persist state, run for hours or days, and coordinate between third-party APIs. -You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger [user lifecycle emails](TODO) using your favorite email API. +You can build Workflows to post-process file uploads to [R2 object storage](/r2/), automate generation of [Workers AI](/workers-ai/) embeddings into a [Vectorize](/vectorize/) vector database, or to trigger user lifecycle emails using your favorite email API. This guide will instruct you through: @@ -183,7 +183,7 @@ We have a very basic Workflow definition, but now need to provide a way to call 1. External HTTP requests via a `fetch()` handler 2. Messages from a [Queue](/queues/) 3. A schedule via [Cron Trigger](/workers/configuration/cron-triggers/) -4. Via the [Workflows REST API](TODO) or [wrangler CLI](/workers/wrangler/commands/#workflows) +4. Via the [Workflows REST API](/api/paths/accounts-account_id--workflows/get) or [wrangler CLI](/workers/wrangler/commands/#workflows) Return to the `src/index.ts` file we created in the previous step and add a `fetch` handler that _binds_ to our Workflow. This binding allows us to create new Workflow instances, fetch the status of an existing Workflow, pause and/or terminate a Workflow. @@ -325,7 +325,15 @@ Deploying a Workflow is identical to deploying a Worker. npx wrangler deploy ``` ```sh output -TODO +# Note the "Workflows" binding mentioned here, showing that +# wrangler has detected your Workflow +Your worker has access to the following bindings: +- Workflows: + - MY_WORKFLOW: MyWorkflow (defined in workflows-starter) +Uploaded workflows-starter (2.53 sec) +Deployed workflows-starter triggers (1.12 sec) + https://workflows-starter.silverlock.workers.dev + workflow: workflows-starter ``` A Worker with a valid Workflow definition will be automatically registered by Workflows. You can list your current Workflows using wrangler: @@ -334,12 +342,14 @@ A Worker with a valid Workflow definition will be automatically registered by Wo npx wrangler workflows list ``` ```sh output -TODO +Showing last 1 workflow: +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Name β”‚ Script name β”‚ Class name β”‚ Created β”‚ Modified β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ workflows-starter β”‚ workflows-starter β”‚ MyWorkflow β”‚ 10/23/2024, 11:33:58 AM β”‚ 10/23/2024, 11:33:58 AM β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` -TODO - describe the output, Workflows, instances, etc. - - ## 6. Run and observe your Workflow With your Workflow deployed, you can now run it. @@ -458,8 +468,6 @@ curl -s https://workflows-starter.YOUR_WORKERS_SUBDOMAIN.workers.dev/ ## 7. (Optional) Clean up -TODO - delete the Workflow. - You can optionally delete the Workflow, which will prevent the creation of any (all) instances by using `wrangler`: ```sh From 9533bfd08eb7698f6ea6b43d5a3c0d94f86510fb Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 07:45:13 -0400 Subject: [PATCH 25/54] rules of workflows --- .../workflows/build/rules-of-workflows.mdx | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/content/docs/workflows/build/rules-of-workflows.mdx b/src/content/docs/workflows/build/rules-of-workflows.mdx index 912c61bbcd67893..6f78ea2fdbe3f02 100644 --- a/src/content/docs/workflows/build/rules-of-workflows.mdx +++ b/src/content/docs/workflows/build/rules-of-workflows.mdx @@ -193,14 +193,34 @@ export class MyWorkflow extends WorkflowEntrypoint { } ``` -### Set sensible retry parameters - -TODO - -### Name your steps clearly +### Don't mutate your incoming events -TODO +The `event` passed to your Workflow's `run` method is immutable: changes you make to the event are not persisted across steps and/or Workflow restarts. -### Don't mutate your incoming events +```ts +interface MyEvent { + user: string; + data: string; +} -TODO +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // πŸ”΄ Bad: Mutating the event + // This will not be persisted across steps and `event.data` will + // take on its original value. + await step.do("bad step that mutates the incoming event", async () => { + let userData = await env.KV.get(event.user) + event.data = 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.user) + }) + + let someOtherData await step.do("following step that uses that state", async () => { + // Access to userData here + // Will always be the same if this step is retried + }) +``` From f9eccb3386a7908d8d5c7f2b6857cf528256fa7c Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 07:48:20 -0400 Subject: [PATCH 26/54] trigger done --- src/content/docs/workflows/build/trigger-workflows.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index fc502e1f060ed67..f3769cc096ba5f9 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -6,7 +6,11 @@ sidebar: --- -TODO - intro +You can trigger Workflows both programmatically and via the Workflows APIs, including: + +1. With [Workers](/workers) via HTTP requests in a `fetch` handler or bindings from a `queue` or `scheduled` handler. +2. Using the [Workflows REST API](/api/paths/accounts-account_id--workflows/get) +2. Via the [wrangler CLI](/workers/wrangler/commands/#workflows) in your terminal ## Workers API (Bindings) From 883e24acedf914a32e80db358dc8f5e4d5fb3e5b Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 08:04:29 -0400 Subject: [PATCH 27/54] sleep --- .../workflows/build/sleeping-and-retrying.mdx | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx index 80d69d0a36b2b49..cd0c7566185d594 100644 --- a/src/content/docs/workflows/build/sleeping-and-retrying.mdx +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -6,11 +6,11 @@ sidebar: --- -TODO +This guide details how to sleep a Workflow and/or configure retries for a Workflow step. ## Sleeping -TODO +You can set a Workflow to sleep as an explicit step, which can be useful when you want a Workflow to wait, schedule work ahead, or pause until an input or other external state is ready. :::note @@ -18,6 +18,35 @@ A Workflow instance that is resuming from sleep will take priority over newly sc ::: +### Sleep for a relative period + +Use `step.sleep` to have a Workflow sleep for a relative period of time: + +```ts +await step.sleep("sleep for a bit", "1 hour") +``` + +The second argument to `step.sleep` accepts both `number` (seconds) or a human-readable format, such as "1 minute" or "26 hours". The accepted units for `step.sleep` when used this way are as follows: + +```ts +| "second" +| "minute" +| "hour" +| "day" +| "week" +| "month" +| "year" +``` + +### Sleep until a fixed date + +Use `step.sleepUntil` to have a Workflow sleep to a specific `Date`: this can be useful when you have a timestamp from another system or want to "schedule" work to occur at a specific time (e.g. Sunday, 9AM UTC) + +```ts +// sleepUntil accepts a Date object as its second argument +const workflowsLaunchDate = Date.parse("24 Oct 2024 13:00:00 UTC"); +await step.sleepUntil("sleep until X times out", workflowsLaunchDate) +``` ## Retrying steps From 57d467ce785791384f4ce1d4b84e4f1e67214783 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 08:17:17 -0400 Subject: [PATCH 28/54] NonRetryableError --- .../workflows/build/sleeping-and-retrying.mdx | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx index cd0c7566185d594..f6af0d73119a528 100644 --- a/src/content/docs/workflows/build/sleeping-and-retrying.mdx +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -84,3 +84,27 @@ let someState = step.do("call an API", { timeout: "30 minutes", }, async () => { /* Step code goes here /* } ``` + +### Force a step to fail + +You can also force a Workflow instance to fail and _not_ retry by throwing a `NonRetryableError` from within the step. + +This can be useful when you detect a terminal (permanent) error from an upstream system (such as an authentication failure) or other errors where retrying would not help. + +```ts +// Import the NonRetryableError definition +import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent, NonRetryableError } from 'cloudflare:workers'; + +// In your step code: +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + await step.do("some step", async () => { + if !(event.data) { + throw NonRetryableError("event.data did not contain the expected payload") + } + }) + } +} +``` + +The Workflow instance itself will fail immediately, no further steps will be invoked, and the Workflow will not be retried. From 1c4c29f6d1a18e4c1f8c71920981fc1e36453017 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 08:22:53 -0400 Subject: [PATCH 29/54] better header for nonretryableerror --- src/content/docs/workflows/build/sleeping-and-retrying.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx index f6af0d73119a528..f535070edeec697 100644 --- a/src/content/docs/workflows/build/sleeping-and-retrying.mdx +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -85,7 +85,7 @@ let someState = step.do("call an API", { }, async () => { /* Step code goes here /* } ``` -### Force a step to fail +## Force a Workflow to fail You can also force a Workflow instance to fail and _not_ retry by throwing a `NonRetryableError` from within the step. From 51b3d24da3bb53db060163ba2ce8a37fe9e07547 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 08:50:30 -0400 Subject: [PATCH 30/54] npm create cloudflare@latest workflows-starter -- --template 'cloudflare/workflows-starter' --- src/content/docs/workflows/get-started/guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index d1460daaf42306f..5f7d31f211e2aeb 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -42,7 +42,7 @@ To continue: To create your first Workflow, use the `create cloudflare` (C3) CLI tool, specifying the Workflows starter template: ```sh -npm create cloudflare@latest workflows-starter -- --template "cloudflare:workflows-starter" +npm create cloudflare@latest workflows-starter -- --template "cloudflare/workflows-starter" ``` This will create a new folder called `workflows-starter`. From ffb389456416f0ee4b84b4d2701cfea6b84bc633 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 08:53:50 -0400 Subject: [PATCH 31/54] Apply suggestions from code review Co-authored-by: Jun Lee --- src/content/changelogs/workflows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/changelogs/workflows.yaml b/src/content/changelogs/workflows.yaml index e68ac49becfe398..355a53ebf0b7cde 100644 --- a/src/content/changelogs/workflows.yaml +++ b/src/content/changelogs/workflows.yaml @@ -8,7 +8,7 @@ entries: - publish_date: "2038-01-19" title: Workflows is now in public beta. description: |- - Workflows, a new product for building reliable, multi-step workflows using Cloudflare Workers, is now in public beta. The public beta is avaiable to any user with a [free or paid Workers plan](/workers/platform/pricing/). + Workflows, a new product for building reliable, multi-step workflows using Cloudflare Workers, is now in public beta. The public beta is available to any user with a [free or paid Workers plan](/workers/platform/pricing/). A Workflow allows you to define multiple, independent steps that encapsulate errors, automatically retry, persist state, and can run for seconds, minutes, hours or even days. A Workflow can be useful for post-processing data from R2 buckets before querying it, automating a [Workers AI RAG pipeline](/workers-ai/tutorials/build-a-retrieval-augmented-generation-ai/), or managing user signup flows and lifecycle emails. From 477972b88523e3f82e6bf340b3933cb57c13b3b1 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 09:06:12 -0400 Subject: [PATCH 32/54] @workflows -> @latest --- src/content/docs/workflows/get-started/cli-quick-start.mdx | 4 ++-- src/content/docs/workflows/get-started/guide.mdx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index c8a61e4a6c6315e..13fa3694a97bd6d 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -248,9 +248,9 @@ The `wrangler workflows` command group has several sub-commands for managing and You can also view the state of the latest instance of a Workflow by using the `latest` keyword instead of an ID: ```sh -npx wrangler@workflows workflows instances describe workflows-starter latest +npx wrangler@latest workflows instances describe workflows-starter latest # Or by ID: -# npx wrangler@workflows workflows instances describe workflows-starter 12dc179f-9f77-4a37-b973-709dca4189ba +# npx wrangler@latest workflows instances describe workflows-starter 12dc179f-9f77-4a37-b973-709dca4189ba ``` ```sh output Workflow Name: workflows-starter diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 5f7d31f211e2aeb..b796f5be0b50e3a 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -376,9 +376,9 @@ npx wrangler workflows trigger workflows-starter '{"hello":"world"}' To inspect the current status of the Workflow instance we just triggered, we can either reference it by ID or by using the keyword `latest`: ```sh -npx wrangler@workflows workflows instances describe workflows-starter latest +npx wrangler@latest workflows instances describe workflows-starter latest # Or by ID: -# npx wrangler@workflows workflows instances describe workflows-starter 12dc179f-9f77-4a37-b973-709dca4189ba +# npx wrangler@latest workflows instances describe workflows-starter 12dc179f-9f77-4a37-b973-709dca4189ba ``` ```sh output Workflow Name: workflows-starter From f8997ec63ff5e67e19c73296633492a9729df6fc Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 09:08:17 -0400 Subject: [PATCH 33/54] remove output from CLI guide --- .../workflows/get-started/cli-quick-start.mdx | 65 +------------------ 1 file changed, 2 insertions(+), 63 deletions(-) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 13fa3694a97bd6d..709bf27e5d1e382 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -193,7 +193,7 @@ script_name = "workflows-starter" You can then invoke the methods on this binding directly from your Worker script's `env` parameter. The `Workflow` type has methods for: * `create()` - creating (triggering) a new instance of the Workflow, returning the ID. -* `getByName()`- retrieve a Workflow instance by name (or `getById` by ID) +* `get()`- retrieve a Workflow instance by its ID. * `status()` - get the current status of a unique Workflow instance. For example, the following Worker will fetch the status of an existing Workflow instance by ID (if supplied), else it will create a new Workflow instance and return its ID: @@ -252,69 +252,8 @@ npx wrangler@latest workflows instances describe workflows-starter latest # Or by ID: # npx wrangler@latest workflows instances describe workflows-starter 12dc179f-9f77-4a37-b973-709dca4189ba ``` -```sh output -Workflow Name: workflows-starter -Instance Id: f72c1648-dfa3-45ea-be66-b43d11d216f8 -Version Id: cedc33a0-11fa-4c26-8a8e-7d28d381a291 -Status: βœ… Completed -Trigger: 🌎 API -Queued: 10/15/2024, 1:55:31 PM -Success: βœ… Yes -Start: 10/15/2024, 1:55:31 PM -End: 10/15/2024, 1:56:32 PM -Duration: 1 minute -Last Successful Step: make a call to write that could maybe, just might, fail-1 -Steps: - - Name: my first step-1 - Type: 🎯 Step - Start: 10/15/2024, 1:55:31 PM - End: 10/15/2024, 1:55:31 PM - Duration: 0 seconds - Success: βœ… Yes - Output: "{\"inputParams\":[{\"timestamp\":\"2024-10-15T13:55:29.363Z\",\"payload\":{\"hello\":\"world\"}}],\"files\":[\"doc_7392_rev3.pdf\",\"report_x29_final.pdf\",\"memo_2024_05_12.pdf\",\"file_089_update.pdf\",\"proj_alpha_v2.pdf\",\"data_analysis_q2.pdf\",\"notes_meeting_52.pdf\",\"summary_fy24_draft.pdf\",\"plan_2025_outline.pdf\"]}" -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ 10/15/2024, 1:55:31 PM β”‚ 10/15/2024, 1:55:31 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - - Name: some other step-1 - Type: 🎯 Step - Start: 10/15/2024, 1:55:31 PM - End: 10/15/2024, 1:55:31 PM - Duration: 0 seconds - Success: βœ… Yes - Output: "{\"result\":{\"ipv4_cidrs\":[\"173.245.48.0/20\",\"103.21.244.0/22\",\"103.22.200.0/22\",\"103.31.4.0/22\",\"141.101.64.0/18\",\"108.162.192.0/18\",\"190.93.240.0/20\",\"188.114.96.0/20\",\"197.234.240.0/22\",\"198.41.128.0/17\",\"162.158.0.0/15\",\"104.16.0.0/13\",\"104.24.0.0/14\",\"172.64.0.0/13\",\"131.0.72.0/22\"],\"ipv6_cidrs\":[\"2400:cb00::/32\",\"2606:4700::/32\",\"2803:f800::/32\",\"2405:b500::/32\",\"2405:8100::/32\",\"2a06:98c0::/29\",\"2c0f:f248::/32\"],\"etag\":\"38f79d050aa027e3be3865e495dcc9bc\"},\"success\":true,\"errors\":[],\"messages\":[]}" -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ 10/15/2024, 1:55:31 PM β”‚ 10/15/2024, 1:55:31 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - - Name: wait on something-1 - Type: πŸ’€ Sleeping - Start: 10/15/2024, 1:55:31 PM - End: 10/15/2024, 1:56:31 PM - Duration: 1 minute - - Name: make a call to write that could maybe, just might, fail-1 - Type: 🎯 Step - Start: 10/15/2024, 1:56:31 PM - End: 10/15/2024, 1:56:32 PM - Duration: 1 second - Success: βœ… Yes - Output: null -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Start β”‚ End β”‚ Duration β”‚ State β”‚ Error β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ 10/15/2024, 1:56:31 PM β”‚ 10/15/2024, 1:56:31 PM β”‚ 0 seconds β”‚ ❌ Error β”‚ Error: API call to $STORAGE_SYSTEM failed β”‚ -β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ -β”‚ 10/15/2024, 1:56:32 PM β”‚ 10/15/2024, 1:56:32 PM β”‚ 0 seconds β”‚ βœ… Success β”‚ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` -From the output above, we can inspect: +The output of `instances describe` shows: * The status (success, failure, running) of each step * Any state emitted by the step From e0e4248b70e92b0a02722d3609aa4e3eac25d0df Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 09:31:26 -0400 Subject: [PATCH 34/54] fix missing link --- src/content/docs/workflows/get-started/cli-quick-start.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 709bf27e5d1e382..a482d8109c4f677 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -160,7 +160,7 @@ npx wrangler@latest deploy ## 3. Running a Workflow -You can run a Workflow via the `wrangler` CLI, via a Worker binding, or via the Workflows [REST API](). +You can run a Workflow via the `wrangler` CLI, via a Worker binding, or via the Workflows [REST API](/api/paths/accounts-account_id--workflows/get). ### CLI From af6c6e4a6d90e88447dc96eded366202b232f323 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 09:51:39 -0400 Subject: [PATCH 35/54] fix links --- src/content/docs/workflows/get-started/cli-quick-start.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index a482d8109c4f677..477ed5c8a65a829 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -13,7 +13,7 @@ import { Render, PackageManagers } from "~/components" Workflows is in **public beta**, and any developer with a [free or paid Workers plan](/workers/platform/pricing/#workers) can start using Workflows immediately. -To learn more about Workflows, read [the beta announcement blog](https://blog.cloudflare.com/THE-BLOG-SLUG-WILL-GO-HERE) and the [How Workflows Works](/workflows/build/how-workflows-works/) developer documentation. +To learn more about Workflows, read [the beta announcement blog](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers) and the [How Workflows Works](/workflows/reference/how-workflows-works/) developer documentation. ::: From 86ba2a81217028f36d31b4a412eddd0444ba1f54 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 11:31:55 -0400 Subject: [PATCH 36/54] workflows: review Co-authored-by: Jun Lee --- .../runtime-apis/bindings/workflows.mdx | 2 +- .../docs/workers/wrangler/commands.mdx | 4 +-- .../docs/workflows/build/dynamic-steps.mdx | 4 +-- .../workflows/build/events-and-parameters.mdx | 6 ++--- src/content/docs/workflows/build/index.mdx | 2 ++ .../workflows/build/rules-of-workflows.mdx | 24 ++++++++--------- .../workflows/build/sleeping-and-retrying.mdx | 8 +++--- .../workflows/build/trigger-workflows.mdx | 16 +++++------ .../docs/workflows/build/workers-api.mdx | 2 +- src/content/docs/workflows/examples/index.mdx | 2 ++ .../workflows/get-started/cli-quick-start.mdx | 27 ++++++++----------- .../docs/workflows/get-started/index.mdx | 2 ++ src/content/docs/workflows/index.mdx | 6 ++--- .../docs/workflows/observability/index.mdx | 2 ++ .../observability/metrics-analytics.mdx | 4 +-- .../docs/workflows/reference/limits.mdx | 10 +++---- .../docs/workflows/reference/pricing.mdx | 8 +++--- src/content/glossary/workflows.yaml | 4 +-- 18 files changed, 68 insertions(+), 65 deletions(-) diff --git a/src/content/docs/workers/runtime-apis/bindings/workflows.mdx b/src/content/docs/workers/runtime-apis/bindings/workflows.mdx index 4c56e0d6fe75aa3..4410f72314e7424 100644 --- a/src/content/docs/workers/runtime-apis/bindings/workflows.mdx +++ b/src/content/docs/workers/runtime-apis/bindings/workflows.mdx @@ -1,6 +1,6 @@ --- pcx_content_type: navigation -title: Vectorize +title: Workflows external_link: /workflows/ head: [] description: APIs available in Cloudflare Workers to interact with diff --git a/src/content/docs/workers/wrangler/commands.mdx b/src/content/docs/workers/wrangler/commands.mdx index ceae99b920b68dd..91d195be2b7dfc1 100644 --- a/src/content/docs/workers/wrangler/commands.mdx +++ b/src/content/docs/workers/wrangler/commands.mdx @@ -1258,9 +1258,9 @@ wrangler workflows list ``` - `--page` - - Show a sepecific page from the listing, can configure page size using "per-page" + - Show a specific page from the listing. You can configure page size using "per-page". - `--per-page` - - Configure the maximum number of workflows to show per page. + - Configure the maximum number of Workflows to show per page. ### `instances` diff --git a/src/content/docs/workflows/build/dynamic-steps.mdx b/src/content/docs/workflows/build/dynamic-steps.mdx index ec8550fc6f93405..6cb4fafa2756a72 100644 --- a/src/content/docs/workflows/build/dynamic-steps.mdx +++ b/src/content/docs/workflows/build/dynamic-steps.mdx @@ -8,12 +8,12 @@ sidebar: A Workflow does not have to define all of its steps statically: steps can be created programmatically and/or conditionally. -This allows you to not only trigger steps based on specific input parameters, but to also name steps dynamically, set the retry configuration for a step +This allows you to not only trigger steps based on specific input parameters, but to also name steps dynamically and set the retry configuration for a single step. ## Example -Steps can be created on-the-fly, allowing you create a step for each parameter passed to your Workflow, for each file you want to read from storage, or for calls to third-party APIs. +You can create steps on-the-fly. You can create a step for each parameter passed to your Workflow, for each file you want to read from storage, or for calls to third-party APIs. For example, you can loop over each event, label the step dynamically, and have the step operate only over that `event`: diff --git a/src/content/docs/workflows/build/events-and-parameters.mdx b/src/content/docs/workflows/build/events-and-parameters.mdx index 300574a6e4430d1..8f769caf4006d32 100644 --- a/src/content/docs/workflows/build/events-and-parameters.mdx +++ b/src/content/docs/workflows/build/events-and-parameters.mdx @@ -10,11 +10,11 @@ When a Workflow is triggered, it can receive an optional event. This event can i Events are a powerful part of a Workflow, as you often want a Workflow to act on data. Because a given Workflow instance executes durably, events are a useful way to provide a Workflow with data that should be immutable (not changing) and/or represents data the Workflow needs to operate on at that point in time. -## Passing parameters to a Workflow +## Pass parameters to a Workflow You can pass parameters to a Workflow in two ways: -* As an optional argument to the `create` method on a [Workflow binding] when triggering a Workflow from a Worker. +* As an optional argument to the `create` method on a [Workflow binding](/workers/wrangler/commands/#trigger) when triggering a Workflow from a Worker. * Via the `--params` flag when using the `wrangler` CLI to trigger a Workflow. You can pass any JSON-serializable object as a parameter. @@ -80,7 +80,7 @@ interface YourEventType { } ``` -When we pass our `YourEventType` to `WorkflowEvent` as a type parameter, the `event.payload` property now has the type `YourEventType` throughout our workflow definition: +When you pass your `YourEventType` to `WorkflowEvent` as a type parameter, the `event.payload` property now has the type `YourEventType` throughout your workflow definition: ```ts title="src/index.ts" // Import the Workflow definition diff --git a/src/content/docs/workflows/build/index.mdx b/src/content/docs/workflows/build/index.mdx index 5c03da2fec55c8b..aa19963235b2fcd 100644 --- a/src/content/docs/workflows/build/index.mdx +++ b/src/content/docs/workflows/build/index.mdx @@ -3,6 +3,8 @@ title: Build with Workflows pcx_content_type: navigation sidebar: order: 2 + group: + hideIndex: true --- diff --git a/src/content/docs/workflows/build/rules-of-workflows.mdx b/src/content/docs/workflows/build/rules-of-workflows.mdx index 6f78ea2fdbe3f02..b50a0bf0624fc6d 100644 --- a/src/content/docs/workflows/build/rules-of-workflows.mdx +++ b/src/content/docs/workflows/build/rules-of-workflows.mdx @@ -5,16 +5,16 @@ sidebar: order: 10 --- -A Workflow contains one or more steps. Each step is a self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow is comprised of one or more steps. +A Workflow contains one or more steps. Each step is a self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. This is a small guidebook on how to build more resilient and correct Workflows. ### Ensure API/Binding calls are idempotent Because a step might be retried multiple times, your steps should (ideally) be idempotent. For context, idempotency is a logical property where the operation (in this case a step), -can be applied multiple times without changing the result beyond the intial application. +can be applied multiple times without changing the result beyond the initial application. -As an example, let's assume you have a Workflow that charges your customers and you really don't want to charge them twice by accident, before charging them, you should +As an example, let us assume you have a Workflow that charges your customers, and you really do not want to charge them twice by accident. Before charging them, you should check if they were already charged: ```ts @@ -55,13 +55,13 @@ export class MyWorkflow extends WorkflowEntrypoint { :::note -Guaranteeing idempotency might be optional in your specific use-case and implementation, although we recommend it to always try to guarantee it. +Guaranteeing idempotency might be optional in your specific use-case and implementation, but we recommend that you always try to guarantee it. ::: ### Make your steps granular -Steps should be as self-contained as possible, this allows your own logic to be more durable in case of failures in third-party APIs, network errors, and so on. +Steps should be as self-contained as possible. This allows your own logic to be more durable in case of failures in third-party APIs, network errors, and so on. You can also think of it as a transaction, or a unit of work. @@ -85,12 +85,12 @@ export class MyWorkflow extends WorkflowEntrypoint { } ``` -Otherwise your entire workflow might not be as durable as you might think, and encounter into some undefined behaviour and you can avoid them by: +Otherwise, your entire Workflow might not be as durable as you might think, and you may encounter some undefined behaviour. You can avoid them by following the rules below: - πŸ”΄ Do not encapsulate your entire logic in one single step. -- πŸ”΄ Do not call seperate services in the same step (unless you need it to prove idempotency) -- πŸ”΄ Do not make too many service calls in the same step (unless you need it to prove idempotency) -- πŸ”΄ Do not do too much CPU-intensive work inside of a single step - sometimes engine might have to restart and it will start over from that step. +- πŸ”΄ Do not call separate services in the same step (unless you need it to prove idempotency). +- πŸ”΄ Do not make too many service calls in the same step (unless you need it to prove idempotency). +- πŸ”΄ Do not do too much CPU-intensive work inside a single step - sometimes the engine may have to restart, and it will start over from the beginning of that step. ```ts export class MyWorkflow extends WorkflowEntrypoint { @@ -106,9 +106,9 @@ export class MyWorkflow extends WorkflowEntrypoint { } ``` -### Don't rely on state outside of a step +### Do not rely on state outside of a step -Workflows may hibernate and lose all in-memory state. This will happen when engine detects that there's no pending work and can hibernate until it needs to wake-up (because of a sleep, retry or event). +Workflows may hibernate and lose all in-memory state. This will happen when engine detects that there is no pending work and can hibernate until it needs to wake-up (because of a sleep, retry, or event). This means that you should not store state outside of a step: @@ -193,7 +193,7 @@ export class MyWorkflow extends WorkflowEntrypoint { } ``` -### Don't mutate your incoming events +### Do not mutate your incoming events The `event` passed to your Workflow's `run` method is immutable: changes you make to the event are not persisted across steps and/or Workflow restarts. diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx index f535070edeec697..946f888cca38c74 100644 --- a/src/content/docs/workflows/build/sleeping-and-retrying.mdx +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -8,7 +8,7 @@ sidebar: This guide details how to sleep a Workflow and/or configure retries for a Workflow step. -## Sleeping +## Sleep a Workflow You can set a Workflow to sleep as an explicit step, which can be useful when you want a Workflow to wait, schedule work ahead, or pause until an input or other external state is ready. @@ -40,7 +40,7 @@ The second argument to `step.sleep` accepts both `number` (seconds) or a human-r ### Sleep until a fixed date -Use `step.sleepUntil` to have a Workflow sleep to a specific `Date`: this can be useful when you have a timestamp from another system or want to "schedule" work to occur at a specific time (e.g. Sunday, 9AM UTC) +Use `step.sleepUntil` to have a Workflow sleep to a specific `Date`: this can be useful when you have a timestamp from another system or want to "schedule" work to occur at a specific time (e.g. Sunday, 9AM UTC). ```ts // sleepUntil accepts a Date object as its second argument @@ -48,7 +48,7 @@ const workflowsLaunchDate = Date.parse("24 Oct 2024 13:00:00 UTC"); await step.sleepUntil("sleep until X times out", workflowsLaunchDate) ``` -## Retrying steps +## Retry steps Each call to `step.do` in a Workflow accepts an optional `StepConfig`, which allows you define the retry behaviour for that step. @@ -70,7 +70,7 @@ When providing your own `StepConfig`, you can configure: * The total number of attempts to make for a step * The delay between attempts * What backoff algorithm to apply between each attempt: any of `constant`, `linear`, or `exponential` -* When to timeout (in duration) before considering the step as failed (including during a retry attempt). +* When to timeout (in duration) before considering the step as failed (including during a retry attempt) 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`: diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index f3769cc096ba5f9..71f2f1c3b1c417a 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -8,7 +8,7 @@ sidebar: You can trigger Workflows both programmatically and via the Workflows APIs, including: -1. With [Workers](/workers) via HTTP requests in a `fetch` handler or bindings from a `queue` or `scheduled` handler. +1. With [Workers](/workers) via HTTP requests in a `fetch` handler, or bindings from a `queue` or `scheduled` handler 2. Using the [Workflows REST API](/api/paths/accounts-account_id--workflows/get) 2. Via the [wrangler CLI](/workers/wrangler/commands/#workflows) in your terminal @@ -21,7 +21,7 @@ You can interact with a Workflow: * Directly over HTTP via the [`fetch`](/workers/runtime-apis/handlers/fetch/) handler * From a [Queue consumer](/queues/configuration/javascript-apis/#consumer) inside a `queue` handler * From a [Cron Trigger](/workers/configuration/cron-triggers/) inside a `scheduled` handler -* Within a [Durable Object](/durable-objects/). +* Within a [Durable Object](/durable-objects/) :::note @@ -56,7 +56,7 @@ The following example shows how you can manage Workflows from within a Worker, i * Retrieving the status of an existing Workflow instance by its ID * Creating (triggering) a new Workflow instance -* Returning the status of a given instance ID. +* Returning the status of a given instance ID ```ts title="src/index.ts" interface Env { @@ -93,7 +93,7 @@ export default { ### Inspect a Workflow's status -You can inspect the status of any running Workflow instance by calling `status` against a specific instance ID. This allows you to programmatically inspect whether an instance is queued (waiting to be scheduled), actively running, paused or errored. +You can inspect the status of any running Workflow instance by calling `status` against a specific instance ID. This allows you to programmatically inspect whether an instance is queued (waiting to be scheduled), actively running, paused, or errored. ```ts let instance = await env.MY_WORKFLOW.get("abc-123") @@ -118,7 +118,7 @@ The possible values of status are as follows: }; ``` -### Explicitly pausing a Workflow +### Explicitly pause a Workflow You can explicitly pause a Workflow instance (and later resume it) by calling `pause` against a specific instance ID. @@ -127,7 +127,7 @@ let instance = await env.MY_WORKFLOW.get("abc-123") await instance.pause() // Returns Promise ``` -### Resuming a Workflow +### Resume a Workflow You can resume a paused Workflow instance by calling `resume` against a specific instance ID. @@ -138,7 +138,7 @@ await instance.resume() // Returns Promise Calling `resume` on an instance that is not currently paused will have no effect. -### Stopping a Workflow +### Stop a Workflow You can stop a Workflow instance by calling `abort` against a specific instance ID. @@ -149,7 +149,7 @@ await instance.abort() // Returns Promise Once stopped, the Workflow instance *cannot* be resumed. -### Restarting a Workflow +### Restart a Workflow You can restart a Workflow instance by calling `restart` against a specific instance ID. diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index adc9ed42c084bdc..cd73327b77bdf10 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -12,7 +12,7 @@ TODO TODO -## Interacting with Workflows +## Interact with Workflows * Running a Workflow with the `run()` method * `StepConfig` diff --git a/src/content/docs/workflows/examples/index.mdx b/src/content/docs/workflows/examples/index.mdx index b7030341362eabd..c305f7917ef78ce 100644 --- a/src/content/docs/workflows/examples/index.mdx +++ b/src/content/docs/workflows/examples/index.mdx @@ -5,6 +5,8 @@ pcx_content_type: navigation title: Examples sidebar: order: 6 + group: + hideIndex: true --- diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 477ed5c8a65a829..d311cb79d012ff3 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -27,15 +27,10 @@ You can build Workflows to post-process file uploads to [R2 object storage](/r2/ This guide is for users who are already familiar with Cloudflare Workers and [durable execution](/workflows/reference/how-workflows-works/) programming models. -If you're new to either, we recommend the [introduction to Workflows](/workflows/get-started/guide/) guide, which steps through how a Workflow is defined, how to persist state, and how to deploy and run your first Workflow. +If you are new to either, we recommend the [introduction to Workflows](/workflows/get-started/guide/) guide, which walks you through how a Workflow is defined, how to persist state, and how to deploy and run your first Workflow. ::: - -Before you continue, ensure you: - -1. Sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages) if you have not already. -2. Install [`npm`](https://docs.npmjs.com/getting-started). -3. Install [`Node.js`](https://nodejs.org/en/). Use a Node version manager like [Volta](https://volta.sh/) or [nvm](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](/workers/wrangler/install-and-update/) requires a Node version of `16.17.0` or later. + ## 1. Create a Workflow @@ -152,17 +147,17 @@ You can edit this Workflow by adding (or removing) additional `step` calls, chan ## 2. Deploy a Workflow -Workflows are deployed via [`wrangler`](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Workers scripts, and deployed the same way: +Workflows are deployed via [`wrangler`](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Worker scripts, and are deployed the same way: ```sh npx wrangler@latest deploy ``` -## 3. Running a Workflow +## 3. Run a Workflow You can run a Workflow via the `wrangler` CLI, via a Worker binding, or via the Workflows [REST API](/api/paths/accounts-account_id--workflows/get). -### CLI +### `wrangler` CLI ```sh # Trigger a Workflow from the CLI, and pass (optional) parameters as an event to the Workflow. @@ -229,13 +224,13 @@ export default { }; ``` -Refer to the [triggering Workflows](/workflows/build/trigger-workflows/) documentation for how to trigger a Workflow from other Workers handler functions. +Refer to the [triggering Workflows](/workflows/build/trigger-workflows/) documentation for how to trigger a Workflow from other Workers' handler functions. -## 4. Managing Workflows +## 4. Manage Workflows :::note -The `wrangler workflows` command requires wrangler version `3.82.0` or greater. Use `npx wrangler@latest` to always use the latest wrangler version when invoking commands. +The `wrangler workflows` command requires Wrangler version `3.82.0` or greater. Use `npx wrangler@latest` to always use the latest Wrangler version when invoking commands. ::: @@ -258,7 +253,7 @@ The output of `instances describe` shows: * The status (success, failure, running) of each step * Any state emitted by the step * Any `sleep` state, including when the Workflow will wake up -* Retries associated with each step. +* Retries associated with each step * Errors, including exception messages :::note @@ -269,8 +264,8 @@ You do not have to wait for a Workflow instance to finish executing to inspect i ## Next steps -* Learn more about [how events are passed to a Workflow](/workflows/build/events-and-parameters/) -* Binding to and triggering Workflow instances using the [Workers API](/workflows/build/workers-api/) +* Learn more about [how events are passed to a Workflow](/workflows/build/events-and-parameters/). +* Binding to and triggering Workflow instances using the [Workers API](/workflows/build/workers-api/). * The [Rules of Workflows](/workflows/build/rules-of-workflows/) and best practices for building applications using Workflows. If you have any feature requests or notice any bugs, share your feedback directly with the Cloudflare team by joining the [Cloudflare Developers community on Discord](https://discord.cloudflare.com). diff --git a/src/content/docs/workflows/get-started/index.mdx b/src/content/docs/workflows/get-started/index.mdx index f318b640b29cb8d..3d5d65fb0aba4a8 100644 --- a/src/content/docs/workflows/get-started/index.mdx +++ b/src/content/docs/workflows/get-started/index.mdx @@ -3,6 +3,8 @@ title: Get started pcx_content_type: navigation sidebar: order: 1 + group: + hideIndex: true --- diff --git a/src/content/docs/workflows/index.mdx b/src/content/docs/workflows/index.mdx index a970a62011788f4..a338bcded94fc6e 100644 --- a/src/content/docs/workflows/index.mdx +++ b/src/content/docs/workflows/index.mdx @@ -21,9 +21,9 @@ Build durable multi-step applications on Cloudflare Workers with Workflows. -Workflows is a durable execution engine built on Cloudflare Workers. Workflows allow you to build multi-step applications that can automatically retry, persist state and run for minutes, hours, days or weeks. Workflows introduces a programming model that makes it easier to build reliable, long-running tasks, observe as they progress, and programatically trigger instances based on events across your services. +Workflows is a durable execution engine built on Cloudflare Workers. Workflows allow you to build multi-step applications that can automatically retry, persist state and run for minutes, hours, days, or weeks. Workflows introduces a programming model that makes it easier to build reliable, long-running tasks, observe as they progress, and programatically trigger instances based on events across your services. -Refer to the [get started guide](/workflows/get-started/guide/) to start building with Workflows +Refer to the [get started guide](/workflows/get-started/guide/) to start building with Workflows. *** @@ -76,7 +76,7 @@ Learn more about how Workflows is priced. -Learn about what limits Workflows has and how to work within them. +Learn more about Workflow limits, and how to work within them. diff --git a/src/content/docs/workflows/observability/index.mdx b/src/content/docs/workflows/observability/index.mdx index a1d9cc8345878d0..bb19599ff91a034 100644 --- a/src/content/docs/workflows/observability/index.mdx +++ b/src/content/docs/workflows/observability/index.mdx @@ -3,6 +3,8 @@ title: Observability pcx_content_type: navigation sidebar: order: 5 + group: + hideIndex: true --- diff --git a/src/content/docs/workflows/observability/metrics-analytics.mdx b/src/content/docs/workflows/observability/metrics-analytics.mdx index 16146df03fd0ac7..29f122d624b9937 100644 --- a/src/content/docs/workflows/observability/metrics-analytics.mdx +++ b/src/content/docs/workflows/observability/metrics-analytics.mdx @@ -6,13 +6,13 @@ sidebar: --- -Workflows exposes metrics that allow you to inspect and measure Workflow execution, error rates, steps and total duration across each (and all) of your Workflows. +Workflows expose metrics that allow you to inspect and measure Workflow execution, error rates, steps, and total duration across each (and all) of your Workflows. The metrics displayed in the [Cloudflare dashboard](https://dash.cloudflare.com/) charts are queried from Cloudflare’s [GraphQL Analytics API](/analytics/graphql-api/). You can access the metrics [programmatically](#query-via-the-graphql-api) via GraphQL or HTTP client. ## Metrics -Workflows currently exports the below metrics: +Workflows currently export the below metrics: | Metric | GraphQL Field Name | Description | | ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/src/content/docs/workflows/reference/limits.mdx b/src/content/docs/workflows/reference/limits.mdx index 5a9f7c22f3723a1..884f62a80cbd016 100644 --- a/src/content/docs/workflows/reference/limits.mdx +++ b/src/content/docs/workflows/reference/limits.mdx @@ -20,14 +20,14 @@ Many limits are inherited from those applied to Workers scripts and as documente | Duration (wall clock) per `step` | Unlimited | Unlimited - e.g. waiting on network I/O calls or querying a database | | Maximum persisted state per step | 1MiB (2^20 bytes) | 1MiB (2^20 bytes) | | Maximum state that can be persisted per Workflow instance | 100MB | 1GB | -| Maximum `step.sleep` duration | 365 days (1 year) beta | -| Maximum steps per Workflow | 256 beta | +| Maximum `step.sleep` duration | 365 days (1 year) [^1] | +| Maximum steps per Workflow | 256 [^1] | | Maximum Workflow executions | 100,000 per day [shared with Workers daily limit](/workers/platform/limits/#worker-limits) | Unlimited | -| Concurrent Workflow instances (executions) | 25 | 1,000 beta | -| Retention limit for completed Workflow state | 3 days | 30 days 1 | +| Concurrent Workflow instances (executions) | 25 | 1,000 [^1] | +| Retention limit for completed Workflow state | 3 days | 30 days [^2] | beta This limit will be reviewed and revised during the open beta for Workflows, and we'll update this page and our changelog. -1 Workflow state and logs will be retained for 3 days on the Workers Free plan and 30 days on the Workers Paid plan. +[^2]: Workflow state and logs will be retained for 3 days on the Workers Free plan and 30 days on the Workers Paid plan. diff --git a/src/content/docs/workflows/reference/pricing.mdx b/src/content/docs/workflows/reference/pricing.mdx index 520fdc921bc603c..73f0729840e11c3 100644 --- a/src/content/docs/workflows/reference/pricing.mdx +++ b/src/content/docs/workflows/reference/pricing.mdx @@ -27,18 +27,18 @@ A Workflow that is waiting on a response to an API call, paused as a result of c Frequently asked questions related to Workflows pricing: -- Are there additional costs for Workflows? +### Are there additional costs for Workflows? No. Workflows are priced based on the same compute (CPU time) and requests (invocations) as Workers. -- Is Workflows available on the [Workers Free](/workers/platform/pricing/#workers) plan? +### Are Workflows available on the [Workers Free](/workers/platform/pricing/#workers) plan? Yes. -- How does Workflows show up on my bill? +### How do Workflows show up on my bill? Workflows are billed as Workers, and share the same CPU time and request SKUs. -- Are there any limits to Workflows? +### Are there any limits to Workflows? Refer to the published [limits](/workflows/reference/limits/) documentation. diff --git a/src/content/glossary/workflows.yaml b/src/content/glossary/workflows.yaml index 67692867cd73eef..ddf90ce1503dfee 100644 --- a/src/content/glossary/workflows.yaml +++ b/src/content/glossary/workflows.yaml @@ -9,7 +9,7 @@ entries: general_definition: |- A specific instance (running, paused, errored) of a Workflow. A Workflow can have a potentially infinite number of instances. -- term: "Step" +- term: "step" general_definition: |- A step is self-contained, individually retriable component of a Workflow. Steps may emit (optional) state that allows a Workflow to persist and continue from that step, even if a Workflow fails due to a network or infrastructure issue. A Workflow can have one or more steps up to the [step limit](/workflows/reference/limits/). @@ -19,4 +19,4 @@ entries: - term: "Durable Execution" general_definition: |- - "Durable Execution" is a programming model that allows applications to execute reliably, automatically persist state, retry, and be resistant to errors caused by API, network or even machine/infrastructure failures. Cloudflare Workflows provides a way to build and deploy applications that align with this model. + "Durable Execution" is a programming model that allows applications to execute reliably, automatically persist state, retry, and be resistant to errors caused by API, network or even machine/infrastructure failures. Cloudflare Workflows provide a way to build and deploy applications that align with this model. From dcdf9eef9a461b6d6be7aa90853fdec13cc2135f Mon Sep 17 00:00:00 2001 From: Celso Martinho Date: Wed, 23 Oct 2024 17:08:30 +0100 Subject: [PATCH 37/54] Adds Workflows GraphQL examples and docs --- .../observability/metrics-analytics.mdx | 176 +++++++++++++++--- 1 file changed, 152 insertions(+), 24 deletions(-) diff --git a/src/content/docs/workflows/observability/metrics-analytics.mdx b/src/content/docs/workflows/observability/metrics-analytics.mdx index 16146df03fd0ac7..830211eb355b4b6 100644 --- a/src/content/docs/workflows/observability/metrics-analytics.mdx +++ b/src/content/docs/workflows/observability/metrics-analytics.mdx @@ -38,31 +38,159 @@ Workflows GraphQL datasets require an `accountTag` filter with your Cloudflare a ### Examples -To query the sum of `readQueries`, `writeQueries` for a given `$databaseId`, grouping by `databaseId` and `date`: +To query the count (number of workflow invocations) and sum of `wallTime` for a given `$workflowName` between `$datetimeStart` and `$datetimeEnd`, grouping by `date`: ```graphql -query { - viewer { - accounts(filter: { accountTag: $accountId }) { - d1AnalyticsAdaptiveGroups( - limit: 10000 - filter: { - date_geq: $startDate - date_leq: $endDate - databaseId: $databaseId - } - orderBy: [date_DESC] - ) { - sum { - readQueries - writeQueries - } - dimensions { - date - databaseId - } - } - } - } +{ + viewer { + accounts(filter: { accountTag: $accountTag }) { + wallTime: workflowsAdaptiveGroups( + limit: 10000 + filter: { + datetimeHour_geq: $datetimeStart, + datetimeHour_leq: $datetimeEnd, + workflowName: $workflowName + } + orderBy: [count_DESC] + ) { + count + sum { + wallTime + } + dimensions { + date: datetimeHour + } + } + } + } } ``` + +Here we are doing the same for `wallTime`, `instanceRuns` and `stepCount` in the same query: + +```graphql +{ + viewer { + accounts(filter: { accountTag: $accountTag }) { + instanceRuns: workflowsAdaptiveGroups( + limit: 10000 + filter: { + datetimeHour_geq: $datetimeStart + datetimeHour_leq: $datetimeEnd + workflowName: $workflowName + eventType: "WORKFLOW_START" + } + orderBy: [count_DESC] + ) { + count + dimensions { + date: datetimeHour + } + } + stepCount: workflowsAdaptiveGroups( + limit: 10000 + filter: { + datetimeHour_geq: $datetimeStart + datetimeHour_leq: $datetimeEnd + workflowName: $workflowName + eventType: "STEP_START" + } + orderBy: [count_DESC] + ) { + count + dimensions { + date: datetimeHour + } + } + wallTime: workflowsAdaptiveGroups( + limit: 10000 + filter: { + datetimeHour_geq: $datetimeStart + datetimeHour_leq: $datetimeEnd + workflowName: $workflowName + } + orderBy: [count_DESC] + ) { + count + sum { + wallTime + } + dimensions { + date: datetimeHour + } + } + } + } +} +``` + +Here lets query `workflowsAdaptive` for raw data about `$instanceId` between `$datetimeStart` and `$datetimeEnd`: + +``` +{ + viewer { + accounts(filter: { accountTag: $accountTag }) { + workflowsAdaptive( + limit: 100 + filter: { + datetime_geq: $datetimeStart + datetime_leq: $datetimeEnd + instanceId: $instanceId + } + orderBy: [datetime_ASC] + ) { + datetime + eventType + workflowName + instanceId + stepName + stepCount + wallTime + } + } + } +} +``` + +#### GraphQL query variables + +Example values for the query variables: + +```json +{ + "accountTag": "fedfa729a5b0ecfd623bca1f9000f0a22", + "datetimeStart": "2024-10-20T00:00:00Z", + "datetimeEnd": "2024-10-29T00:00:00Z", + "workflowName": "shoppingCart", + "instanceId": "ecc48200-11c4-22a3-b05f-88a3c1c1db81" +} +``` + +As said above, you can use [introspection](/analytics/graphql-api/features/discovery/introspection/) with your favorite GraphQL client to discover the available fields and types of the Workflows datasets, but here are some of our `workflowsAdaptiveGroups` dimensions: + +* date - The date when trigger was triggered +* datetimeFifteenMinutes - The date and time truncated to fifteen minutes +* datetimeFiveMinutes - The date and time truncated to five minutes +* datetimeHour - The date and time truncated to the hour +* datetimeMinute - The date and time truncated to the minute +* eventType - Event type +* instanceId - Instance Id +* stepCount - Step number +* stepName - Step name +* workflowName - Workflow Name + +And here's a list of `eventType`s you can filter on: + +* WORKFLOW_QUEUED +* ORKFLOW_START +* WORKFLOW_SUCCESS +* WORKFLOW_FAILURE +* WORKFLOW_TERMINATED +* STEP_START +* STEP_SUCCESS +* STEP_FAILURE +* SLEEP_START +* SLEEP_COMPLETE +* ATTEMPT_START +* ATTEMPT_SUCCESS +* ATTEMPT_FAILURE \ No newline at end of file From 43c4b993fa633ae64fb2722aaa871cf69c5d606f Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 12:23:24 -0400 Subject: [PATCH 38/54] Apply suggestions from code review Co-authored-by: Jun Lee --- src/content/docs/workflows/build/trigger-workflows.mdx | 4 +++- src/content/docs/workflows/examples/index.mdx | 2 ++ src/content/docs/workflows/get-started/index.mdx | 2 ++ .../docs/workflows/observability/metrics-analytics.mdx | 2 +- src/content/docs/workflows/reference/limits.mdx | 4 ++-- src/content/docs/workflows/reference/pricing.mdx | 2 +- 6 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index 71f2f1c3b1c417a..80c36f3976a972c 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -151,7 +151,9 @@ Once stopped, the Workflow instance *cannot* be resumed. ### Restart a Workflow -You can restart a Workflow instance by calling `restart` against a specific instance ID. +:::caution + +**Known issue**: Restarting a Workflow via the `restart()` method is not currently supported ad will throw an exception (error). ```ts let instance = await env.MY_WORKFLOW.get("abc-123") diff --git a/src/content/docs/workflows/examples/index.mdx b/src/content/docs/workflows/examples/index.mdx index c305f7917ef78ce..99d307086195093 100644 --- a/src/content/docs/workflows/examples/index.mdx +++ b/src/content/docs/workflows/examples/index.mdx @@ -7,6 +7,8 @@ sidebar: order: 6 group: hideIndex: true + group: + hideIndex: true --- diff --git a/src/content/docs/workflows/get-started/index.mdx b/src/content/docs/workflows/get-started/index.mdx index 3d5d65fb0aba4a8..ef6adf5f38d8d01 100644 --- a/src/content/docs/workflows/get-started/index.mdx +++ b/src/content/docs/workflows/get-started/index.mdx @@ -5,6 +5,8 @@ sidebar: order: 1 group: hideIndex: true + group: + hideIndex: true --- diff --git a/src/content/docs/workflows/observability/metrics-analytics.mdx b/src/content/docs/workflows/observability/metrics-analytics.mdx index 68cf75f1309f678..295f2e5d6b1d7ef 100644 --- a/src/content/docs/workflows/observability/metrics-analytics.mdx +++ b/src/content/docs/workflows/observability/metrics-analytics.mdx @@ -34,7 +34,7 @@ You can optionally select a time window to query. This defaults to the last 24 h You can programmatically query analytics for your Workflows via the [GraphQL Analytics API](/analytics/graphql-api/). This API queries the same datasets as the Cloudflare dashboard, and supports GraphQL [introspection](/analytics/graphql-api/features/discovery/introspection/). -Workflows GraphQL datasets require an `accountTag` filter with your Cloudflare account ID and includes the `workflowsAdaptive` and `workflowsAdaptiveGroups` datasets. +Workflows GraphQL datasets require an `accountTag` filter with your Cloudflare account ID, and includes the `workflowsAdaptive` and `workflowsAdaptiveGroups` datasets. ### Examples diff --git a/src/content/docs/workflows/reference/limits.mdx b/src/content/docs/workflows/reference/limits.mdx index 884f62a80cbd016..a43f990f6dc368c 100644 --- a/src/content/docs/workflows/reference/limits.mdx +++ b/src/content/docs/workflows/reference/limits.mdx @@ -8,7 +8,7 @@ sidebar: import { Render } from "~/components" -Limits that apply to authoring, deploying and running Workflows are detailed below. +Limits that apply to authoring, deploying, and running Workflows are detailed below. Many limits are inherited from those applied to Workers scripts and as documented in the [Workers limits](/workers/platform/limits/) documentation. @@ -26,7 +26,7 @@ Many limits are inherited from those applied to Workers scripts and as documente | Concurrent Workflow instances (executions) | 25 | 1,000 [^1] | | Retention limit for completed Workflow state | 3 days | 30 days [^2] | -beta This limit will be reviewed and revised during the open beta for Workflows, and we'll update this page and our changelog. +[^1]: This limit will be reviewed and revised during the open beta for Workflows, and we'll update this page and our changelog. [^2]: Workflow state and logs will be retained for 3 days on the Workers Free plan and 30 days on the Workers Paid plan. diff --git a/src/content/docs/workflows/reference/pricing.mdx b/src/content/docs/workflows/reference/pricing.mdx index 73f0729840e11c3..a6150403e8f8bcc 100644 --- a/src/content/docs/workflows/reference/pricing.mdx +++ b/src/content/docs/workflows/reference/pricing.mdx @@ -21,7 +21,7 @@ Workflows pricing is identical to [Workers Standard pricing](/workers/platform/p * **CPU time**: the total amount of compute (measured in milliseconds) consumed by a given Workflow. * **Requests** (invocations): the number of Workflow invocations. [Subrequests](/workers/platform/limits/#subrequests) made from a Workflow do not incur additional request costs. -A Workflow that is waiting on a response to an API call, paused as a result of calling `step.sleep`, or otherwise idle does not incur CPU time. +A Workflow that is waiting on a response to an API call, paused as a result of calling `step.sleep`, or otherwise idle, does not incur CPU time. ## Frequently Asked Questions From ad0fb1a9e28970a2eb651da3d68703df2e1406f5 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 12:16:59 -0400 Subject: [PATCH 39/54] types --- .../workflows/build/sleeping-and-retrying.mdx | 2 + .../docs/workflows/build/workers-api.mdx | 106 +++++++++++++----- 2 files changed, 81 insertions(+), 27 deletions(-) diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx index 946f888cca38c74..8a0a45b9de28cc7 100644 --- a/src/content/docs/workflows/build/sleeping-and-retrying.mdx +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -48,6 +48,8 @@ const workflowsLaunchDate = Date.parse("24 Oct 2024 13:00:00 UTC"); await step.sleepUntil("sleep until X times out", workflowsLaunchDate) ``` +You can also provide a Unix timestamp (seconds since the Unix epoch) directly to `sleepUntil`. + ## Retry steps Each call to `step.do` in a Workflow accepts an optional `StepConfig`, which allows you define the retry behaviour for that step. diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index cd73327b77bdf10..557e1605fd36067 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -6,57 +6,109 @@ sidebar: --- -TODO +Workflows exposes a API directly to your Workers scripts via the [bindings](/workers/runtime-apis/bindings/#what-is-a-binding) concept. Bindings allow you to securely call a Workflow without having to manage API keys or clients. -## Binding +This guide details the Workflows API within Cloudflare Workers, including methods, types and usage examples. -TODO +## Bind to a Workflow -## Interact with Workflows +:::note[Workflows beta] -* Running a Workflow with the `run()` method -* `StepConfig` -* `NonRetriableError` -* TODO on TypeScript type params +Workflows currently requires you to bind to a Workflow via `wrangler.toml` and does not yet support bindings via the Workers dashboard. + +::: + +You can bind to a Workflow by defining a `[[workflows]]` binding within your `wrangler.toml` configuration. + +For example, to bind to a Workflow called `workflows-starter` and to make it available on the `MY_WORKFLOW` variable to your Worker script, you would configure the following fields within the `[[workflows]]` binding definition: + +```toml title="wrangler.toml" +#:schema node_modules/wrangler/config-schema.json +name = "workflows-starter" +main = "src/index.ts" +compatibility_date = "2024-10-16" + +[[workflows]] +# name of your workflow +name = "workflows-starter" +# binding name env.MYWORKFLOW +binding = "MY_WORKFLOW" +# this is class that extends the Workflow class in src/index.ts +class_name = "MyWorkflow" +# script_name is required during for the beta. +# Must match the "name" of your Worker at the top of wrangler.toml +script_name = "workflows-starter" +``` ## Workflow -* run(events: WorkflowEvent<T>[], step: WorkflowStep): Promise<T> +* run(event: WorkflowEvent<T>, step: WorkflowStep): Promise<T> + + * `event` - the event passed to the Workflow, including an optional `payload` containing data (parameters) + * `step` - the `WorkflowStep` type that provides the step methods for your Workflow + +The `WorkflowEvent` type accepts an optional [type parameter](https://www.typescriptlang.org/docs/handbook/2/generics.html#working-with-generic-type-variables) that allows you to provide a type for the `payload` property within the `WorkflowEvent`. + +Refer to the [events and parameters](/workflows/build/events-and-parameters/) documentation for how to handle events within yur Workflow code. -TODO +## WorkflowEvent ```ts -export class MyWorkflow extends Workflow { - async run(events: WorkflowEvent[], step: WorkflowStep) { - // TODO - } -} +export type WorkflowEvent = { + payload: Readonly; + timestamp: Date; +}; ``` +* The `WorkflowEvent` is the first argument to a Workflow's `run` method, and includes an optional `payload` parameter and a `timestamp` property. + + * `payload` - a default type of `any` or type `T` if a type parameter is provided. + * `timestamp` - a `Date` object set to the time the Workflow instance was created (triggered). + +Refer to the [events and parameters](/workflows/build/events-and-parameters/) documentation for how to handle events within yur Workflow code. ## WorkflowStep ### step -TODO - into to steps +* step.do(name: string, callback: (): RpcSerializable): Promise<T> +* step.do(name: string, config?: WorkflowStepConfig, callback: (): RpcSerializable): Promise<T> -* step.do(name: string, callback: (): RpcSerializable, config?: StepConfig): Promise<T> + * `name` - the name of the step. + * `config` (optional) - an optional `WorkflowStepConfig` for configuring [step specific retry behaviour](/workflows/build/sleeping-and-retrying/) + * `callback` - an asynchronous function that optionally returns serializable state for the Workflow to persist. - * TODO - describe each param - * TODO - +* step.sleep(name: string, duration: WorkflowDuration): Promise<void> -TODO - show an example -TODO - show an example of dynamically creating a step + * `name` - the name of the step. + * `duration` - the duration to sleep until, in either seconds or as a `WorkflowDuration` compatible string. + * Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how how Workflows are retried. -* step.sleep(name: string, duration: WorkflowDuration): Promise<void> +* step.sleepUntil(name: string, timestamp: Date | number): Promise<void> - * TODO - + * `name` - the name of the step. + * `timestamp` - a JavaScript `Date` object or seconds from the Unix epoch to sleep the Workflow instance until. + +## WorkflowStepConfig + +```ts +export type WorkflowStepConfig = { + retries?: { + limit: number; + delay: string | number; + backoff?: WorkflowBackoff; + }; + timeout?: string | number; +}; +``` -TODO - show an example +* A `WorkflowStepConfig` is an optional argument to the `do` method of a `WorkflowStep` and defines properties that allow you to configure the retry behaviour of that step. +Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how how Workflows are retried. -## StepConfig +## NonRetryableError -TODO +* throw new NonRetryableError(message: , name ): -## NonRetriableError + * Throws an error that forces the current Workflow instance to fail and not be retried. + * Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how how Workflows are retried. From acd1169edd2121ca814e22fcc83c6c6ccc3c2a84 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 12:28:35 -0400 Subject: [PATCH 40/54] changelog + dates --- src/content/changelogs/workflows.yaml | 12 ++++++------ .../docs/workflows/get-started/cli-quick-start.mdx | 2 +- src/content/docs/workflows/get-started/guide.mdx | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/content/changelogs/workflows.yaml b/src/content/changelogs/workflows.yaml index 355a53ebf0b7cde..ffdc7c5419ad54f 100644 --- a/src/content/changelogs/workflows.yaml +++ b/src/content/changelogs/workflows.yaml @@ -1,15 +1,15 @@ --- -link: "/workflows/platform/changelog/" +link: "/workflows/reference/changelog/" productName: Workflows productLink: "/workflows/" productArea: Developer platform -productAreaLink: /workflows/ +productAreaLink: /workers/platform/changelog/platform/ entries: - - publish_date: "2038-01-19" - title: Workflows is now in public beta. + - publish_date: "2024-10-23" + title: "Workflows is now in public beta!" description: |- Workflows, a new product for building reliable, multi-step workflows using Cloudflare Workers, is now in public beta. The public beta is available to any user with a [free or paid Workers plan](/workers/platform/pricing/). - A Workflow allows you to define multiple, independent steps that encapsulate errors, automatically retry, persist state, and can run for seconds, minutes, hours or even days. A Workflow can be useful for post-processing data from R2 buckets before querying it, automating a [Workers AI RAG pipeline](/workers-ai/tutorials/build-a-retrieval-augmented-generation-ai/), or managing user signup flows and lifecycle emails. + A Workflow allows you to define multiple, independent steps that encapsulate errors, automatically retry, persist state, and can run for seconds, minutes, hours or even days. A Workflow can be useful for post-processing data from R2 buckets before querying it, automating a Workers AI RAG pipeline, or managing user signup flows and lifecycle emails. - You can learn more about Workflows in [our announcement blog], or start building in our [get started guide]. + You can learn more about Workflows in [our announcement blog](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers/), or start building in our [get started guide](/workflows/get-started/guide/). diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index d311cb79d012ff3..b4280a9193b5c58 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -1,7 +1,7 @@ --- title: CLI quick start pcx_content_type: get-started -updated: 2038-01-19 +updated: 2024-10-23 sidebar: order: 3 diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index b796f5be0b50e3a..5153f2c430a686e 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -1,7 +1,7 @@ --- title: Guide pcx_content_type: get-started -updated: 2038-01-19 +updated: 2024-10-23 sidebar: order: 1 From 5ddce18a7248bcc37cfa34b84cc1687652dd7746 Mon Sep 17 00:00:00 2001 From: Jun Lee Date: Wed, 23 Oct 2024 18:11:42 +0100 Subject: [PATCH 41/54] Apply suggestions from code review Fixing broken CI (required) pipeline by removing duplicated lines. --- src/content/docs/workflows/examples/index.mdx | 2 -- src/content/docs/workflows/get-started/index.mdx | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/content/docs/workflows/examples/index.mdx b/src/content/docs/workflows/examples/index.mdx index 99d307086195093..c305f7917ef78ce 100644 --- a/src/content/docs/workflows/examples/index.mdx +++ b/src/content/docs/workflows/examples/index.mdx @@ -7,8 +7,6 @@ sidebar: order: 6 group: hideIndex: true - group: - hideIndex: true --- diff --git a/src/content/docs/workflows/get-started/index.mdx b/src/content/docs/workflows/get-started/index.mdx index ef6adf5f38d8d01..3d5d65fb0aba4a8 100644 --- a/src/content/docs/workflows/get-started/index.mdx +++ b/src/content/docs/workflows/get-started/index.mdx @@ -5,8 +5,6 @@ sidebar: order: 1 group: hideIndex: true - group: - hideIndex: true --- From a8de6692fac0f68b63789fb48cdb252f0714de01 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 13:22:46 -0400 Subject: [PATCH 42/54] workflows: Apply suggestions from code review Co-authored-by: Jun Lee --- .../docs/workflows/get-started/guide.mdx | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 5153f2c430a686e..7b418b4c0b60f66 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -31,11 +31,7 @@ At the end of this guide, you should be able to author, deploy and debug your ow ## Prerequisites -To continue: - -1. Sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages) if you have not already. -2. Install [`npm`](https://docs.npmjs.com/getting-started). -3. Install [`Node.js`](https://nodejs.org/en/). Use a Node version manager like [Volta](https://volta.sh/) or [nvm](https://github.com/nvm-sh/nvm) to avoid permission issues and change Node.js versions. [Wrangler](/workers/wrangler/install-and-update/) requires a Node version of `16.17.0` or later. + ## 1. Define your Workflow @@ -83,7 +79,7 @@ A Workflow definition: 2. Has at least one or more calls to `step.do` that encapsulates the logic of your Workflow. 3. Allows steps to return (optional) state, allowing a Workflow to continue execution even if subsequent steps fail, without having to re-run all previous steps. -A single Worker application can contain multiple Workflow definitions, as long as each Workflow has a unique class name. This can be useful for code re-use or to define Workflows are related to each other conceptually. +A single Worker application can contain multiple Workflow definitions, as long as each Workflow has a unique class name. This can be useful for code re-use or to define Workflows which are related to each other conceptually. Each Workflow is otherwise entirely independent: a Worker that defines multiple Workflows is no different from a set of Workers that define one Workflow each. @@ -93,11 +89,11 @@ Each `step` in a Workflow is an independently retriable function. A `step` is what makes a Workflow powerful, as you can encapsulate errors and persist state as your Workflow progresses from step to step, avoiding your application from having to start from scratch on failure and ultimately build more reliable applications. -* A step can execute code (`step.do`) or sleep a Workflow (`step.sleep`) +* A step can execute code (`step.do`) or sleep a Workflow (`step.sleep`). * If a step fails (throws an exception), it will be automatically be retried based on your retry logic. -* Ia step succeeds, any state it returns will be persisted within the Workflow. +* If a step succeeds, any state it returns will be persisted within the Workflow. -At it's most basic, a step looks like this: +At its most basic, a step looks like this: ```ts title="src/index.ts" // Import the Workflow definition @@ -123,7 +119,7 @@ Each call to `step.do` accepts three arguments: 1. (Required) A step name, which identifies the step in logs and telemetry 2. (Required) A callback function that contains the code to run for your step, and any state you want the Workflow to persist -3. (Optional) A `StepConfig` that defines the retry configuration (max retries, delay, and backoff algorithm) for the step. +3. (Optional) A `StepConfig` that defines the retry configuration (max retries, delay, and backoff algorithm) for the step When trying to decide whether to break code up into more than one step, a good rule of thumb is to ask "do I want _all_ of this code to run again if just one part of it fails?". In many cases, you do _not_ want to repeatedly call an API if the following data processing stage fails, or if you get an error when attempting to send a completion or welcome email. @@ -134,13 +130,13 @@ For example, each of the below tasks is ideally encapsulated in its own step, so * Querying a D1 database or a database via [Hyperdrive](/hyperdrive/) * Calling a third-party API -If a subsequent step fails, your Workflow can retry from that step, using any state returned from a previous step. This can also help you avoid unnecessarily querying a database or calling an paid API repeatedly for data you've already fetched. +If a subsequent step fails, your Workflow can retry from that step, using any state returned from a previous step. This can also help you avoid unnecessarily querying a database or calling an paid API repeatedly for data you have already fetched. :::note The term "Durable Execution" is widely used to describe this programming model. -"Durable" describes the ability of the program (application) to implicitly persist state without you having to write to an external store or serialize program state manual +"Durable" describes the ability of the program (application) to implicitly persist state without you having to manually write to an external store or serialize program state. ::: @@ -170,7 +166,7 @@ script_name = "workflows-starter" :::note -If you have changed the name of the Workflow in your wrangler commands, the JavaScript class name, or the name of the project you created, ensure that you update the values above to match. +If you have changed the name of the Workflow in your Wrangler commands, the JavaScript class name, or the name of the project you created, ensure that you update the values above to match the changes. ::: @@ -336,7 +332,7 @@ Deployed workflows-starter triggers (1.12 sec) workflow: workflows-starter ``` -A Worker with a valid Workflow definition will be automatically registered by Workflows. You can list your current Workflows using wrangler: +A Worker with a valid Workflow definition will be automatically registered by Workflows. You can list your current Workflows using Wrangler: ```sh npx wrangler workflows list @@ -355,16 +351,16 @@ Showing last 1 workflow: With your Workflow deployed, you can now run it. 1. A Workflow can run in parallel: each unique invocation of a Workflow is an _instance_ of that Workflow. -2. An instance will run to completion (success or failure) +2. An instance will run to completion (success or failure). 3. Deploying newer versions of a Workflow will cause all instances after that point to run the newest Workflow code. :::note -Because Workflows can be long running, it's possible to have running instances that represent different versions of your Workflow code over time. +Because Workflows can be long running, it is possible to have running instances that represent different versions of your Workflow code over time. ::: -To trigger our Workflow, we will use the `wrangler` CLI and pass in an optional `--payload`. The `payload` will be passed to your Workflow's `run` method handler as an `Event` +To trigger our Workflow, we will use the `wrangler` CLI and pass in an optional `--payload`. The `payload` will be passed to your Workflow's `run` method handler as an `Event`. ```sh npx wrangler workflows trigger workflows-starter '{"hello":"world"}' @@ -447,7 +443,7 @@ From the output above, we can inspect: * The status (success, failure, running) of each step * Any state emitted by the step * Any `sleep` state, including when the Workflow will wake up -* Retries associated with each step. +* Retries associated with each step * Errors, including exception messages :::note @@ -480,8 +476,8 @@ Re-deploying the Workers script containing your Workflow code will re-create the ## Next steps -* Learn more about [how events are passed to a Workflow](/workflows/build/events-and-parameters/) -* Binding to and triggering Workflow instances using the [Workers API](/workflows/build/workers-api/) -* The [Rules of Workflows](/workflows/build/rules-of-workflows/) and best practices for building applications using Workflows. +* Learn more about [how events are passed to a Workflow](/workflows/build/events-and-parameters/). +* Learn more about binding to and triggering Workflow instances using the [Workers API](/workflows/build/workers-api/). +* Learn more about the [Rules of Workflows](/workflows/build/rules-of-workflows/) and best practices for building applications using Workflows. If you have any feature requests or notice any bugs, share your feedback directly with the Cloudflare team by joining the [Cloudflare Developers community on Discord](https://discord.cloudflare.com). From 6fb0e14cfcaf4fc9cccd7b68a2b996c67d0a8322 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 13:56:56 -0400 Subject: [PATCH 43/54] compat date --- src/content/docs/workflows/get-started/guide.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 7b418b4c0b60f66..3c610a926148d10 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -150,7 +150,7 @@ Open the `wrangler.toml` file at the root of your `workflows-starter` folder, wh #:schema node_modules/wrangler/config-schema.json name = "workflows-starter" main = "src/index.ts" -compatibility_date = "2024-10-16" +compatibility_date = "2024-10-23" [[workflows]] # name of your workflow From 8d7f168ddb4d3bab73157a87c8d722aa956e355c Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 15:31:09 -0400 Subject: [PATCH 44/54] remove how-workflows-works for now --- .../workflows/get-started/cli-quick-start.mdx | 4 +-- .../docs/workflows/get-started/guide.mdx | 2 +- .../reference/how-workflows-works.mdx | 27 ------------------- 3 files changed, 3 insertions(+), 30 deletions(-) delete mode 100644 src/content/docs/workflows/reference/how-workflows-works.mdx diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index b4280a9193b5c58..033e4871d74ab40 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -13,7 +13,7 @@ import { Render, PackageManagers } from "~/components" Workflows is in **public beta**, and any developer with a [free or paid Workers plan](/workers/platform/pricing/#workers) can start using Workflows immediately. -To learn more about Workflows, read [the beta announcement blog](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers) and the [How Workflows Works](/workflows/reference/how-workflows-works/) developer documentation. +To learn more about Workflows and how it works, read [the beta announcement blog](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers). ::: @@ -25,7 +25,7 @@ You can build Workflows to post-process file uploads to [R2 object storage](/r2/ :::caution -This guide is for users who are already familiar with Cloudflare Workers and [durable execution](/workflows/reference/how-workflows-works/) programming models. +This guide is for users who are already familiar with Cloudflare Workers the [durable execution](/workflows/reference/glossary/) programming model it enables. If you are new to either, we recommend the [introduction to Workflows](/workflows/get-started/guide/) guide, which walks you through how a Workflow is defined, how to persist state, and how to deploy and run your first Workflow. diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 3c610a926148d10..2c46767953bb6b3 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -13,7 +13,7 @@ import { Render, PackageManagers } from "~/components" Workflows is in **public beta**, and any developer with a [free or paid Workers plan](/workers/platform/pricing/#workers) can start using Workflows immediately. -To learn more about Workflows, read [the beta announcement blog](https://blog.cloudflare.com/THE-BLOG-SLUG-WILL-GO-HERE) and the [How Workflows Works](/workflows/reference/how-workflows-works/) developer documentation. +To learn more about Workflows and how it works, read [the beta announcement blog](https://blog.cloudflare.com/building-workflows-durable-execution-on-workers). ::: diff --git a/src/content/docs/workflows/reference/how-workflows-works.mdx b/src/content/docs/workflows/reference/how-workflows-works.mdx deleted file mode 100644 index 43aad05f0a3b1ea..000000000000000 --- a/src/content/docs/workflows/reference/how-workflows-works.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: How Workflows Works -pcx_content_type: concept -sidebar: - order: 1 - ---- - -TODO - -## Data model - -- Workflow -- Version -- Instances - -## Workflow - -TODO - -## Version - -TODO - -## Instance - -TODO \ No newline at end of file From 9f6490050720b5e290025bc1533a5721780c143e Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 20:28:58 -0400 Subject: [PATCH 45/54] finalize types --- .../workflows/build/trigger-workflows.mdx | 2 + .../docs/workflows/build/workers-api.mdx | 218 +++++++++++++++--- .../observability/metrics-analytics.mdx | 75 +++--- .../docs/workflows/reference/limits.mdx | 11 +- 4 files changed, 239 insertions(+), 67 deletions(-) diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index 80c36f3976a972c..b01c0966cf15e98 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -155,6 +155,8 @@ Once stopped, the Workflow instance *cannot* be resumed. **Known issue**: Restarting a Workflow via the `restart()` method is not currently supported ad will throw an exception (error). +::: + ```ts let instance = await env.MY_WORKFLOW.get("abc-123") await instance.restart() // Returns Promise diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index 557e1605fd36067..3df18741a610017 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -6,42 +6,20 @@ sidebar: --- -Workflows exposes a API directly to your Workers scripts via the [bindings](/workers/runtime-apis/bindings/#what-is-a-binding) concept. Bindings allow you to securely call a Workflow without having to manage API keys or clients. - This guide details the Workflows API within Cloudflare Workers, including methods, types and usage examples. -## Bind to a Workflow - -:::note[Workflows beta] - -Workflows currently requires you to bind to a Workflow via `wrangler.toml` and does not yet support bindings via the Workers dashboard. - -::: +## WorkflowEntrypoint -You can bind to a Workflow by defining a `[[workflows]]` binding within your `wrangler.toml` configuration. - -For example, to bind to a Workflow called `workflows-starter` and to make it available on the `MY_WORKFLOW` variable to your Worker script, you would configure the following fields within the `[[workflows]]` binding definition: - -```toml title="wrangler.toml" -#:schema node_modules/wrangler/config-schema.json -name = "workflows-starter" -main = "src/index.ts" -compatibility_date = "2024-10-16" +The `WorkflowEntrypoint` class is the core element of a Workflow definition. A Workflow must extend this class and define a `run` method with at least one `step` call to be considered a valid Workflow. -[[workflows]] -# name of your workflow -name = "workflows-starter" -# binding name env.MYWORKFLOW -binding = "MY_WORKFLOW" -# this is class that extends the Workflow class in src/index.ts -class_name = "MyWorkflow" -# script_name is required during for the beta. -# Must match the "name" of your Worker at the top of wrangler.toml -script_name = "workflows-starter" +```ts +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // Steps here + } +}; ``` -## Workflow - * run(event: WorkflowEvent<T>, step: WorkflowStep): Promise<T> * `event` - the event passed to the Workflow, including an optional `payload` containing data (parameters) @@ -112,3 +90,183 @@ Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping- * Throws an error that forces the current Workflow instance to fail and not be retried. * Refer to the [documentation on sleeping and retrying](/workflows/build/sleeping-and-retrying/) to learn more about how how Workflows are retried. + +## Call Workflows from Workers + +:::note[Workflows beta] + +Workflows currently requires you to bind to a Workflow via `wrangler.toml` and does not yet support bindings via the Workers dashboard. + +::: + +Workflows exposes a API directly to your Workers scripts via the [bindings](/workers/runtime-apis/bindings/#what-is-a-binding) concept. Bindings allow you to securely call a Workflow without having to manage API keys or clients. + +You can bind to a Workflow by defining a `[[workflows]]` binding within your `wrangler.toml` configuration. + +For example, to bind to a Workflow called `workflows-starter` and to make it available on the `MY_WORKFLOW` variable to your Worker script, you would configure the following fields within the `[[workflows]]` binding definition: + +```toml title="wrangler.toml" +#:schema node_modules/wrangler/config-schema.json +name = "workflows-starter" +main = "src/index.ts" +compatibility_date = "2024-10-16" + +[[workflows]] +# name of your workflow +name = "workflows-starter" +# binding name env.MYWORKFLOW +binding = "MY_WORKFLOW" +# this is class that extends the Workflow class in src/index.ts +class_name = "MyWorkflow" +# script_name is required during for the beta. +# Must match the "name" of your Worker at the top of wrangler.toml +script_name = "workflows-starter" +``` + +## Workflow + +:::note + +Ensure you have `@cloudflare/workers-types` version `4.20241022.0` or later installed when binding to Workflows from within a Workers project. + +::: + +The `Workflow` type provides methods that allow you to create, inspect the status and manage running Workflow instances from within a Worker script. + +```ts +interface Env { + // The 'MY_WORKFLOW' variable should match the "binding" value set in wrangler.toml + MY_WORKFLOW: Workflow; +} +``` + +The `Workflow` type exports the following methods: + +### create + +Create (trigger) a new instance of the given Workflow. + +* create(options?: WorkflowInstanceCreateOptions): Promise<WorkflowInstance> + + * `options` - optional properties to pass when creating an instance. + +An ID is automatically generated, but a user-provided ID can be specified. This can be useful when mapping Workflows to users, merchants or other identifiers in your system. + +```ts +// Create a new Workflow instance with your own ID: +let instance = await env.MY_WORKFLOW.create({ id: myIdDefinedFromOtherSystem }) +return Response.json({ + id: instance.id, + details: await instance.status(), +}); +``` + +Returns a `WorkflowInstance`. + +### get + +Get a specific Workflow instance by ID. + +* get(id: string): Promise<WorkflowInstance> + + * `id` - the ID of the Workflow instance. + +Returns a `WorkflowInstance`. + +## WorkflowInstanceCreateOptions + +Optional properties to pass when creating an instance. + +```ts +interface WorkflowInstanceCreateOptions { + /** + * An id for your Workflow instance. Must be unique within the Workflow. + */ + id?: string; + /** + * The event payload the Workflow instance is triggered with + */ + params?: unknown; +} +``` + +## WorkflowInstance + +Represents a specific instance of a Workflow, and provides methods to manage the instance. + +```ts +declare abstract class WorkflowInstance { + public id: string; + /** + * Pause the instance. + */ + public pause(): Promise; + /** + * Resume the instance. If it is already running, an error will be thrown. + */ + public resume(): Promise; + /** + * Terminate the instance. If it is errored, terminated or complete, an error will be thrown. + */ + public terminate(): Promise; + /** + * Restart the instance. + */ + public restart(): Promise; + /** + * Returns the current status of the instance. + */ + public status(): Promise; +} +``` + +### id + +Return the id of a Workflow. + +* id: string + +### status + +Pause a running Workflow instance. + +* status(): Promise<void> + +### pause + +Pause a running Workflow instance. + +* pause(): Promise<void> + +### restart + +Restart a Workflow instance. + +* restart(): Promise<void> + +### terminate + +Terminate a Workflow instance. + +* terminate(): Promise<void> + +### InstanceStatus + +Details the status of a Workflow instance. + +```ts +type InstanceStatus = { + status: + | "queued" // means that instance is waiting to be started (see concurrency limits) + | "running" + | "paused" + | "errored" + | "terminated" // user terminated the instance while it was running + | "complete" + | "waiting" // instance is hibernating and waiting for sleep or event to finish + | "waitingForPause" // instance is finishing the current work to pause + | "unknown"; + error?: string; + output?: object; +}; +``` diff --git a/src/content/docs/workflows/observability/metrics-analytics.mdx b/src/content/docs/workflows/observability/metrics-analytics.mdx index 295f2e5d6b1d7ef..65c9cb408bd9005 100644 --- a/src/content/docs/workflows/observability/metrics-analytics.mdx +++ b/src/content/docs/workflows/observability/metrics-analytics.mdx @@ -12,7 +12,7 @@ The metrics displayed in the [Cloudflare dashboard](https://dash.cloudflare.com/ ## Metrics -Workflows currently export the below metrics: +Workflows currently export the below metrics within the `workflowsAdaptiveGroups` GraphQL dataset | Metric | GraphQL Field Name | Description | | ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | @@ -20,6 +20,46 @@ Workflows currently export the below metrics: Metrics can be queried (and are retained) for the past 31 days. +### Labels and dimensions + +The `workflowsAdaptiveGroups` dataset provides the following dimensions for filtering and grouping query results: + +* `workflowName` - Workflow name - e.g. `my-workflow` +* `instanceId` - Instance ID. +* `stepName` - Step name +* `eventType` - Event type (see [event types](#event-types)) +* `stepCount` - Step number within a given instance +* `date` - The date when trigger was triggered +* `datetimeFifteenMinutes` - The date and time truncated to fifteen minutes +* `datetimeFiveMinutes` - The date and time truncated to five minutes +* `datetimeHour` - The date and time truncated to the hour +* `datetimeMinute` - The date and time truncated to the minute + +### Event types + +The `eventType` metric allows you to filter (or groupBy) Workflows and steps based on their last observed status. + +The possible values for `eventType` are documented below: + +#### Workflows-level status labels + +* `WORKFLOW_QUEUED` - the Workflow is queued, but not currently running. This can happen when you are at the [concurrency limit](/workflows/reference/limits/) and new instances are waiting for currently running instances to complete. +* `WORKFLOW_START` - the Workflow has started and is running. +* `WORKFLOW_SUCCESS` - the Workflow finished without errors. +* `WORKFLOW_FAILURE` - the Workflow failed due to errors (exhausting retries, errors thrown, etc) +* `WORKFLOW_TERMINATED` - the Workflow was explicitly terminated. + +#### Step-level status labels + +* `STEP_START` - the step has started and is running. +* `STEP_SUCCESS` - the step finished without errors. +* `STEP_FAILURE` - the step failed due to an error. +* `SLEEP_START` - the step is sleeping. +* `SLEEP_COMPLETE` - the step last finished sleeping. +* `ATTEMPT_START` - a step is retrying. +* `ATTEMPT_SUCCESS` - the retry succeeded. +* `ATTEMPT_FAILURE` - the retry attempt failed. + ## View metrics in the dashboard Per-Workflow and instance analytics for Workflows are available in the Cloudflare dashboard. To view current and historical metrics for a database: @@ -34,7 +74,7 @@ You can optionally select a time window to query. This defaults to the last 24 h You can programmatically query analytics for your Workflows via the [GraphQL Analytics API](/analytics/graphql-api/). This API queries the same datasets as the Cloudflare dashboard, and supports GraphQL [introspection](/analytics/graphql-api/features/discovery/introspection/). -Workflows GraphQL datasets require an `accountTag` filter with your Cloudflare account ID, and includes the `workflowsAdaptive` and `workflowsAdaptiveGroups` datasets. +Workflows GraphQL datasets require an `accountTag` filter with your Cloudflare account ID, and includes the `workflowsAdaptiveGroups` dataset. ### Examples @@ -126,7 +166,7 @@ Here we are doing the same for `wallTime`, `instanceRuns` and `stepCount` in the Here lets query `workflowsAdaptive` for raw data about `$instanceId` between `$datetimeStart` and `$datetimeEnd`: -``` +```graphql { viewer { accounts(filter: { accountTag: $accountTag }) { @@ -165,32 +205,3 @@ Example values for the query variables: "instanceId": "ecc48200-11c4-22a3-b05f-88a3c1c1db81" } ``` - -As said above, you can use [introspection](/analytics/graphql-api/features/discovery/introspection/) with your favorite GraphQL client to discover the available fields and types of the Workflows datasets, but here are some of our `workflowsAdaptiveGroups` dimensions: - -* date - The date when trigger was triggered -* datetimeFifteenMinutes - The date and time truncated to fifteen minutes -* datetimeFiveMinutes - The date and time truncated to five minutes -* datetimeHour - The date and time truncated to the hour -* datetimeMinute - The date and time truncated to the minute -* eventType - Event type -* instanceId - Instance Id -* stepCount - Step number -* stepName - Step name -* workflowName - Workflow Name - -And here's a list of `eventType`s you can filter on: - -* WORKFLOW_QUEUED -* ORKFLOW_START -* WORKFLOW_SUCCESS -* WORKFLOW_FAILURE -* WORKFLOW_TERMINATED -* STEP_START -* STEP_SUCCESS -* STEP_FAILURE -* SLEEP_START -* SLEEP_COMPLETE -* ATTEMPT_START -* ATTEMPT_SUCCESS -* ATTEMPT_FAILURE \ No newline at end of file diff --git a/src/content/docs/workflows/reference/limits.mdx b/src/content/docs/workflows/reference/limits.mdx index a43f990f6dc368c..83e24c98bc12653 100644 --- a/src/content/docs/workflows/reference/limits.mdx +++ b/src/content/docs/workflows/reference/limits.mdx @@ -18,16 +18,17 @@ Many limits are inherited from those applied to Workers scripts and as documente | Total scripts per account | 100 | 500 (shared with [Worker script limits](/workers/platform/limits/#account-plan-limits) | | Compute time per Workflow | 10 seconds | 30 seconds of [active CPU time](/workers/platform/limits/#cpu-time) | | Duration (wall clock) per `step` | Unlimited | Unlimited - e.g. waiting on network I/O calls or querying a database | -| Maximum persisted state per step | 1MiB (2^20 bytes) | 1MiB (2^20 bytes) | -| Maximum state that can be persisted per Workflow instance | 100MB | 1GB | +| Maximum persisted state per step | 1MiB (2^20 bytes) | 1MiB (2^20 bytes) | +| Maximum state that can be persisted per Workflow instance | 100MB | 1GB | | Maximum `step.sleep` duration | 365 days (1 year) [^1] | | Maximum steps per Workflow | 256 [^1] | | Maximum Workflow executions | 100,000 per day [shared with Workers daily limit](/workers/platform/limits/#worker-limits) | Unlimited | -| Concurrent Workflow instances (executions) | 25 | 1,000 [^1] | +| Concurrent Workflow instances (executions) | 25 | 100 [^1] | | Retention limit for completed Workflow state | 3 days | 30 days [^2] | +| Maximum length of a Workflow ID | 64 bytes | -[^1]: This limit will be reviewed and revised during the open beta for Workflows, and we'll update this page and our changelog. +[^1]: This limit will be reviewed and revised during the open beta for Workflows. Follow the [Workflows changelog](/workflows/reference/changelog/) for updates. -[^2]: Workflow state and logs will be retained for 3 days on the Workers Free plan and 30 days on the Workers Paid plan. +[^2]: Workflow state and logs will be retained for 3 days on the Workers Free plan and for 7 days on the Workers Paid plan. From 4fdb2b0efd03a1112ab5725c77534619522b322b Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Wed, 23 Oct 2024 20:30:13 -0400 Subject: [PATCH 46/54] wrangler version note --- src/content/docs/workers/wrangler/commands.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/content/docs/workers/wrangler/commands.mdx b/src/content/docs/workers/wrangler/commands.mdx index 91d195be2b7dfc1..5c90bff412d9dfc 100644 --- a/src/content/docs/workers/wrangler/commands.mdx +++ b/src/content/docs/workers/wrangler/commands.mdx @@ -1247,6 +1247,12 @@ Finished processing secrets JSON file: ## `workflows` +:::note + +`wrangler workflows` commands are available in `wrangler` version `3.82.0` or later. + +::: + Manage and configure [Workflows](/workflows/). ### `list` From a7f2553eaa92ad58b250954e81266b81b36f9628 Mon Sep 17 00:00:00 2001 From: Jun Lee Date: Thu, 24 Oct 2024 09:44:54 +0100 Subject: [PATCH 47/54] Importing Type and MetaInfo component Stops the required CI pipeline from breaking. --- src/content/docs/workflows/build/workers-api.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index 3df18741a610017..4eca6a9aac97666 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -6,6 +6,8 @@ sidebar: --- +import { MetaInfo, Type } from "~/components"; + This guide details the Workflows API within Cloudflare Workers, including methods, types and usage examples. ## WorkflowEntrypoint From 5ae5439d02e0fabea3ad53437ede89fd8d82ee03 Mon Sep 17 00:00:00 2001 From: Jun Lee Date: Thu, 24 Oct 2024 10:28:52 +0100 Subject: [PATCH 48/54] Fixing REST API links Not fixing it blocks merge. --- src/content/docs/workflows/build/trigger-workflows.mdx | 4 ++-- src/content/docs/workflows/get-started/cli-quick-start.mdx | 2 +- src/content/docs/workflows/get-started/guide.mdx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index b01c0966cf15e98..7f528fda7113b6a 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -9,7 +9,7 @@ sidebar: You can trigger Workflows both programmatically and via the Workflows APIs, including: 1. With [Workers](/workers) via HTTP requests in a `fetch` handler, or bindings from a `queue` or `scheduled` handler -2. Using the [Workflows REST API](/api/paths/accounts-account_id--workflows/get) +2. Using the [Workflows REST API](/api/operations/wor-list-workflows) 2. Via the [wrangler CLI](/workers/wrangler/commands/#workflows) in your terminal ## Workers API (Bindings) @@ -166,7 +166,7 @@ Restarting an instance will immediately cancel any in-progress steps, erase any ## REST API (HTTP) -Refer to the [Workflows REST API documentation](/api/paths/accounts-account_id--workflows--workflow_name--instances/post). +Refer to the [Workflows REST API documentation](/api/operations/wor-create-new-workflow-instance). ## Command line (CLI) diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 033e4871d74ab40..094a9f1009b0fb3 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -155,7 +155,7 @@ npx wrangler@latest deploy ## 3. Run a Workflow -You can run a Workflow via the `wrangler` CLI, via a Worker binding, or via the Workflows [REST API](/api/paths/accounts-account_id--workflows/get). +You can run a Workflow via the `wrangler` CLI, via a Worker binding, or via the Workflows [REST API](/api/operations/wor-list-workflows). ### `wrangler` CLI diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 2c46767953bb6b3..00b91ad0f1ac72e 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -179,7 +179,7 @@ We have a very basic Workflow definition, but now need to provide a way to call 1. External HTTP requests via a `fetch()` handler 2. Messages from a [Queue](/queues/) 3. A schedule via [Cron Trigger](/workers/configuration/cron-triggers/) -4. Via the [Workflows REST API](/api/paths/accounts-account_id--workflows/get) or [wrangler CLI](/workers/wrangler/commands/#workflows) +4. Via the [Workflows REST API](/api/operations/wor-list-workflows) or [wrangler CLI](/workers/wrangler/commands/#workflows) Return to the `src/index.ts` file we created in the previous step and add a `fetch` handler that _binds_ to our Workflow. This binding allows us to create new Workflow instances, fetch the status of an existing Workflow, pause and/or terminate a Workflow. From a10ae6a704bbc8fb059e68b3da3fef46d68310c4 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 24 Oct 2024 06:42:39 -0400 Subject: [PATCH 49/54] Apply suggestions from code review Co-authored-by: Diogo Ferreira Co-authored-by: Jun Lee --- .../docs/workflows/build/events-and-parameters.mdx | 2 +- .../docs/workflows/build/sleeping-and-retrying.mdx | 6 +++--- src/content/docs/workflows/build/trigger-workflows.mdx | 4 ++-- src/content/docs/workflows/build/workers-api.mdx | 8 ++++---- .../docs/workflows/get-started/cli-quick-start.mdx | 4 ++-- .../docs/workflows/observability/metrics-analytics.mdx | 4 ++-- src/content/docs/workflows/workflows-api.mdx | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/content/docs/workflows/build/events-and-parameters.mdx b/src/content/docs/workflows/build/events-and-parameters.mdx index 8f769caf4006d32..e2b5613fe508674 100644 --- a/src/content/docs/workflows/build/events-and-parameters.mdx +++ b/src/content/docs/workflows/build/events-and-parameters.mdx @@ -84,7 +84,7 @@ When you pass your `YourEventType` to `WorkflowEvent` as a type parameter, the ` ```ts title="src/index.ts" // Import the Workflow definition -import { Workflow, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" +import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent} from 'cloudflare:workers'; export class MyWorkflow implements Workflow { // Pass your type as a type parameter to WorkflowEvent diff --git a/src/content/docs/workflows/build/sleeping-and-retrying.mdx b/src/content/docs/workflows/build/sleeping-and-retrying.mdx index 8a0a45b9de28cc7..4836e04fb4dd5e5 100644 --- a/src/content/docs/workflows/build/sleeping-and-retrying.mdx +++ b/src/content/docs/workflows/build/sleeping-and-retrying.mdx @@ -60,10 +60,10 @@ If you do not provide your own retry configuration, Workflows applies the follow const defaultConfig: WorkflowStepConfig = { retries: { limit: 5, - delay: 1000, - backoff: "constant", + delay: 10000, + backoff: 'exponential', }, - timeout: "15 minutes", + timeout: '10 minutes', }; ``` diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index 7f528fda7113b6a..6741d830f9dbae9 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -34,7 +34,7 @@ To bind to a Workflow from your Workers code, you need to define a [binding](/wo ```toml title="wrangler.toml" name = "workflows-tutorial" main = "src/index.ts" -compatibility_date = "2024-10-15" +compatibility_date = "2024-10-22" [[workflows]] # The name of the Workflow @@ -153,7 +153,7 @@ Once stopped, the Workflow instance *cannot* be resumed. :::caution -**Known issue**: Restarting a Workflow via the `restart()` method is not currently supported ad will throw an exception (error). +**Known issue**: Restarting a Workflow via the `restart()` method is not currently supported and will throw an exception (error). ::: diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index 4eca6a9aac97666..1f2f70ae824db35 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -8,7 +8,7 @@ sidebar: import { MetaInfo, Type } from "~/components"; -This guide details the Workflows API within Cloudflare Workers, including methods, types and usage examples. +This guide details the Workflows API within Cloudflare Workers, including methods, types, and usage examples. ## WorkflowEntrypoint @@ -101,7 +101,7 @@ Workflows currently requires you to bind to a Workflow via `wrangler.toml` and d ::: -Workflows exposes a API directly to your Workers scripts via the [bindings](/workers/runtime-apis/bindings/#what-is-a-binding) concept. Bindings allow you to securely call a Workflow without having to manage API keys or clients. +Workflows exposes an API directly to your Workers scripts via the [bindings](/workers/runtime-apis/bindings/#what-is-a-binding) concept. Bindings allow you to securely call a Workflow without having to manage API keys or clients. You can bind to a Workflow by defining a `[[workflows]]` binding within your `wrangler.toml` configuration. @@ -133,7 +133,7 @@ Ensure you have `@cloudflare/workers-types` version `4.20241022.0` or later inst ::: -The `Workflow` type provides methods that allow you to create, inspect the status and manage running Workflow instances from within a Worker script. +The `Workflow` type provides methods that allow you to create, inspect the status, and manage running Workflow instances from within a Worker script. ```ts interface Env { @@ -230,7 +230,7 @@ Return the id of a Workflow. ### status -Pause a running Workflow instance. +Return the status of a running Workflow instance. * status(): Promise<void> diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index 094a9f1009b0fb3..ee299585e1c1098 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -147,7 +147,7 @@ You can edit this Workflow by adding (or removing) additional `step` calls, chan ## 2. Deploy a Workflow -Workflows are deployed via [`wrangler`](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare`, above. Workflows are Worker scripts, and are deployed the same way: +Workflows are deployed via [`wrangler`](/workers/wrangler/install-and-update/), which is installed when you first ran `npm create cloudflare` above. Workflows are Worker scripts, and are deployed the same way: ```sh npx wrangler@latest deploy @@ -195,7 +195,7 @@ For example, the following Worker will fetch the status of an existing Workflow ```ts title="src/index.ts" // Import the Workflow definition -import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" +import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent} from 'cloudflare:workers'; interface Env { // Matches the binding definition in your wrangler.toml diff --git a/src/content/docs/workflows/observability/metrics-analytics.mdx b/src/content/docs/workflows/observability/metrics-analytics.mdx index 65c9cb408bd9005..b84cd93738d53a3 100644 --- a/src/content/docs/workflows/observability/metrics-analytics.mdx +++ b/src/content/docs/workflows/observability/metrics-analytics.mdx @@ -25,11 +25,11 @@ Metrics can be queried (and are retained) for the past 31 days. The `workflowsAdaptiveGroups` dataset provides the following dimensions for filtering and grouping query results: * `workflowName` - Workflow name - e.g. `my-workflow` -* `instanceId` - Instance ID. +* `instanceId` - Instance ID * `stepName` - Step name * `eventType` - Event type (see [event types](#event-types)) * `stepCount` - Step number within a given instance -* `date` - The date when trigger was triggered +* `date` - The date when the Workflow was triggered * `datetimeFifteenMinutes` - The date and time truncated to fifteen minutes * `datetimeFiveMinutes` - The date and time truncated to five minutes * `datetimeHour` - The date and time truncated to the hour diff --git a/src/content/docs/workflows/workflows-api.mdx b/src/content/docs/workflows/workflows-api.mdx index a61fb4f3c00ebff..ae210cbdd331cf3 100644 --- a/src/content/docs/workflows/workflows-api.mdx +++ b/src/content/docs/workflows/workflows-api.mdx @@ -1,7 +1,7 @@ --- pcx_content_type: navigation title: Workflows REST API -external_link: /api/operations/cloudflare-d1-create-database +external_link: /api/paths/accounts-account_id--workflows/get sidebar: order: 10 From 151eb2e4067a8ff5267e6dd7522d704cf85eca3b Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 24 Oct 2024 06:46:13 -0400 Subject: [PATCH 50/54] fix API URL --- src/content/docs/workflows/workflows-api.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/workflows-api.mdx b/src/content/docs/workflows/workflows-api.mdx index ae210cbdd331cf3..5befbd93331dff1 100644 --- a/src/content/docs/workflows/workflows-api.mdx +++ b/src/content/docs/workflows/workflows-api.mdx @@ -1,7 +1,7 @@ --- pcx_content_type: navigation title: Workflows REST API -external_link: /api/paths/accounts-account_id--workflows/get +external_link: /api/operations/wor-list-workflows sidebar: order: 10 From af8d02c36830b1552ee5611555dae6e8d3910567 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 24 Oct 2024 07:16:46 -0400 Subject: [PATCH 51/54] comment out pause --- .../docs/workers/wrangler/commands.mdx | 5 ++- .../docs/workflows/build/dynamic-steps.mdx | 35 ------------------- .../workflows/build/trigger-workflows.mdx | 7 ++-- .../workflows/get-started/cli-quick-start.mdx | 2 +- 4 files changed, 9 insertions(+), 40 deletions(-) delete mode 100644 src/content/docs/workflows/build/dynamic-steps.mdx diff --git a/src/content/docs/workers/wrangler/commands.mdx b/src/content/docs/workers/wrangler/commands.mdx index 5c90bff412d9dfc..a36edf5d3cdc38e 100644 --- a/src/content/docs/workers/wrangler/commands.mdx +++ b/src/content/docs/workers/wrangler/commands.mdx @@ -1249,7 +1249,7 @@ Finished processing secrets JSON file: :::note -`wrangler workflows` commands are available in `wrangler` version `3.82.0` or later. +The `wrangler workflows` command requires Wrangler version `3.83.0` or greater. Use `npx wrangler@latest` to always use the latest Wrangler version when invoking commands. ::: @@ -1327,6 +1327,7 @@ wrangler workflows instances terminate [OPTIONS] - `ID` - The ID of a Workflow instance. +{/* ### `instances pause` Pause (until resumed) a Workflow instance. @@ -1353,6 +1354,8 @@ wrangler workflows instances resume [OPTIONS] - `ID` - The ID of a Workflow instance. +*/} + ### `describe` ```sh diff --git a/src/content/docs/workflows/build/dynamic-steps.mdx b/src/content/docs/workflows/build/dynamic-steps.mdx deleted file mode 100644 index 6cb4fafa2756a72..000000000000000 --- a/src/content/docs/workflows/build/dynamic-steps.mdx +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Dynamic steps -pcx_content_type: concept -sidebar: - order: 5 - ---- - -A Workflow does not have to define all of its steps statically: steps can be created programmatically and/or conditionally. - -This allows you to not only trigger steps based on specific input parameters, but to also name steps dynamically and set the retry configuration for a single step. - - -## Example - -You can create steps on-the-fly. You can create a step for each parameter passed to your Workflow, for each file you want to read from storage, or for calls to third-party APIs. - -For example, you can loop over each event, label the step dynamically, and have the step operate only over that `event`: - -```ts -export class MyWorkflow extends Workflow { - async run(events: WorkflowEvent[], step: WorkflowStep) { - // Dynamically create a step for each event passed to our workflow - // ... or for every file we want to read from R2 storage - // ... or each API call we need to make based on an incoming request - for (const event of events) { - await step.do(`processing ${event.id}`, async () => { - // Step logic for one event goes here - // You can also specify a StepConfig for each step, just as you - // would for any other step - }) - } - } -} -``` \ No newline at end of file diff --git a/src/content/docs/workflows/build/trigger-workflows.mdx b/src/content/docs/workflows/build/trigger-workflows.mdx index 6741d830f9dbae9..7b631d78a80f8a4 100644 --- a/src/content/docs/workflows/build/trigger-workflows.mdx +++ b/src/content/docs/workflows/build/trigger-workflows.mdx @@ -34,7 +34,7 @@ To bind to a Workflow from your Workers code, you need to define a [binding](/wo ```toml title="wrangler.toml" name = "workflows-tutorial" main = "src/index.ts" -compatibility_date = "2024-10-22" +compatibility_date = "2024-10-15" [[workflows]] # The name of the Workflow @@ -117,7 +117,7 @@ The possible values of status are as follows: output?: object; }; ``` - +{/* ### Explicitly pause a Workflow You can explicitly pause a Workflow instance (and later resume it) by calling `pause` against a specific instance ID. @@ -137,6 +137,7 @@ await instance.resume() // Returns Promise ``` Calling `resume` on an instance that is not currently paused will have no effect. +*/} ### Stop a Workflow @@ -153,7 +154,7 @@ Once stopped, the Workflow instance *cannot* be resumed. :::caution -**Known issue**: Restarting a Workflow via the `restart()` method is not currently supported and will throw an exception (error). +**Known issue**: Restarting a Workflow via the `restart()` method is not currently supported ad will throw an exception (error). ::: diff --git a/src/content/docs/workflows/get-started/cli-quick-start.mdx b/src/content/docs/workflows/get-started/cli-quick-start.mdx index ee299585e1c1098..bad746ae9faae60 100644 --- a/src/content/docs/workflows/get-started/cli-quick-start.mdx +++ b/src/content/docs/workflows/get-started/cli-quick-start.mdx @@ -230,7 +230,7 @@ Refer to the [triggering Workflows](/workflows/build/trigger-workflows/) documen :::note -The `wrangler workflows` command requires Wrangler version `3.82.0` or greater. Use `npx wrangler@latest` to always use the latest Wrangler version when invoking commands. +The `wrangler workflows` command requires Wrangler version `3.83.0` or greater. Use `npx wrangler@latest` to always use the latest Wrangler version when invoking commands. ::: From 336d86313f204092a11bc1ab99afb2ab063133d9 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 24 Oct 2024 07:19:07 -0400 Subject: [PATCH 52/54] fix bad class naming --- src/content/docs/workflows/build/events-and-parameters.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/workflows/build/events-and-parameters.mdx b/src/content/docs/workflows/build/events-and-parameters.mdx index e2b5613fe508674..50b79bf839b44a5 100644 --- a/src/content/docs/workflows/build/events-and-parameters.mdx +++ b/src/content/docs/workflows/build/events-and-parameters.mdx @@ -86,7 +86,7 @@ When you pass your `YourEventType` to `WorkflowEvent` as a type parameter, the ` // Import the Workflow definition import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent} from 'cloudflare:workers'; -export class MyWorkflow implements Workflow { +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, step: WorkflowStep) { From 925dd822467decc9ef1850507cb2fe1b5a8d2085 Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 24 Oct 2024 07:23:59 -0400 Subject: [PATCH 53/54] edits --- src/content/docs/workflows/build/workers-api.mdx | 2 +- src/content/docs/workflows/get-started/guide.mdx | 6 +++--- .../docs/workflows/observability/metrics-analytics.mdx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/content/docs/workflows/build/workers-api.mdx b/src/content/docs/workflows/build/workers-api.mdx index 1f2f70ae824db35..b9c45e2dc15706f 100644 --- a/src/content/docs/workflows/build/workers-api.mdx +++ b/src/content/docs/workflows/build/workers-api.mdx @@ -55,7 +55,7 @@ Refer to the [events and parameters](/workflows/build/events-and-parameters/) do * step.do(name: string, config?: WorkflowStepConfig, callback: (): RpcSerializable): Promise<T> * `name` - the name of the step. - * `config` (optional) - an optional `WorkflowStepConfig` for configuring [step specific retry behaviour](/workflows/build/sleeping-and-retrying/) + * `config` (optional) - an optional `WorkflowStepConfig` for configuring [step specific retry behaviour](/workflows/build/sleeping-and-retrying/). * `callback` - an asynchronous function that optionally returns serializable state for the Workflow to persist. * step.sleep(name: string, duration: WorkflowDuration): Promise<void> diff --git a/src/content/docs/workflows/get-started/guide.mdx b/src/content/docs/workflows/get-started/guide.mdx index 00b91ad0f1ac72e..85f90a645813da3 100644 --- a/src/content/docs/workflows/get-started/guide.mdx +++ b/src/content/docs/workflows/get-started/guide.mdx @@ -47,7 +47,7 @@ Open the `src/index.ts` file in your text editor. This file contains the followi ```ts title="src/index.ts" // Import the Workflow definition -import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" +import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workers" // Create your own class that implements a Workflow export class MyWorkflow implements WorkflowEntrypoint { @@ -60,7 +60,7 @@ export class MyWorkflow implements WorkflowEntrypoint { await step.sleep('wait on something', '1 minute'); - await await step.do( + await step.do( 'make a call to write that could maybe, just might, fail', async () => { // Do stuff here, with access to 'state' from the previous step @@ -97,7 +97,7 @@ At its most basic, a step looks like this: ```ts title="src/index.ts" // Import the Workflow definition -import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workflows" +import { WorkflowEntrypoint, WorkflowEvent, WorkflowStep } from "cloudflare:workers" // Create your own class that implements a Workflow export class MyWorkflow implements WorkflowEntrypoint { diff --git a/src/content/docs/workflows/observability/metrics-analytics.mdx b/src/content/docs/workflows/observability/metrics-analytics.mdx index b84cd93738d53a3..e67e25e73e4495d 100644 --- a/src/content/docs/workflows/observability/metrics-analytics.mdx +++ b/src/content/docs/workflows/observability/metrics-analytics.mdx @@ -12,7 +12,7 @@ The metrics displayed in the [Cloudflare dashboard](https://dash.cloudflare.com/ ## Metrics -Workflows currently export the below metrics within the `workflowsAdaptiveGroups` GraphQL dataset +Workflows currently export the below metrics within the `workflowsAdaptiveGroups` GraphQL dataset. | Metric | GraphQL Field Name | Description | | ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | @@ -46,7 +46,7 @@ The possible values for `eventType` are documented below: * `WORKFLOW_QUEUED` - the Workflow is queued, but not currently running. This can happen when you are at the [concurrency limit](/workflows/reference/limits/) and new instances are waiting for currently running instances to complete. * `WORKFLOW_START` - the Workflow has started and is running. * `WORKFLOW_SUCCESS` - the Workflow finished without errors. -* `WORKFLOW_FAILURE` - the Workflow failed due to errors (exhausting retries, errors thrown, etc) +* `WORKFLOW_FAILURE` - the Workflow failed due to errors (exhausting retries, errors thrown, etc). * `WORKFLOW_TERMINATED` - the Workflow was explicitly terminated. #### Step-level status labels From e858bb1e8f2696e4a9a52bd584e190ff3edf934b Mon Sep 17 00:00:00 2001 From: Matt Silverlock Date: Thu, 24 Oct 2024 07:35:39 -0400 Subject: [PATCH 54/54] naming steps --- .../workflows/build/rules-of-workflows.mdx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/content/docs/workflows/build/rules-of-workflows.mdx b/src/content/docs/workflows/build/rules-of-workflows.mdx index b50a0bf0624fc6d..b89258b3ddeb5b5 100644 --- a/src/content/docs/workflows/build/rules-of-workflows.mdx +++ b/src/content/docs/workflows/build/rules-of-workflows.mdx @@ -224,3 +224,26 @@ export class MyWorkflow extends WorkflowEntrypoint { // Will always be the same if this step is retried }) ``` + +### Name steps deterministically + +Dynamically naming a step will prevent it from being cached, and cause the step to be re-run unnecessarily. Step names act as the "cache key" in your Workflow. + +```ts +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + // πŸ”΄ Bad: Dynamically naming the step 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.user) + event.data = 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.user) + console.log(`fetched at ${Date.now}) + return userData + }) +```