Build complex, multi-step processes with a lightweight, composable, and type-safe approach. Model complex business processes, data pipelines, ETL workflows, or AI agents and scale from in-memory scripts to distributed systems without changing the core business logic.
- Zero Dependencies: Lightweight and dependency-free, ensuring easy integration in any runtime.
- Declarative Workflows: Define workflows as serializable objects with nodes and edges.
- Unopinionated Logic: Nodes can be simple functions or structured classes, supporting any logic.
- Progressive Scalability: Run in-memory or scale to distributed systems using the same blueprint.
- Resilient Execution: Built-in support for retries, fallbacks, timeouts, and cancellation.
- Advanced Patterns: Includes batches, loops, subflows, and HITL constructs for complex workflows.
- Extensibility: Pluggable loggers, evaluators, serializers, and middleware for custom behavior.
- Static Analysis: Tools to detect cycles, validate blueprints, and generate visual diagrams.
- Type-Safe API: Fully typed with TypeScript for a robust developer experience.
npm install flowcraftThere are three ways to compose workflows in Flowcraft:
Fluent API — Build workflows programmatically with the createFlow builder.
import { createFlow, FlowRuntime, type NodeContext } from 'flowcraft'
// 1. Define your functions for the nodes
async function startNode({ context }: NodeContext) {
const output = await context.get('value')
return { output }
}
async function doubleNode({ input, context }: NodeContext) {
const output = input * 2
context.set('double', output)
return { output }
}
// 2. Define the workflow structure
const flow = createFlow('simple-workflow')
.node('start', startNode)
.node('double', doubleNode)
.edge('start', 'double')
// 3. Initialize the runtime
const runtime = new FlowRuntime()
// 4. Execute the workflow
async function run() {
const result = await flow.run(runtime, { value: 42 })
console.log(result.context) // { start: 42, double: 84 }
console.log(result.status) // 'completed'
}
run()See the Fluent API Guide for more details.
Declarative — Separate workflow structure (JSON) from node implementations (registry).
import { FlowRuntime } from 'flowcraft'
// 1. Define reusable node functions in a registry
const nodeRegistry = {
startNode: async ({ context }) => {
const value = await context.get('value')
return { output: value }
},
doubleNode: async ({ input }) => {
return { output: input * 2 }
},
}
// 2. Define the workflow structure as a JSON blueprint
const blueprint = {
id: 'simple-workflow',
nodes: [
{ id: 'start', uses: 'startNode' },
{ id: 'double', uses: 'doubleNode', inputs: 'start' },
],
edges: [{ source: 'start', target: 'double' }],
}
// 3. Run the blueprint with the registry
const runtime = new FlowRuntime({ registry: nodeRegistry })
const result = await runtime.run(blueprint, { value: 42 }, { functionRegistry: nodeRegistry })See the Declarative Workflows Guide for more details.
Compiler — Use @flow / @step annotations to compile imperative code into a blueprint.
/** @step */
export async function startNode(params: { value: number }) {
return { output: params.value }
}
/** @step */
export async function doubleNode(params: { value: number }) {
return { output: params.value * 2 }
}
/** @flow */
export async function simpleWorkflow() {
const start = await startNode({ value: 42 })
const result = await doubleNode({ value: start.output })
return result
}The compiler generates the blueprint and registry at build time. See the Compiler API for more details.
Design robust workflows with built-in resiliency features.
- Retries: Configure the
maxRetriesproperty on a node to automatically retry it on failure. - Fallbacks: Specify a
fallbacknode ID in a node's configuration. If the node fails all its retry attempts, the fallback node will be executed instead, preventing the entire workflow from failing.
For more granular control, you can implement a node using the BaseNode class, which provides prep, exec, post, fallback, and recover lifecycle methods.
Flowcraft includes tools to help you validate, visualize, and integrate workflows with LLMs.
- Linter (
lintBlueprint): Statically analyze a blueprint to find common errors, such as orphan nodes, invalid edges, or nodes with missing implementations. - Analysis (
analyzeBlueprint): Programmatically inspect a blueprint to detect cycles, find start/terminal nodes, and get other graph metrics. - Diagram Generation (
generateMermaid): Automatically generate a Mermaid syntax string from a blueprint to easily visualize your workflow's structure. - Agent Tools: Use
@flowcraft/toolsto give LLMs Zod-based tools for composing, running, and monitoring workflows.
The FlowRuntime can be configured with pluggable components to tailor its behavior to your specific needs:
- Logger: Provide a custom
ILoggerimplementation (e.g., Pino, Winston) to integrate with your existing logging infrastructure. - Serializer: Replace the default
JsonSerializerwith a more robust one (e.g.,superjson) to handle complex data types likeDate,Map, andSetin the workflow context. - Evaluator: Swap the default
PropertyEvaluatorfor a more powerful expression engine (likejseporgovaluate) to enable complex logic in edge conditions. For trusted environments, anUnsafeEvaluatoris also available. - Middleware: Wrap node execution with custom logic for cross-cutting concerns like distributed tracing, performance monitoring, or advanced authorization.
- Event Bus: An event emitter for monitoring workflow and node lifecycle events (
workflow:start,node:finish, etc.).
Flowcraft's architecture is designed for progressive scalability. The BaseDistributedAdapter provides a foundation for running workflows across multiple machines. Flowcraft provides official adapters for BullMQ, AWS, GCP, Azure, RabbitMQ, Kafka, Vercel, and Cloudflare.
For a complete overview of features, patterns, examples, and APIs, see the full documentation.
Flowcraft is licensed under the MIT License.