| id | core-api |
|---|---|
| title | Core API |
@tanstack/workflow-core is the replay engine and authoring API.
Creates a workflow definition.
import { createWorkflow } from '@tanstack/workflow-core'
const workflow = createWorkflow({
id: 'checkout',
version: 'v1',
input,
output,
state,
}).handler(async (ctx) => {
return { ok: true }
})Important config:
| Option | Purpose |
|---|---|
id |
Stable workflow ID. |
version |
Optional version persisted with started runs. |
input |
Optional Standard Schema validator for workflow input. |
output |
Optional Standard Schema validator for workflow output. |
state |
Optional Standard Schema validator for workflow state. |
Runs a side effect durably.
const result = await ctx.step('charge-card', async (stepCtx) => {
return stripe.charges.create(
{ amount: ctx.input.amount },
{ idempotencyKey: stepCtx.id },
)
})On replay, a completed step returns the recorded result and does not call the function again.
Use stepCtx.id as the idempotency key for external systems.
Pauses until a signal is delivered.
const now = await ctx.now()
const payment = await ctx.waitForEvent<{ paymentId: string }>(
'payment-received',
{
id: 'payment-webhook',
deadline: now + 24 * 60 * 60_000,
meta: { orderId: ctx.input.orderId },
},
)Resume with runWorkflow({ signalDelivery }) or
runtime.deliverSignal(...).
Pauses until an approval is delivered.
const decision = await ctx.approve({
id: 'refund-approval',
title: 'Approve refund?',
description: `Refund ${ctx.input.amount}`,
})Resume with runWorkflow({ approval }) or runtime.deliverApproval(...).
Pauses until a timer deadline.
await ctx.sleep(30_000)
const now = await ctx.now()
await ctx.sleepUntil(now + 30_000, { id: 'cooldown' })In the runtime package, due timers are resumed by runtime.sweep().
Records deterministic values.
const now = await ctx.now({ id: 'started-at' })
const id = await ctx.uuid({ id: 'correlation-id' })Use these instead of Date.now() and crypto.randomUUID() when the value
affects workflow control flow or output.
For sleep, sleepUntil, waitForEvent, approve, now, and uuid, the
optional id is the stable durable-operation ID used for replay. If omitted,
the engine generates a positional internal ID.
Low-level engine entrypoint.
for await (const event of runWorkflow({
workflow,
input,
runId,
runStore,
signalDelivery,
approval,
})) {
console.log(event.type)
}Use this directly for tests, custom runtimes, or advanced embedding. Use
defineWorkflowRuntime for the production runtime/store/sweep model.
Stateless one-invocation helper around the same engine.
await handleWorkflowWebhook({
workflow,
runStore,
payload: {
runId,
signalDelivery,
},
})Extends workflow context.
const auth = createMiddleware().server<{
user: { id: string }
}>(async ({ next }) => {
return next({ context: { user: await loadUser() } })
})
const workflow = createWorkflow({ id: 'authed' })
.middleware([auth])
.handler(async (ctx) => {
return { userId: ctx.user.id }
})import type {
WorkflowInput,
WorkflowOutput,
WorkflowState,
} from '@tanstack/workflow-core'
type Input = WorkflowInput<typeof workflow>
type Output = WorkflowOutput<typeof workflow>
type State = WorkflowState<typeof workflow>For full generated types and interfaces, see Generated core reference.