UNPKG

workflow

Version:

Workflow SDK - Build durable, resilient, and observable workflows

297 lines (209 loc) • 11.2 kB
--- title: Starting workflows description: Trigger workflow execution with the start() function and track progress with Run objects. type: guide summary: Trigger workflows and track their execution using the start() function. prerequisites: - /docs/foundations/workflows-and-steps related: - /docs/api-reference/workflow-api/start --- After you define a workflow function, use the `start()` function from `workflow/api` to trigger it. The function enqueues a new workflow run and returns a `Run` object for tracking its progress. ## The `start()` function The [`start()`](/docs/api-reference/workflow-api/start) function programmatically triggers workflow executions from runtime contexts such as API routes, Server Actions, or other server-side code. In v5, you can also call `start()` inside a workflow function to spawn a child run or continue work in a new run. ```typescript lineNumbers import { start } from "workflow/api"; import { handleUserSignup } from "./workflows/user-signup"; export async function POST(request: Request) { const { email } = await request.json(); // Start the workflow const run = await start(handleUserSignup, [email]); // [!code highlight] return Response.json({ message: "Workflow started", runId: run.runId }); } ``` **Key points:** - `start()` returns immediately after enqueuing the workflow. It doesn't wait for completion - The first argument is your workflow function - The second argument is an array of arguments to pass to the workflow (optional if the workflow takes no arguments) - All arguments must be [serializable](/docs/foundations/serialization) - On Worlds with a regional dimension, the optional `region` option pins the new run's storage, queuing, and streams to a specific region. See [Multi-region on the Vercel World](/worlds/vercel#multi-region) **Learn more**: [`start()` API reference](/docs/api-reference/workflow-api/start) <Callout type="info"> For parent-child workflow patterns, see [Workflow Composition](/cookbook/common-patterns/workflow-composition). For long-lived workflows that intentionally hand off to newer deployments with `deploymentId: "latest"`, see [Versioning](/docs/foundations/versioning). </Callout> ## The `Run` object When you call `start()`, it returns a [`Run`](/docs/api-reference/workflow-api/start#returns) object that provides access to the workflow's status and results. ```typescript lineNumbers import { start } from "workflow/api"; import { processOrder } from "./workflows/process-order"; const run = await start(processOrder, [/* orderId */]); // The run object has properties you can await console.log("Run ID:", run.runId); // Check the workflow status const status = await run.status; // "running" | "completed" | "failed" // Get the workflow's return value (blocks until completion) const result = await run.returnValue; ``` **Key properties:** - `runId`: Unique identifier for this workflow run - `status`: Current status of the workflow (async) - `returnValue`: The value returned by the workflow function (async, blocks until completion) - `readable`: `ReadableStream` for streaming updates from the workflow <Callout type="info"> Most `Run` properties are async getters that return promises. `await` them to get their values. For a complete list of properties and methods, see the API reference below. </Callout> **Learn more**: [`Run` API reference](/docs/api-reference/workflow-api/start#returns) ## Common patterns ### Starting workflows from workflow functions You can also call `start()` directly inside workflow functions to spawn child workflows. For choosing between this and awaiting a workflow function directly, see [Workflow Composition](/cookbook/common-patterns/workflow-composition). ```typescript lineNumbers import { start } from "workflow/api"; import { childWorkflow } from "./workflows/child"; export async function parentWorkflow(inputValue: number) { "use workflow"; const childRun = await start(childWorkflow, [inputValue]); // [!code highlight] // childRun is a full Run object. Use it like normal. const childResult = await childRun.returnValue; return { childRunId: childRun.runId, childResult }; } ``` When you call `start()` inside a workflow function, it automatically executes through an internal step to maintain deterministic replay. The returned `Run` object works as it does outside workflows. Properties such as `.runId`, `.status`, and `.returnValue`, and methods such as `.cancel()`, are all available. Each property access or method call executes as a separate step. If the child run fails or is canceled, `await childRun.returnValue` throws [`WorkflowRunFailedError`](/docs/api-reference/workflow-errors/workflow-run-failed-error) or [`WorkflowRunCancelledError`](/docs/api-reference/workflow-errors/workflow-run-cancelled-error) on the first attempt. <Callout type="info"> Inside workflow functions, each `Run` property access (e.g., `run.status`, `run.returnValue`) triggers a workflow step. This means each access is recorded in the event log and replayed deterministically. </Callout> <Callout type="warn"> Awaiting `returnValue` polls the child run every second, and the polling step holds its worker slot open until the child finishes. Size worker-based Worlds to cover the peak number of these polls in flight. If the child workflow is long-running, spawn it without awaiting `returnValue` and have it resume a [hook](/docs/foundations/hooks) when it completes. See the [`startAndWait()` pattern](/cookbook/advanced/child-workflows). </Callout> ### Fire and forget Start a workflow and immediately return to let it execute in the background: ```typescript lineNumbers import { start } from "workflow/api"; import { sendNotifications } from "./workflows/notifications"; export async function POST(request: Request) { // Start workflow and don't wait for it const run = await start(sendNotifications, [userId]); // Return immediately return Response.json({ message: "Notifications queued", runId: run.runId }); } ``` ### Wait for completion If you need to wait for the workflow to complete before responding: ```typescript lineNumbers import { start } from "workflow/api"; import { generateReport } from "./workflows/reports"; export async function POST(request: Request) { const run = await start(generateReport, [reportId]); // Wait for the workflow to complete const report = await run.returnValue; // [!code highlight] return Response.json({ report }); } ``` <Callout type="warn"> Waiting for `returnValue` can cause your API route to time out if the workflow takes a long time. </Callout> ### Stream updates to client Stream updates from your workflow as it executes without waiting for completion: ```typescript lineNumbers import { start } from "workflow/api"; import { generateAIContent } from "./workflows/ai-generation"; export async function POST(request: Request) { const { prompt } = await request.json(); // Start the workflow const run = await start(generateAIContent, [prompt]); // Get the readable stream (can also use run.readable as shorthand) const stream = run.getReadable(); // [!code highlight] // Return the stream immediately return new Response(stream, { headers: { "Content-Type": "application/octet-stream", }, }); } ``` Your workflow can write to the stream using [`getWritable()`](/docs/api-reference/workflow/get-writable): ```typescript lineNumbers import { getWritable } from "workflow"; export async function generateAIContent(prompt: string) { "use workflow"; const writable = getWritable(); // [!code highlight] await streamContentToClient(writable, prompt); return { status: "complete" }; } async function streamContentToClient( writable: WritableStream, prompt: string ) { "use step"; const writer = writable.getWriter(); // Stream updates as they become available for (let i = 0; i < 10; i++) { const chunk = new TextEncoder().encode(`Update ${i}\n`); await writer.write(chunk); } writer.releaseLock(); } ``` <Callout type="info"> Use streams to show users real-time progress from AI workflows or intermediate results from long-running processes. </Callout> **Learn more**: [Streaming in workflows](/docs/foundations/serialization#streaming) ### Check status later You can retrieve a workflow run later using its `runId` with [`getRun()`](/docs/api-reference/workflow-api/get-run): ```typescript lineNumbers import { getRun } from "workflow/api"; export async function GET(request: Request) { const url = new URL(request.url); const runId = url.searchParams.get("runId"); // Retrieve the existing run const run = getRun(runId); // [!code highlight] // Check its status const status = await run.status; if (status === "completed") { const result = await run.returnValue; return Response.json({ result }); } return Response.json({ status }); } ``` ### Recursive and repeating workflows A workflow can start a new instance of itself. This pattern prevents a single long-running workflow from accumulating too many events. Large event logs are slower to replay, more expensive to store, and harder to inspect in the UI. Breaking work into smaller runs that chain together keeps each run lean. ```typescript lineNumbers import { start } from "workflow/api"; declare function fetchBatch(cursor?: string): Promise<{ items: string[]; nextCursor?: string }>; // @setup declare function processBatch(items: string[]): Promise<void>; // @setup export async function processQueue(cursor?: string) { "use workflow"; const { items, nextCursor } = await fetchBatch(cursor); await processBatch(items); if (nextCursor) { // Continue processing in a new workflow run await start(processQueue, [nextCursor]); // [!code highlight] } } ``` This pattern also enables **repeating cron-like workflows**. A workflow can complete its work, sleep, and then schedule a new instance of itself. This creates an indefinite chain without allowing any single run to grow too large: ```typescript lineNumbers import { sleep } from "workflow"; import { start } from "workflow/api"; declare function refreshMetrics(): Promise<void>; // @setup export async function syncDashboard() { "use workflow"; await refreshMetrics(); await sleep("1h"); // Schedule the next run await start(syncDashboard); // [!code highlight] } ``` #### Starting against the latest deployment By default a chained run starts on the same deployment as its parent. For workflows that chain over long periods, pass [`deploymentId: "latest"`](/docs/api-reference/workflow-api/start#using-deploymentid-latest) so the next run picks up new code. [Versioning](/docs/foundations/versioning#self-upgrading-workflows) covers this pattern in full, including how the serialized state acts as the migration boundary between versions. ## Next steps - Browse the [Cookbook](/cookbook) for copy-paste recipes covering composition, scheduling, timeouts, and more - Explore [Errors and retrying](/docs/foundations/errors-and-retries) to handle failures - Check the [`start()` API reference](/docs/api-reference/workflow-api/start) for complete details