### Full Testing Example for User Onboarding Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/Testing.md A comprehensive example demonstrating the testing of a user onboarding workflow. It includes setup, starting the workflow, checking status, sending verification events, and handling payment failures. ```typescript import { v } from "convex/values"; import { describe, it, expect, beforeEach } from "vitest"; import { convexTest } from "convex-test"; import { register } from "@convex-dev/workflow/test"; import schema from "./convex/schema"; import { internal, components } from "./convex/_generated/api"; describe("User Onboarding Workflow", () => { let t: ReturnType; beforeEach(() => { t = convexTest(schema); register(t); }); it("sends welcome email on start", async () => { // Start the workflow const workflowId = await t.mutation(internal.workflows.startWorkflow, { userId: "user_123", }); expect(workflowId).toBeDefined(); }); it("waits for email verification", async () => { // Start workflow const workflowId = await t.mutation(internal.workflows.startWorkflow, { userId: "user_123", }); // Check status (in progress) let status = await t.query(internal.workflows.getStatus, { workflowId, }); expect(status.type).toBe("inProgress"); // Send verification event await t.mutation(internal.events.sendVerification, { workflowId, verified: true, }); // Now workflow can proceed // Trigger a mutation that continues the workflow await t.mutation(internal.workflows.continueWorkflow, { workflowId, }); }); it("handles payment failures", async () => { const workflowId = await t.mutation(internal.workflows.startPaymentWorkflow, { userId: "user_123", orderId: "order_456", amount: 100, }); // Simulate payment failure by sending error event await t.mutation(internal.events.sendPaymentError, { workflowId, error: "Card declined", }); // Trigger retry await t.mutation(internal.workflows.continueWorkflow, { workflowId, }); // Eventually should fail const status = await t.query(internal.workflows.getStatus, { workflowId, }); expect(status.type).toBe("failed"); }); }); ``` -------------------------------- ### Example: Kickoff Workflow with Start Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/WorkflowManager.md Demonstrates starting a workflow using the `start` method within an internal mutation, including specifying a completion handler and context. ```typescript export const kickoff = internalMutation({ args: { userId: v.id("users") }, handler: async (ctx, args) => { const workflowId = await workflow.start( ctx, internal.index.myWorkflow, { userId: args.userId }, { onComplete: internal.index.handleComplete, context: { userId: args.userId }, }, ); return workflowId; }, }); ``` -------------------------------- ### Install Dependencies and Run Locally Source: https://github.com/get-convex/workflow/blob/main/CONTRIBUTING.md Installs project dependencies and starts the local development server. ```sh npm i npm run dev ``` -------------------------------- ### CallbackOptions Example Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Example of starting a workflow with a specified onComplete handler and context. ```typescript await start(ctx, workflow, args, { onComplete: internal.handlers.onWorkflowDone, context: { workflowName: "myWorkflow" }, }); ``` -------------------------------- ### Example: Kicking Off and Handling Workflow Completion Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/StandaloneFunctions.md This example shows how to use the `start` function to initiate the `userOnboarding` workflow and defines a `handleComplete` mutation to process the workflow's outcome. ```typescript import { start, vWorkflowId, vResultValidator } from "@convex-dev/workflow"; export const kickoffWorkflow = internalMutation({ args: { userId: v.id("users") }, handler: async (ctx, args) => { const id = await start( ctx, internal.workflows.userOnboarding, { userId: args.userId }, { onComplete: internal.workflows.handleComplete, context: { userId: args.userId }, }, ); return id; }, }); export const handleComplete = internalMutation({ args: { workflowId: vWorkflowId, result: vResultValidator, context: v.object({ userId: v.id("users") }), }, handler: async (ctx, args) => { if (args.result.kind === "success") { console.log("Workflow completed:", args.context.userId); } else if (args.result.kind === "error") { console.error("Workflow failed:", args.result.error); } }, }); ``` -------------------------------- ### Start a Workflow from CLI Source: https://github.com/get-convex/workflow/blob/main/README.md Demonstrates how to start a workflow directly from the command line using the Convex CLI. ```sh npx convex run example:exampleWorkflow '{ "args": { "exampleArg": "James" } }' ``` -------------------------------- ### StartOptions Type Definition Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Extended options for start(), including callback setup and an option to start the workflow asynchronously. ```typescript type StartOptions = CallbackOptions & { startAsync?: boolean; }; ``` -------------------------------- ### Example: Defining a User Onboarding Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/StandaloneFunctions.md This example demonstrates how to use `defineWorkflow` to set up arguments, return values, and the handler logic for a user onboarding workflow. ```typescript import { defineWorkflow } from "@convex-dev/workflow"; import { components } from "./_generated/api"; export const userOnboarding = defineWorkflow(components.workflow, { args: { userId: v.id("users") }, returns: v.object({ emailVerified: v.boolean() }), }).handler(async (step, args) => { const user = await step.runQuery(internal.users.getUser, { userId: args.userId }); await step.runMutation(internal.notifications.sendWelcome, { userId: args.userId }); return { emailVerified: user.emailVerified }; }); ``` -------------------------------- ### Start a Workflow with Callbacks Source: https://github.com/get-convex/workflow/blob/main/_autodocs/QUICKSTART.md Start the `sendWelcomeEmail` workflow and define handlers for completion and errors using `start`. ```typescript // convex/mutations.ts import { start, vWorkflowId, vResultValidator } from "@convex-dev/workflow"; import { internal } from "./_generated/api"; export const kickoff = internalMutation({ args: { userId: v.id("users") }, handler: async (ctx, args) => { const workflowId = await start( ctx, internal.workflows.sendWelcomeEmail, { userId: args.userId }, { onComplete: internal.mutations.handleWorkflowComplete, context: { userId: args.userId }, }, ); return workflowId; }, }); export const handleWorkflowComplete = internalMutation({ args: { workflowId: vWorkflowId, result: vResultValidator, context: v.object({ userId: v.id("users") }), }, handler: async (ctx, args) => { if (args.result.kind === "success") { console.log(`Workflow completed for ${args.context.userId}`); // Clean up // await cleanup(ctx, components.workflow, args.workflowId); } else if (args.result.kind === "error") { console.error(`Workflow failed: ${args.result.error}`); } }, }); ``` -------------------------------- ### Run Action with Options Example Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Example demonstrating how to run an action with custom options like name and delayed execution. ```typescript await step.runAction( internal.api.notify, args, { name: "sendNotification", runAfter: 3600000, // 1 hour from now }, ); ``` -------------------------------- ### start() Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/StandaloneFunctions.md Starts a workflow from a mutation or action context. It takes the workflow function reference, arguments, and optional completion handlers. ```APIDOC ## start() ### Description Start a workflow from a mutation or action context. ### Parameters #### Path Parameters - **ctx** (RunMutationCtx) - Required - Convex mutation or action context. - **workflow** (FunctionReference) - Required - The workflow function reference. - **args** (object) - Required - Arguments for the workflow. #### Query Parameters - **options.onComplete** (FunctionReference) - Optional - Mutation to call on completion. Must accept `OnCompleteArgs`. - **options.context** (any) - Optional - Data to pass to the `onComplete` mutation. - **options.startAsync** (boolean) - Optional - Default: `false`. If `true`, enqueue async instead of running inline. ### Returns A unique `WorkflowId` for tracking the workflow. ### Request Example ```typescript import { start, vWorkflowId, vResultValidator } from "@convex-dev/workflow"; export const kickoffWorkflow = internalMutation({ args: { userId: v.id("users") }, handler: async (ctx, args) => { const id = await start( ctx, internal.workflows.userOnboarding, { userId: args.userId }, { onComplete: internal.workflows.handleComplete, context: { userId: args.userId }, }, ); return id; }, }); export const handleComplete = internalMutation({ args: { workflowId: vWorkflowId, result: vResultValidator, context: v.object({ userId: v.id("users") }), }, handler: async (ctx, args) => { if (args.result.kind === "success") { console.log("Workflow completed:", args.context.userId); } else if (args.result.kind === "error") { console.error("Workflow failed:", args.result.error); } }, }); ``` ``` -------------------------------- ### Install Convex Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/QUICKSTART.md Install the Convex workflow package using npm. ```bash npm install @convex-dev/workflow ``` -------------------------------- ### WorkflowComponent Example Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Example of initializing a WorkflowManager with a workflow component. ```typescript import { components } from "./_generated/api"; const manager = new WorkflowManager(components.workflow); ``` -------------------------------- ### Basic Workflow Test Example Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/Testing.md Demonstrates how to set up a test environment, register the workflow component, and run a simple workflow mutation. This is a foundational example for testing workflow logic. ```typescript import { describe, it, expect } from "vitest"; import { convexTest } from "convex-test"; import { register } from "@convex-dev/workflow/test"; import schema from "./convex/schema"; describe("workflows", () => { it("runs a simple workflow", async () => { const t = convexTest(schema); register(t); // Register the workflow component // Now you can test workflows const result = await t.mutation(internal.workflows.myWorkflow, { args: { userId: "123" }, }); expect(result).toBeDefined(); }); }); ``` -------------------------------- ### WorkflowHandler Example Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Example implementation of a WorkflowHandler, demonstrating how to use step.runAction to execute an internal action and return a result. ```typescript const handler: WorkflowHandler< typeof myWorkflowArgs, typeof myWorkflowReturns > = async (step, args) => { const result = await step.runAction(internal.process, args); return { status: "done", data: result }; }; ``` -------------------------------- ### Workflow Call Examples Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Illustrates how to call a workflow using startAsync and directly via ctx.runMutation, including specifying completion handlers and context. ```typescript // Via start(): const id = await start(ctx, internal.workflows.myWorkflow, { userId: "123" }, { onComplete: internal.workflows.handleComplete, context: { userId: "123" }, }); // Direct call: const id = await ctx.runMutation(internal.workflows.myWorkflow, { args: { userId: "123" }, onComplete: await createFunctionHandle(internal.workflows.handleComplete), context: { userId: "123" }, }); ``` -------------------------------- ### Example Usage of WorkflowId Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Demonstrates how to use a `WorkflowId` obtained from `start()` in subsequent workflow operations. ```typescript const workflowId: WorkflowId = await start(ctx, workflow, args); ``` -------------------------------- ### Start a Workflow from a Mutation or Action Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/StandaloneFunctions.md The `start` function initiates a workflow from a mutation or action context. It accepts the context, workflow reference, arguments, and optional completion handlers. ```typescript async function start< Context = unknown, F extends FunctionReference<"mutation", "internal"> = FunctionReference<"mutation", "internal"> >( ctx: RunMutationCtx, workflow: F, args: FunctionArgs["args"], options?: StartOptions, ): Promise ``` -------------------------------- ### start() Source: https://github.com/get-convex/workflow/blob/main/_autodocs/EXPORTS.md Initiates a new workflow execution and returns its unique WorkflowId. ```APIDOC ## start() ### Description Starts a new workflow execution. ### Returns - `WorkflowId` - The unique identifier for the started workflow. ``` -------------------------------- ### Example: Checking Workflow Status Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/StandaloneFunctions.md This example demonstrates how to use `getStatus` within a Convex query to determine if a workflow is currently running and to return its status. ```typescript export const checkWorkflow = query({ args: { workflowId: vWorkflowId }, handler: async (ctx, args) => { const status = await getStatus(ctx, components.workflow, args.workflowId); return { isRunning: status.type === "inProgress", status, }; }, }); ``` -------------------------------- ### Start a Workflow from a Mutation Source: https://github.com/get-convex/workflow/blob/main/README.md Starts a defined workflow asynchronously from a Convex mutation. The `start()` helper returns a `WorkflowId`. ```typescript import { start } from "@convex-dev/workflow"; import { internal } from "./_generated/api"; export const kickoffWorkflow = mutation({ handler: async (ctx): Promise => { // Starts the workflow immediately, run asynchronously const workflowId = await start(ctx, internal.example.exampleWorkflow, { exampleArg: "James", }); return workflowId; }, }); ``` -------------------------------- ### Build One-Off Package Source: https://github.com/get-convex/workflow/blob/main/CONTRIBUTING.md Cleans previous builds, installs exact dependencies, and packages the project. ```sh npm run clean npm ci npm pack ``` -------------------------------- ### Example: Define and Export Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/WorkflowManager.md Illustrates defining a workflow with arguments and return types, then exporting it using the fluent API's handler method. ```typescript const workflow = new WorkflowManager(components.workflow); export const myWorkflow = workflow.define({ args: { userId: v.id("users") }, returns: v.object({ success: v.boolean() }), }).handler(async (step, args) => { const user = await step.runQuery(internal.users.getUser, { userId: args.userId }); return { success: true }; }); ``` -------------------------------- ### OnComplete Handler Example Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Example of an onComplete mutation handler that processes the workflow result and context. ```typescript export const handleComplete = internalMutation({ args: { workflowId: vWorkflowId, result: vResultValidator, context: v.object({ userId: v.id("users") }), }, handler: async (ctx, args) => { const { workflowId, result, context } = args; if (result.kind === "success") { console.log(`Workflow completed for user ${context.userId}`, result.returnValue); } else if (result.kind === "error") { console.error(`Workflow failed:`, result.error); } }, }); ``` -------------------------------- ### Start a Workflow Execution Source: https://github.com/get-convex/workflow/blob/main/_autodocs/README.md Start a workflow execution using `workflow.start`. This function takes the Convex context, the workflow definition, arguments, and optional configuration like completion handlers. ```typescript export const kickoff = internalMutation({ args: { userId: v.id("users") }, handler: async (ctx, args) => { const workflowId = await workflow.start( ctx, internal.workflows.myWorkflow, { userId: args.userId }, { onComplete: internal.workflows.handleComplete, context: { userId: args.userId }, }, ); return workflowId; }, }); ``` -------------------------------- ### Example Usage of WorkflowStep Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Illustrates how to retrieve and iterate over a list of `WorkflowStep` objects using `listSteps()`, logging details about each step's execution. ```typescript const steps = await listSteps(ctx, components.workflow, workflowId); for (const step of steps.page) { console.log(`${step.stepNumber}: ${step.name} (${step.kind})`); if (step.runResult?.kind === "failed") { console.error(` Error: ${step.runResult.error}`); } else if (step.completedAt) { console.log(` Duration: ${step.completedAt - step.startedAt}ms`); } else { console.log(` Still running...`); } } ``` -------------------------------- ### Start Workflow and Wait for Completion Helper Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/Testing.md A helper function to start a workflow and manually trigger its completion. This is useful for testing scenarios where you need to control the workflow's progression. ```typescript // test-helpers.ts export async function startAndWaitForCompletion( t: TestConvex, workflow: FunctionReference, args: any, onCompleteHandler: FunctionReference, ): Promise { const workflowId = await t.mutation(workflow, args); // Manually trigger workflow completion const result = { kind: "success", returnValue: null }; await t.mutation(onCompleteHandler, { workflowId, result, context: {}, }); return workflowId; } ``` -------------------------------- ### Querying File Metadata from _storage System Table Source: https://github.com/get-convex/workflow/blob/main/example/convex/_generated/ai/guidelines.md This example demonstrates how to query file metadata from the `_storage` system table using `ctx.db.system.get`. This is the recommended approach for retrieving file metadata instead of deprecated methods. ```typescript import { query } from "./_generated/server"; import { Id } from "./_generated/dataModel"; type FileMetadata = { _id: Id<"_storage">; _creationTime: number; contentType?: string; sha256: string; size: number; } export const exampleQuery = query({ args: { fileId: v.id("_storage") }, handler: async (ctx, args) => { const metadata: FileMetadata | null = await ctx.db.system.get("_storage", args.fileId); console.log(metadata); return null; }, }); ``` -------------------------------- ### Start a Workflow with onComplete Handler Source: https://github.com/get-convex/workflow/blob/main/README.md Starts a workflow and specifies an `onComplete` handler to process the workflow's result or errors. The `context` object can pass arbitrary data to the handler. ```typescript import { start, vWorkflowId } from "@convex-dev/workflow"; import { vResultValidator } from "@convex-dev/workpool"; export const foo = mutation({ handler: async (ctx): Promise => { const name = "James"; const workflowId = await start( ctx, internal.example.exampleWorkflow, { name }, { onComplete: internal.example.handleOnComplete, context: { intent: "welcome", for: "James" }, // can be anything }, ); return workflowId; }, }); export const handleOnComplete = mutation({ args: { workflowId: vWorkflowId, result: vResultValidator, // used to pass through data from the start site. context: v.object({ intent: v.string(), for: v.string() }), }, handler: async (ctx, args): Promise => { const name = args.context.name; if (args.result.kind === "success") { const text = args.result.returnValue; console.log(`${name} result: ${text}`); } else if (args.result.kind === "error") { console.error("Workflow failed", args.result.error); } else if (args.result.kind === "canceled") { console.log("Workflow canceled", args.context); } }, }); ``` -------------------------------- ### Component Setup in convex.config.ts Source: https://github.com/get-convex/workflow/blob/main/_autodocs/EXPORTS.md Configure the Convex app to use the workflow package by importing and using the workflow plugin in your convex/convex.config.ts file. ```typescript // convex/convex.config.ts import workflow from "@convex-dev/workflow/convex.config.js"; import { defineApp } from "convex/server"; const app = defineApp(); app.use(workflow); export default app); ``` -------------------------------- ### Example Usage of EventId Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Shows how to create an event using `createEvent` and send it using `sendEvent`, utilizing the `EventId` type. ```typescript const eventId: EventId<"approval"> = await createEvent(ctx, component, { name: "approval", workflowId, }); await sendEvent(ctx, component, { id: eventId, value: { approved: true } }); ``` -------------------------------- ### Define a Basic Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/README.md Define a workflow using `workflow.define`. This example shows how to define arguments, return types, and a handler that includes queries, mutations, and sleeps. ```typescript export const myWorkflow = workflow.define({ args: { userId: v.id("users") }, returns: v.object({ success: v.boolean() }), }).handler(async (step, args) => { const user = await step.runQuery(internal.users.getUser, { userId: args.userId }); await step.runMutation(internal.notifications.sendWelcome, { userId: args.userId }); await step.sleep(86400000); // Wait 1 day await step.runMutation(internal.notifications.sendFollowUp, { userId: args.userId }); return { success: true }; }); ``` -------------------------------- ### Example Action with Node.js Runtime Source: https://github.com/get-convex/workflow/blob/main/example/convex/_generated/ai/guidelines.md This snippet demonstrates the basic structure of a Convex action that can utilize Node.js built-in modules. Ensure 'use node' is at the top of files exclusively containing actions. ```typescript import { action } from "./_generated/server"; export const exampleAction = action({ args: {}, handler: async (ctx, args) => { console.log("This action does not return anything"); return null; }, }); ``` -------------------------------- ### Start Workflow Asynchronously Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/WorkflowManager.md Starts a workflow asynchronously, returning a unique WorkflowId. Options include specifying a completion callback and context. ```typescript async start( ctx: RunMutationCtx, workflow: FunctionReference<"mutation", "internal">, args: FunctionArgs["args"], options?: CallbackOptions & { startAsync?: boolean; }, ): Promise ``` -------------------------------- ### Example Usage of WorkflowStatus Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Demonstrates how to handle different `WorkflowStatus` variants using a switch statement to log relevant information about the workflow's current state. ```typescript const status = await getStatus(ctx, components.workflow, workflowId); switch (status.type) { case "inProgress": console.log("Running:", status.running.map(s => s.name)); break; case "completed": console.log("Result:", status.result); break; case "canceled": console.log("Cancelled"); break; case "failed": console.error("Error:", status.error); break; } ``` -------------------------------- ### Standard Workflow Imports Source: https://github.com/get-convex/workflow/blob/main/_autodocs/EXPORTS.md Import common workflow management components for standard usage. This includes WorkflowManager, start, getStatus, and type definitions. ```typescript import { WorkflowManager, start, getStatus, vWorkflowId, type WorkflowId, type WorkflowCtx, } from "@convex-dev/workflow"; import { components, internal } from "./_generated/api"; const workflow = new WorkflowManager(components.workflow); ``` -------------------------------- ### Configure Reliable Payment Processing Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/configuration.md Set up a payment processing workflow with a carefully tuned default retry behavior. This example uses longer initial backoffs to handle potential payment gateway transient errors. ```typescript const paymentWorkflow = workflow.define({ args: { orderId: v.id("orders"), amount: v.number() }, workpoolOptions: { // Payments need careful retry logic defaultRetryBehavior: { maxAttempts: 3, initialBackoffMs: 2000, // longer initial backoff base: 2, }, }, }).handler(async (step, args) => { // Will retry with 2s, 4s, 8s delays const result = await step.runAction( internal.payments.charge, { orderId: args.orderId, amount: args.amount }, { retry: true }, ); }); ``` -------------------------------- ### Example Usage of defineEvent Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/StandaloneFunctions.md Demonstrates how to define an event using defineEvent and then use it within a workflow step to await an event and send an event from an internal mutation. ```typescript import { defineEvent } from "@convex-dev/workflow"; const approvalEvent = defineEvent({ name: "managerApproval", validator: v.object({ approved: v.boolean(), reason: v.optional(v.string()), }), }); // In the workflow: export const requestApproval = workflow.define({ handler: async (step, args) => { const decision = await step.awaitEvent(approvalEvent); if (decision.approved) { // proceed } }, }); // From elsewhere: export const grantApproval = internalMutation({ args: { workflowId: vWorkflowId }, handler: async (ctx, args) => { await sendEvent(ctx, components.workflow, { ...approvalEvent, workflowId: args.workflowId, value: { approved: true, reason: "Looks good" }, }); }, }); ``` -------------------------------- ### Imports for Standalone Workflow Functions Source: https://github.com/get-convex/workflow/blob/main/_autodocs/EXPORTS.md Import functions like defineWorkflow, start, cancel, and list when working with standalone workflow definitions. ```typescript import { defineWorkflow, start, cancel, list, vWorkflowId, } from "@convex-dev/workflow"; import { components } from "./_generated/api"; export const myWorkflow = defineWorkflow(components.workflow, { args: { /* ... */ }, }).handler(async (step, args) => { // ... }); ``` -------------------------------- ### Full-Text Search Query Source: https://github.com/get-convex/workflow/blob/main/example/convex/_generated/ai/guidelines.md Perform a full-text search on a 'messages' table, filtering by channel and matching terms in the message body. This example retrieves the 10 best matches. ```typescript const messages = await ctx.db .query("messages") .withSearchIndex("search_body", (q) => q.search("body", "hello hi").eq("channel", "#general"), ) .take(10); ``` -------------------------------- ### Restart Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/WorkflowManager.md Restarts a failed or completed workflow from a specified step. This is useful for retrying operations or correcting issues without starting from scratch. The `startAsync` option controls whether the restart runs immediately or is enqueued. ```typescript async restart( ctx: RunMutationCtx, workflowId: WorkflowId, options?: { from?: number | string | FunctionReference; startAsync?: boolean; }, ): Promise ``` ```typescript // Restart from step 3 onward await workflow.restart(ctx, workflowId, { from: 3 }); ``` ```typescript // Restart from a named step await workflow.restart(ctx, workflowId, { from: "processPayment" }); ``` ```typescript // Restart from a function reference await workflow.restart(ctx, workflowId, { from: internal.payments.processPayment, }, ); ``` -------------------------------- ### Testing Convex Functions with Vitest Source: https://github.com/get-convex/workflow/blob/main/example/convex/_generated/ai/guidelines.md Use this snippet to test Convex functions with vitest and convex-test. Ensure you have installed the latest versions of convex-test, vitest, and @edge-runtime/vm, and configured vitest with `environment: "edge-runtime"`. ```typescript /// import { convexTest } from "convex-test"; import { expect, test } from "vitest"; import { api } from "./_generated/api"; import schema from "./schema"; const modules = import.meta.glob("./**/*.ts"); test("some behavior", async () => { const t = convexTest(schema, modules); await t.mutation(api.messages.send, { body: "Hi!", author: "Sarah" }); const messages = await t.query(api.messages.list); expect(messages).toMatchObject([{ body: "Hi!", author: "Sarah" }]); }); ``` -------------------------------- ### Define and Handle a User Onboarding Workflow Source: https://github.com/get-convex/workflow/blob/main/README.md This snippet defines a user onboarding workflow that includes running actions, mutations, and other workflows. It demonstrates conditional logic, event waiting, sleeping, and scheduled execution. ```typescript import { WorkflowManager } from "@convex-dev/workflow"; import { components } from "./_generated/api"; export const workflow = new WorkflowManager(components.workflow); export const userOnboarding = workflow .define({ args: { userId: v.id("users") }, }) .handler(async (step, args): Promise => { let result = await step.runAction( internal.llm.generateCustomContent, { userId: args.userId }, // Retry this on transient errors with the default retry policy. { retry: true }, ); while (result.requiresRefinement) { // Run a whole workflow as a single step. result = await step.runWorkflow(internal.llm.refineContentWorkflow, { userId: args.userId, currentResult: result, }); } const email = await step.runMutation( internal.emails.sendWelcomeEmail, { userId: args.userId, content: result.content }, // Optimization: run the mutation synchronously from this transaction. { inline: true }, ); if (email.status === "needsVerification") { // Waits until verification is completed asynchronously. await step.awaitEvent({ name: "emailHasBeenVerified" }); } // Wait 3 days before starting follow-ups. const DAY = 24 * 60 * 60 * 1000; await step.sleep(3 * DAY); for (let i = 0; i < 3; i++) { const sendTime = await getNextBestEmailTime(step, args.userId); const status = await step.runMutation( internal.emails.sendFollowUpEmailMaybe, { userId: args.userId }, // Waits until this time to run this step. { runAt: sendTime }, ); if (!status.ok) break; } }); ``` -------------------------------- ### Run Testing Suite Source: https://github.com/get-convex/workflow/blob/main/CONTRIBUTING.md Executes cleaning, building, type checking, linting, and testing commands. ```sh npm run clean npm run build npm run typecheck npm run lint npm run test ``` -------------------------------- ### Deploy New Version Source: https://github.com/get-convex/workflow/blob/main/CONTRIBUTING.md Initiates the release process for deploying a new version of the project. ```sh npm run release ``` -------------------------------- ### Workflow Management Functions Source: https://github.com/get-convex/workflow/blob/main/_autodocs/INDEX.md Functions to define, start, manage, and query workflows. ```APIDOC ## defineWorkflow() ### Description Defines a standalone workflow. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ## start() ### Description Starts a workflow execution. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ## getStatus() ### Description Checks the current status of a workflow. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ## cancel() ### Description Stops a running workflow. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ## restart() ### Description Resumes a workflow from a specific step. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ## cleanup() ### Description Deletes workflow data. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ## list() ### Description Lists all available workflows. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ## listByName() ### Description Lists workflows filtered by name. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ## listSteps() ### Description Lists the steps within a specific workflow. ### Method N/A (Function Signature) ### Endpoint N/A ### Parameters N/A ### Request Example N/A ### Response N/A ``` -------------------------------- ### CallbackOptions Type Definition Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Options object for specifying an onComplete handler and its context when starting a workflow. ```typescript type CallbackOptions = | { onComplete: FunctionReference<"mutation", FunctionVisibility, OnCompleteArgs>; context: Context; } | { onComplete?: undefined; context?: undefined; }; ``` -------------------------------- ### Array Validator Example Source: https://github.com/get-convex/workflow/blob/main/example/convex/_generated/ai/guidelines.md Use `v.array` to validate arrays, optionally with `v.union` for elements of mixed types. ```typescript import { mutation } from "./_generated/server"; import { v } from "convex/values"; export default mutation({ args: { simpleArray: v.array(v.union(v.string(), v.number())), }, handler: async (ctx, args) => { //... }, }); ``` -------------------------------- ### Implementing Pagination with `paginate()` Source: https://github.com/get-convex/workflow/blob/main/example/convex/_generated/ai/guidelines.md Shows how to define a query that supports pagination using `paginationOptsValidator` and the `.paginate()` method. This is useful for fetching data in chunks. ```typescript import { v } from "convex/values"; import { query, mutation } from "./_generated/server"; import { paginationOptsValidator } from "convex/server"; export const listWithExtraArg = query({ args: { paginationOpts: paginationOptsValidator, author: v.string() }, handler: async (ctx, args) => { return await ctx.db .query("messages") .withIndex("by_author", (q) => q.eq("author", args.author)) .order("desc") .paginate(args.paginationOpts); }, }); ``` -------------------------------- ### listSteps() Source: https://github.com/get-convex/workflow/blob/main/_autodocs/EXPORTS.md Lists workflow steps with pagination support. ```APIDOC ## listSteps() ### Description Lists the steps of workflows, supporting pagination. ### Returns - `PaginationResult` - A paginated list of workflow steps. ``` -------------------------------- ### File Structure Overview Source: https://github.com/get-convex/workflow/blob/main/_autodocs/INDEX.md This snippet shows the directory structure of the @convex-dev/workflow project, detailing the purpose of each main file. ```bash output/ ├── README.md # Overview and core concepts ├── QUICKSTART.md # Common tasks with code examples ├── DEBUGGING.md # Troubleshooting guide ├── EXPORTS.md # Complete export reference ├── types.md # All TypeScript types ├── configuration.md # Configuration options └── api-reference/ ├── WorkflowManager.md # WorkflowManager class API ├── WorkflowCtx.md # Step context (step parameter) ├── StandaloneFunctions.md # Standalone functions └── Testing.md # Testing utilities ``` -------------------------------- ### Test Simple Workflow Processing Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/Testing.md Tests a workflow that processes a list of items. It verifies that the workflow starts and that its status can be queried after initiation. ```typescript it("processes items", async () => { const t = convexTest(schema); register(t); const workflowId = await t.mutation( internal.workflows.processItems, { itemIds: ["a", "b", "c"] }, ); const status = await t.query(internal.workflows.getStatus, { workflowId }); expect(status.type).toBeDefined(); }); ``` -------------------------------- ### list() Source: https://github.com/get-convex/workflow/blob/main/_autodocs/EXPORTS.md Lists workflows with pagination support. ```APIDOC ## list() ### Description Lists workflows, supporting pagination. ### Returns - `PaginationResult` - A paginated list of workflows. ``` -------------------------------- ### Get Workflows by Name Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/StandaloneFunctions.md Retrieves a paginated list of workflows that exactly match a given name. Useful for finding specific types of workflows. ```typescript async function listByName( ctx: RunQueryCtx, component: WorkflowComponent, name: string, opts?: { order?: "asc" | "desc"; paginationOpts?: PaginationOptions; }, ): Promise> ``` ```typescript const userOnboardings = await listByName( ctx, components.workflow, "example/workflows:userOnboarding", ); ``` -------------------------------- ### Get Workflow Status Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/WorkflowManager.md Retrieves the current execution status of a specific workflow using its ID. Useful for monitoring progress or checking for completion/failure. ```typescript async status( ctx: RunQueryCtx, workflowId: WorkflowId, ): Promise ``` ```typescript export const checkStatus = query({ args: { workflowId: vWorkflowId }, handler: async (ctx, args) => { const status = await workflow.status(ctx, args.workflowId); if (status.type === "inProgress") { console.log("Running steps:", status.running.map(s => s.name)); } return status; }, }); ``` -------------------------------- ### List Workflow Steps Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/WorkflowManager.md Retrieves all steps for a given workflow, including their execution details. Supports ordering and pagination. ```typescript async listSteps( ctx: RunQueryCtx, workflowId: WorkflowId, opts?: { order?: "asc" | "desc"; paginationOpts?: PaginationOptions; }, ): Promise> ``` ```typescript const steps = await workflow.listSteps(ctx, workflowId); console.log(steps.page.map(s => `${s.name}: ${s.runResult?.kind}`)); ``` -------------------------------- ### WorkflowArgs Type Definition Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Defines the argument structure for calling a workflow directly or via the start() function, including arguments, completion handlers, and context. ```typescript type WorkflowArgs = { args: ObjectType; startAsync?: boolean; } & ( | { onComplete: FunctionHandle<"mutation", OnCompleteArgs>; context: Context } | { onComplete?: undefined; context?: undefined } ); ``` -------------------------------- ### List All Workflows Source: https://github.com/get-convex/workflow/blob/main/README.md Retrieves a paginated list of all workflows. Accepts optional pagination options. ```typescript await list(ctx, components.workflow, { order: "asc" }); ``` -------------------------------- ### Get Workflow Status Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/StandaloneFunctions.md The `getStatus` function retrieves the current state of a workflow, returning its progress, completion result, cancellation status, or failure error. ```typescript async function getStatus( ctx: RunQueryCtx, component: WorkflowComponent, workflowId: WorkflowId, ): Promise ``` -------------------------------- ### Deploy Alpha Release Source: https://github.com/get-convex/workflow/blob/main/CONTRIBUTING.md Initiates the release process for deploying an alpha version of the project. ```sh npm run alpha ``` -------------------------------- ### Discriminated Union Validator Example Source: https://github.com/get-convex/workflow/blob/main/example/convex/_generated/ai/guidelines.md Define a schema with a discriminated union using `v.union` and `v.literal` to represent different object shapes based on a 'kind' property. ```typescript import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; export default defineSchema({ results: defineTable( v.union( v.object({ kind: v.literal("error"), errorMessage: v.string(), }), v.object({ kind: v.literal("success"), value: v.number(), }), ), ), }); ``` -------------------------------- ### Override Step Retry Behavior Source: https://github.com/get-convex/workflow/blob/main/_autodocs/README.md Configure specific retry behavior for an individual step, overriding both workflow and global defaults. This example sets `maxAttempts` to 2. ```typescript await step.runAction(internal.api.call, args, { retry: { maxAttempts: 2, initialBackoffMs: 100, base: 2 }, }); ``` -------------------------------- ### Define a Workflow with Steps Source: https://github.com/get-convex/workflow/blob/main/_autodocs/QUICKSTART.md Define a workflow named `sendWelcomeEmail` that includes running queries, mutations, sleeping, and actions. ```typescript // convex/workflows.ts import { v } from "convex/values"; import { WorkflowManager } from "@convex-dev/workflow"; import { internal, components } from "./_generated/api"; const workflow = new WorkflowManager(components.workflow); export const sendWelcomeEmail = workflow.define({ args: { userId: v.id("users") }, returns: v.object({ emailSent: v.boolean() }), }).handler(async (step, args) => { // Run a query const user = await step.runQuery(internal.users.getUser, { userId: args.userId }); // Run a mutation await step.runMutation(internal.emails.send, { userId: args.userId, subject: "Welcome", body: `Hello ${user.name}!`, }); // Sleep for 1 day await step.sleep(86400000); // Run an action await step.runAction(internal.analytics.trackEvent, { event: "email_sent", userId: args.userId, }); return { emailSent: true }; }); ``` -------------------------------- ### register() Source: https://github.com/get-convex/workflow/blob/main/_autodocs/EXPORTS.md Registers a workflow for testing purposes. ```APIDOC ## register() ### Description Registers a workflow component for testing. ### Source `src/test.ts:13` ``` -------------------------------- ### Run Steps in Parallel Source: https://github.com/get-convex/workflow/blob/main/_autodocs/DEBUGGING.md Optimize workflow performance by executing steps in parallel using `Promise.all`. This is more efficient than sequential execution for independent steps. ```typescript // Bad: Sequential await step.runAction(internal.process, { id: 1 }); await step.runAction(internal.process, { id: 2 }); await step.runAction(internal.process, { id: 3 }); // Good: Parallel await Promise.all([ step.runAction(internal.process, { id: 1 }), step.runAction(internal.process, { id: 2 }), step.runAction(internal.process, { id: 3 }), ]); ``` -------------------------------- ### status() Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/WorkflowManager.md Get the current execution status of a workflow. This method allows you to check if a workflow is in progress, completed, canceled, or failed, and retrieve relevant details for each state. ```APIDOC ## status() ### Description Get the current execution status of a workflow. ### Method async ### Parameters #### Path Parameters - **ctx** (RunQueryCtx) - Required - Convex query, mutation, or action context. - **workflowId** (WorkflowId) - Required - The workflow ID returned by `start()`. ### Response #### Success Response - **type** (string) - One of: "inProgress", "completed", "canceled", "failed". - **running** (Step[]) - Required if type is "inProgress" - workflow is executing. - **result** (unknown) - Required if type is "completed" - workflow succeeded with a return value. - **error** (string) - Required if type is "failed" - workflow encountered an error. ### Example ```typescript export const checkStatus = query({ args: { workflowId: vWorkflowId }, handler: async (ctx, args) => { const status = await workflow.status(ctx, args.workflowId); if (status.type === "inProgress") { console.log("Running steps:", status.running.map(s => s.name)); } return status; }, }); ``` ``` -------------------------------- ### List All Steps in a Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/README.md Fetch and iterate through all steps of a workflow using `workflow.listSteps`. This logs the step number, name, and run result kind for each step. ```typescript const steps = await workflow.listSteps(ctx, workflowId); steps.page.forEach(step => { console.log(`${step.stepNumber}: ${step.name} (${step.runResult?.kind})`); }); ``` -------------------------------- ### RunOptions Type Source: https://github.com/get-convex/workflow/blob/main/_autodocs/api-reference/WorkflowCtx.md Defines shared options applicable to all step methods, including custom naming and scheduling. ```APIDOC ## RunOptions Type Shared options across all step methods: ```typescript type RunOptions = { name?: string; unstableArgs?: boolean; } & SchedulerOptions; type SchedulerOptions = | { runAt?: number; runAfter?: never } | { runAfter?: number; runAt?: never }; ``` | Option | Type | Description | |--------|------|-------------| | name | string | Custom step name (used in logs, error messages, and restart-by-name). | | unstableArgs | boolean | If `true`, argument values are not validated on replay. Use for non-deterministic args like error stack traces. | | runAt | number | Unix timestamp (ms) to execute this step at. Mutually exclusive with `runAfter`. | | runAfter | number | Milliseconds from now to delay this step. Mutually exclusive with `runAt`. | ``` -------------------------------- ### Initialize Workflow Manager in Convex Project Source: https://github.com/get-convex/workflow/blob/main/README.md Create a workflow manager instance in your Convex project, pointing it to the installed workflow component. This manager is used to define and interact with workflows. ```typescript // convex/index.ts import { WorkflowManager } from "@convex-dev/workflow"; import { components } from "./_generated/api"; export const workflow = new WorkflowManager(components.workflow); ``` -------------------------------- ### List Steps in a Specific Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/QUICKSTART.md Retrieve detailed information about the steps within a given workflow. This helps in debugging and understanding the execution flow. ```typescript import { list, listSteps } from "@convex-dev/workflow"; import { components } from "./_generated/api"; // Get steps in a workflow export const workflowSteps = query({ args: { workflowId: vWorkflowId }, handler: async (ctx, args) => { const result = await listSteps(ctx, components.workflow, args.workflowId); return result.page.map(step => ({ name: step.name, number: step.stepNumber, status: step.runResult?.kind || "running", duration: step.completedAt ? step.completedAt - step.startedAt : undefined, })); }, }); ``` -------------------------------- ### WorkflowId Type Definition Source: https://github.com/get-convex/workflow/blob/main/_autodocs/types.md Defines a branded string type for unique workflow identifiers. Use this type for workflow IDs returned by `start()` and passed to functions like `getStatus()`, `cancel()`, or `restart()`. ```typescript type WorkflowId = string & { __isWorkflowId: true }; const vWorkflowId: Validator; ``` -------------------------------- ### Implement Retry Loop in Workflow Source: https://github.com/get-convex/workflow/blob/main/_autodocs/QUICKSTART.md Add a retry mechanism to workflow steps that might transiently fail. This example shows an exponential backoff strategy for retrying an action up to a maximum number of attempts. ```typescript for (let attempt = 0; attempt < 5; attempt++) { try { return await step.runAction(internal.api.call, args); } catch (error) { if (attempt < 4) { await step.sleep(Math.pow(2, attempt) * 1000); // 1s, 2s, 4s, 8s } else { throw error; } } } ``` -------------------------------- ### Querying with Record and Id Types Source: https://github.com/get-convex/workflow/blob/main/example/convex/_generated/ai/guidelines.md Utilize `Record` with `Id` types for mapping document IDs to other data, ensuring type safety. This example demonstrates mapping user IDs to usernames within a query. ```typescript import { query } from "./_generated/server"; import { Doc, Id } from "./_generated/dataModel"; export const exampleQuery = query({ args: { userIds: v.array(v.id("users")) }, handler: async (ctx, args) => { const idToUsername: Record, string> = {}; for (const userId of args.userIds) { const user = await ctx.db.get("users", userId); if (user) { idToUsername[user._id] = user.username; } } return idToUsername; }, }); ```