Workflow
Use Workflow to run durable server-side processes that can pause, retry, checkpoint, and outlive a single request.
Create workflow files under server/workflows/ with defineWorkflow(handler, options?). Start them with runWorkflow(). When your provider supports it, use getWorkflowRun() later to check the same run again.
Getting started
Install the package
pnpm add https://pkg.pr.new/vite-hub/vitehub/@vitehub/workflow@main
Configure a provider
On Cloudflare, Netlify, and Vercel, ViteHub usually picks the workflow provider automatically from the hosting platform. Set workflow.provider when you want to override that choice or when you use OpenWorkflow. On Cloudflare, set workflow.binding only when you need a different binding name or you want to pass the binding object directly.
export default defineNuxtConfig({
modules: ['@vitehub/workflow/nuxt'],
})
Define a workflow
Create the workflow file for the job you want to run. ViteHub uses the file path as the workflow name, so server/workflows/publish-draft.ts registers publish-draft.
import { defineWorkflow, readValidatedPayload } from '@vitehub/workflow'
export default defineWorkflow(async (input?: { title?: string }) => {
const { title } = await readValidatedPayload(input, value => ({
title: typeof value?.title === 'string' && value.title.trim()
? value.title.trim()
: 'Untitled draft',
}))
return {
title,
slug: title.toLowerCase().replace(/[^a-z0-9]+/g, '-'),
}
})
Start a run
import { createError, readBody } from 'h3'
import { readValidatedPayload, runWorkflow } from '@vitehub/workflow'
export default defineEventHandler(async (event) => {
const { title } = await readValidatedPayload(await readBody(event), (input) => {
if (!input || typeof input !== 'object' || typeof input.title !== 'string' || !input.title.trim()) {
throw createError({
statusCode: 400,
statusMessage: 'Enter a title for the draft.',
})
}
return {
title: input.title.trim(),
}
})
const run = await runWorkflow('publish-draft', { title })
return {
runId: run.id,
status: await run.status(),
}
})
Reattach to the same run later
Persist or return the run id when you start a workflow. Use it later to check status or call provider-specific run methods.
import { getRouterParam } from 'h3'
import { getWorkflowRun } from '@vitehub/workflow'
export default defineEventHandler(async (event) => {
const run = await getWorkflowRun(getRouterParam(event, 'id'))
if (!run)
return { found: false }
return {
found: true,
status: await run.status(),
}
})
Defer a workflow run
Use deferWorkflow() when you want to start durable workflow work after the current response has already been committed. It reads Nitro's current request context internally, prefers a real waitUntil() hook when one exists, and otherwise falls back to best-effort fire-and-forget dispatch.
import { deferWorkflow } from '@vitehub/workflow'
export default defineEventHandler(() => {
deferWorkflow('publish-draft', {
title: 'Hello from waitUntil',
})
return { ok: true }
})
Public API
| Function | Use it for |
|---|
| defineWorkflow(handler, options?) | Register one named workflow from a file under server/workflows/. |
| runWorkflow(name, payload) | Start a new workflow run and get a run handle back immediately. |
| getWorkflowRun(runId) | Reattach to an existing run from a later request, task, or webhook. |
Type reference
Handler signature
The handler receives an optional input and returns the workflow result. ViteHub does not enforce a return type.
type WorkflowHandler<TInput, TResult> = (
input?: TInput,
context?: import('@vitehub/workflow').WorkflowExecutionContext,
) => TResult | Promise<TResult>
WorkflowExecutionContext
Handlers receive a portable execution context as the second argument.
| Field | Type | Description |
|---|---|---|
provider | WorkflowProvider | Active workflow provider name. |
step | WorkflowStepContext | undefined | Portable step helpers. Implemented by Netlify in this release. |
eventId | string | undefined | Provider-native event or run identifier when available. |
attempt | number | undefined | Retry attempt when the provider exposes it. |
sendEvent | ((eventName, options?) => Promise<WorkflowSendEventResult>) | undefined | Provider-backed event publishing helper when available. |
WorkflowDefinitionOptions
The options object passed to defineWorkflow() accepts:
| Option | Type | Description |
|---|---|---|
timeout | number | Maximum execution time in milliseconds. |
WorkflowRun
runWorkflow() returns a WorkflowRun handle with:
| Field / Method | Type | Description |
|---|---|---|
id | string | Unique run identifier. Persist this to reattach later. |
provider | string | The active provider name. |
native | object | Raw provider run object from the active SDK or platform binding. |
status() | Promise<WorkflowRunStatus> | Poll the current run state. |
stop() | Promise<void> | Cancel the run. |
WorkflowRunStatus
| Field | Type | Description |
|---|---|---|
state | 'queued' | 'running' | 'paused' | 'waiting' | 'completed' | 'failed' | 'cancelled' | 'terminated' | 'unknown' | Current run state. |
raw | unknown | Raw provider response. |
output | unknown | Handler return value when the run completes. |
error | unknown | Error details when the run fails. |
Configure provider and workflow options
Workflow has one global layer and one definition layer:
- Top-level
workflowconfig innuxt.config.tsornitro.config.tsselects the provider and sets up the app-wide integration. createWorkflow(options?)(handler)anddefineWorkflow(handler, options?)both configure one workflow definition with portable options such astimeout. ::
defineWorkflow(handler, options?)configures one workflow definition with portable options such astimeout.
- Netlify ignores definition-level
timeoutfor now and executes workflows through Async Workloads event handlers.
runWorkflow() starts a new run with input for that specific execution. getWorkflowRun() reconnects to an existing run by id. Neither one replaces top-level or definition-level configuration.
export default defineNuxtConfig({
modules: ['@vitehub/workflow/nuxt'],
workflow: {
provider: 'cloudflare',
},
})
export default defineWorkflow(async (input) => {
return input
})
workflow, and OpenWorkflow needs openworkflow.