Defining Workflows
Workflows are defined programmatically using the fluent Flow builder API. This provides a strongly-typed and intuitive way to construct your WorkflowBlueprint with compile-time type safety.
Defining Context Types
Before creating workflows, define the shape of your context data using a TypeScript interface:
interface UserProcessingContext {
user_data?: { id: number; name: string }
validation_result?: boolean
processing_status?: 'pending' | 'completed' | 'failed'
}Using createFlow
The entry point to the builder is the createFlow function. It takes a unique ID for your workflow and is generic over your context type for full type safety.
import { createFlow } from 'flowcraft'
// Providing the context type is optional, but recommended
const flowBuilder = createFlow<UserProcessingContext>('my-first-workflow')Adding Nodes with .node()
You add tasks to your workflow using the .node() method. Node functions receive a strongly-typed NodeContext that provides access to the typed context.
const flowBuilder = createFlow<UserProcessingContext>('user-processing')
// A simple function-based node with type safety
.node('fetch-user', async ({ context }) => {
const user = { id: 1, name: 'Alice' }
await context.set('user_data', user)
return { output: user }
})
// A node with type-safe input handling
.node('validate-user', async ({ context, input }) => {
const userData = input as { id: number; name: string }
const isValid = userData.name === 'Alice'
await context.set('validation_result', isValid)
return {
output: isValid,
action: isValid ? 'valid' : 'invalid'
}
}, {
// This tells the runtime to provide the output of 'fetch-user'
// as the 'input' for this node.
inputs: 'fetch-user'
})Adding Edges with .edge()
Edges define the dependencies and control flow between nodes. You create them with the .edge() method, specifying the source and target node IDs.
const flowBuilder = createFlow<UserProcessingContext>('user-processing')
.node('fetch-user', /* ... */)
.node('validate-user', /* ... */)
.node('process-valid', async ({ context }) => {
// Type-safe context access in downstream nodes
const userData = await context.get('user_data')
const validation = await context.get('validation_result')
await context.set('processing_status', 'completed')
return { output: `Processed user ${userData?.name}` }
})
.node('handle-invalid', async ({ context }) => {
await context.set('processing_status', 'failed')
return { output: 'Invalid user data' }
})
// Basic edge: runs 'validate-user' after 'fetch-user'
.edge('fetch-user', 'validate-user')
// Conditional edges based on the 'action' returned by 'validate-user'
.edge('validate-user', 'process-valid', { action: 'valid' })
.edge('validate-user', 'handle-invalid', { action: 'invalid' })This workflow can be visualized as:
Type Safety Benefits
The strongly-typed workflow system provides:
- Context key validation: Only valid keys from your interface can be accessed
- Precise type inference: Context values have exact types, not
any - IntelliSense support: Full autocomplete for context keys and their types
- Compile-time error prevention: Type mismatches caught during development
Adding Wait Nodes for Human-in-the-Loop
For workflows that require external input (e.g., approvals), use the .wait() method to create pause points.
const flowBuilder = createFlow<UserProcessingContext>('approval-workflow')
.node('fetch-user', async ({ context }) => {
const user = { id: 1, name: 'Alice' }
await context.set('user_data', user)
return { output: user }
})
.edge('fetch-user', 'wait-for-approval')
.wait('wait-for-approval') // Pauses execution here
.edge('wait-for-approval', 'process')
.node('process', async ({ input, context }) => {
const approved = input?.approved
if (approved) {
await context.set('processing_status', 'completed')
return { output: 'User approved and processed' }
}
return { output: 'User rejected' }
})
// Execute the workflow
const blueprint = flowBuilder.toBlueprint()
const functionRegistry = flowBuilder.getFunctionRegistry()
const runtime = new FlowRuntime({ registry: functionRegistry })
// Run until it pauses
const initialResult = await runtime.run(blueprint, { user_data: { id: 1, name: 'Alice' } })
if (initialResult.status === 'awaiting') {
// Provide external input and resume
const resumeResult = await runtime.resume(blueprint, initialResult.serializedContext, {
output: { approved: true }
})
console.log('Final result:', resumeResult.context)
}Finalizing the Blueprint
Once your workflow is defined, call .toBlueprint() to get the serializable WorkflowBlueprint object. You will also need the function registry, which contains the node implementations.
// Continuing from above...
const blueprint = flowBuilder.toBlueprint()
const functionRegistry = flowBuilder.getFunctionRegistry()
// Now you can pass these to the FlowRuntime with type safety
// const runtime = new FlowRuntime({ registry: functionRegistry });
// const result = await runtime.run(blueprint, { user_data: initialUser });