1- # Convex Workflow
1+ # Convex Durable Workflows
22
33[ ![ npm version] ( https://badge.fury.io/js/@convex-dev%2Fworkflow.svg? )] ( https://badge.fury.io/js/@convex-dev%2Fworkflow )
44
55<!-- START: Include on https://convex.dev/components -->
66
7+ The Workflow component enables you
8+
79Have you ever wanted to run a series of functions reliably and durably, where
810each can have its own retry behavior, the overall workflow will survive server
911restarts, and you can have long-running workflows spanning months that can be
1012canceled? Do you want to observe the status of a workflow reactively, as well as
1113the results written from each step?
1214
13- And do you want to do this with code, instead of a DSL ?
15+ And do you want to do this with code, instead of a static configuration ?
1416
1517Welcome to the world of Convex workflows.
1618
@@ -32,23 +34,39 @@ import { components } from "./_generated/api";
3234
3335export const workflow = new WorkflowManager (components .workflow );
3436
35- export const exampleWorkflow = workflow .define ({
37+ export const userOnboarding = workflow .define ({
3638 args: {
37- storageId : v .id (" _storage " ),
39+ userId : v .id (" users " ),
3840 },
39- handler : async (step , args ): Promise <number [] > => {
40- const transcription = await step . runAction (
41- internal .index . computeTranscription ,
41+ handler : async (ctx , args ): Promise <void > => {
42+ const status = await ctx . runMutation (
43+ internal .emails . sendVerificationEmail ,
4244 { storageId: args .storageId },
4345 );
4446
45- const embedding = await step .runAction (
46- internal .index .computeEmbedding ,
47- { transcription },
48- // Run this a month after the transcription is computed.
49- { runAfter: 30 * 24 * 60 * 60 * 1000 },
47+ if (status === " needsVerification" ) {
48+ // Waits until verification is completed asynchronously.
49+ await ctx .awaitEvent ({ name: " verificationEmail" });
50+ }
51+ const result = await ctx .runAction (
52+ internal .llm .generateCustomContent ,
53+ { userId: args .userId },
54+ // Retry this on transient errors with the default retry policy.
55+ { retry: true },
56+ );
57+ if (result .needsHumanInput ) {
58+ // Run a whole workflow as a single step.
59+ await ctx .runWorkflow (internal .llm .refineContentWorkflow , {
60+ userId: args .userId ,
61+ });
62+ }
63+
64+ await ctx .runMutation (
65+ internal .emails .sendFollowUpEmailMaybe ,
66+ { userId: args .userId },
67+ // Runs one day after the previous step.
68+ { runAfter: 24 * 60 * 60 * 1000 },
5069 );
51- return embedding ;
5270 },
5371});
5472```
@@ -97,17 +115,19 @@ is designed to feel like a Convex action but with a few restrictions:
97115
981161 . The workflow runs in the background, so it can't return a value.
991172 . The workflow must be _ deterministic_ , so it should implement most of its logic
100- by calling out to other Convex functions. We will be lifting some of these
101- restrictions over time by implementing ` Math.random() ` , ` Date.now() ` , and
102- ` fetch ` within our workflow environment .
118+ by calling out to other Convex functions. We restrict access to some
119+ non-deterministic functions like ` Math.random() ` and ` fetch ` . Others we
120+ patch, such as ` console ` for logging and ` Date ` for time .
103121
104122Note: To help avoid type cycles, always annotate the return type of the ` handler `
105123with the return type of the workflow.
106124
107125``` ts
108126export const exampleWorkflow = workflow .define ({
109127 args: { name: v .string () },
128+ returns: v .string (),
110129 handler : async (step , args ): Promise <string > => {
130+ // ^ Specify the return type of the handler
111131 const queryResult = await step .runQuery (
112132 internal .example .exampleQuery ,
113133 args ,
@@ -283,11 +303,13 @@ export const exampleWorkflow = workflow.define({
283303});
284304```
285305
286- ### Specifying how many workflows can run in parallel
306+ ### Specifying step parallelism
287307
288308You can specify how many steps can run in parallel by setting the
289309` maxParallelism ` workpool option. It has a reasonable default.
290- On the free tier, you should not exceed 20.
310+ On the free tier, you should not exceed 20, otherwise your other scheduled
311+ functions may become delayed while competing for available functions with your
312+ workflow steps.
291313On a Pro account, you should not exceed 100 across all your workflows and workpools.
292314If you want to do a lot of work in parallel, you should employ batching, where
293315each workflow operates on a batch of work, e.g. scraping a list of links instead
0 commit comments