Reference
Routecraft cheat sheet.
The whole fluent API on one page. Searchable, copyable, and print-to-PDF ready.
Or press Cmd/Ctrl + P
Setup
Installation
# Core library
bun add @routecraft/routecraft
# AI / MCP integration
bun add @routecraft/ai
# CLI (run routes from the terminal)
bun add -g @routecraft/cli
CLI runs TypeScript directly via Bun (Bun >= 1.1.0). Node 22+ also works.
Concept
Route + Context
import { craft, ContextBuilder } from '@routecraft/routecraft'
const route = craft()
.id('my-route')
.from(source)
.transform(x => x.body)
.to(destination)
.build()
const ctx = await new ContextBuilder()
.routes(route)
.build()
await ctx.start()
await ctx.stop()
DSL
Builder DSL (fluent API)
Chain operations to build a type-safe pipeline. Types flow through each step.
craft()
.id('pipeline') // unique route name
.title('Pipeline') // display name (agents, docs)
.description('...') // doc string used in errors
.tag('idempotent') // route classification
.input({ body: schema }) // input validation
.output({ body: schema }) // output validation
.authorize({ roles: ['admin'] }) // route guard (auth)
.from(source) // source adapter
.authenticate(resolver) // mint trusted principal
.transform(body => body) // pure body function
.process(ex => ex) // full exchange access
.filter(ex => ex.body.age > 18) // drop if false
.validate(schema(zodSchema)) // any Standard Schema
.header('key', 'val') // set a header
.enrich(other) // fetch and merge
.tap(log()) // side effect, non-blocking
.split() // fan-out per array item
.aggregate() // collect back into one
.choice(c => c.when(...)) // conditional branching
.to(destination) // destination adapter
.error((e, ex, fwd) => ...) // step-scope handler
.build()
Inputs
Sources
// Static value or async function
.from(simple({ hello: 'world' }))
.from(simple(() => fetch('/api')))
// Emit on an interval
.from(timer({ intervalMs: 5000 }))
// Cron schedule
.from(cron('0 9 * * *'))
.from(cron('0 9 * * *',
{ timezone: 'America/New_York' }))
// In-process direct endpoint
.from(direct('my-endpoint'))
// IMAP mail (push via IDLE)
.from(mail('INBOX',
{ unseen: true, markSeen: true }))
// In-process EventEmitter
.from(event({ eventName: 'order' }))
// File and file-format sources
.from(file({ path: './data.txt' }))
.from(json({ file: './data.json' }))
.from(jsonl({ file: './events.jsonl' }))
.from(csv({ file: './rows.csv' }))
.from(html({ html: '<table>...</table>' }))
Outputs
Destinations
// Log to console
.to(log())
.to(debug(ex => ex.body))
// Discard the exchange
.to(noop())
// HTTP request
.to(http({
method: 'POST',
url: 'https://example.com',
body: ex => ex.body,
}))
// Dynamic URL
.to(http({ url: ex => `/${ex.body.id}` }))
// In-process direct endpoint
.to(direct('my-endpoint'))
// Write file or file format
.to(file({ path: './out.txt' }))
.to(json({ file: './out.json' }))
.to(jsonl({ file: './out.jsonl' }))
.to(csv({ file: './out.csv' }))
// Send email via SMTP
.to(mail({
to: ex => ex.body.email,
subject: 'Hello',
text: ex => ex.body.text,
}))
Envelope
Exchanges
type Exchange<T> = {
id: string
body: T
headers: ExchangeHeaders
logger: Logger
}
Access patterns
// .transform() gets the body
.transform(body => body.toUpperCase())
// .process() gets the full exchange
.process(ex => ({
...ex,
body: { ...ex.body, ts: Date.now() },
}))
// .filter() gets the full exchange
.filter(ex => ex.body.age > 18)
Flow
Split & aggregate
Fan out an array, process each item, collect back.
craft()
.from(simple([1, 2, 3]))
.split() // 3 exchanges
.transform(n => n * 2) // each runs once
.aggregate() // [2, 4, 6]
.to(log())
Flow
Choice & branching
First matching when wins. halt() short-circuits the branch.
craft()
.from(source)
.choice(c => c
.when(ex => ex.body.priority === 'urgent',
b => b.to(urgentQueue))
.when(ex => ex.body.amount > 1000,
b => b.to(reviewQueue))
.otherwise(
b => b.to(errorSink).halt()))
.to(defaultDestination)
Lookup
Enrich (fetch & merge)
// Default: deep merge
.enrich(http({ url: '/api/user' }))
// Custom merge strategy
.enrich(dest, (orig, fetched) => ({
...orig.body,
meta: fetched.body,
}))
// Merge helpers
.enrich(dest, only('meta'))
.enrich(dest, replace())
Safety
Validation (Standard Schema)
Works with Zod, Valibot, ArkType, or any Standard Schema library.
import { z } from 'zod'
import { schema } from '@routecraft/routecraft'
craft()
.from(direct('input'))
.validate(schema(z.object({
email: z.string().email(),
age: z.number().min(18),
})))
.to(log())
// body type is inferred from the schema
Observability
Events system
// Context lifecycle
ctx.on('context:started', () => {})
ctx.on('context:error', ({ details }) => {
// { error, route?, exchange? }
})
// Route lifecycle (route id required)
ctx.on('route:my-route:started', () => {})
// Exchange tracking (use * for all routes)
ctx.on('route:*:exchange:completed',
({ details }) => {
// { exchange, duration }
})
// Step-level tracing
ctx.on('route:*:step:completed',
({ details }) => {
// { operation, adapter, duration }
})
// Wildcards
ctx.on('route:*:exchange:*', () => {})
ctx.on('plugin:*:started', () => {})
Recovery
Error handling
craft()
.error((error, exchange, forward) => {
// Return a recovery value
return { recovered: true }
// Or forward to a dead-letter route
return forward('dlq', {
source: error.message,
})
})
.from(source)
.to(destination)
Error code ranges
RC1xxx Definition
RC2xxx DSL
RC3xxx Lifecycle
RC5xxx Adapter (incl. auth)
RC9xxx Testing
Runtime
Context & plugins
const ctx = await new ContextBuilder()
.add({
crm: { timezone: 'UTC' },
direct: { channelType: 'memory' },
mail: { accounts: { /* ... */ } },
plugins: [myPlugin],
})
.on('context:started', () => {})
.store('custom-key', value)
.routes(route1, route2)
.build()
const myPlugin: CraftPlugin = {
async apply(ctx) {
ctx.store('key', new Map())
},
async teardown(ctx) { /* cleanup */ },
}
AI
LLM destination
Model id is provider:model. Providers (Anthropic, OpenAI, Ollama, Gemini, OpenRouter) are registered via llmPlugin.
import { llm } from '@routecraft/ai'
// Basic call (user prompt defaults to body)
craft()
.from(direct('text-in'))
.to(llm('anthropic:claude-sonnet-4-6', {
system: 'Summarize concisely',
user: ex => ex.body.text,
temperature: 0.2,
}))
.to(log())
// Structured output (body.output is typed)
.to(llm('openai:gpt-4o', {
system: 'Extract contact info',
output: z.object({
email: z.string().email(),
name: z.string(),
}),
}))
AI
Agents & tools
agent() runs a multi-turn tool-calling loop. tools() selects from MCP tools, registered functions, and direct routes.
import { agent, tools } from '@routecraft/ai'
craft()
.id('assistant')
.from(direct('chat-in'))
.to(agent({
model: 'anthropic:claude-sonnet-4-6',
system: 'You are a helpful assistant.',
user: ex => ex.body.message,
tools: tools([
'CurrentTime', // registered fn
'Direct(greet-user)', // direct route as tool
'MCP(github:create_issue)', // single MCP tool
{ tagged: 'read-only' }, // by tag
]),
}))
.to(log())
Register functions and direct-route tools once via agentPlugin({ functions: { CurrentTime: currentTime(), greetUser: directTool('greet-user') } }).
AI
MCP integration
Expose a route as an MCP tool
import { mcp } from '@routecraft/ai'
craft()
.id('fetch-page')
.title('Fetch page')
.description('Fetch webpage content')
.tag('read-only')
.input({ body: z.object({
url: z.string().url(),
}) })
.from(mcp())
.enrich(http({ url: ex => ex.body.url }))
.to(log())
Call an MCP server
// As a destination (invoke a remote tool)
.to(mcp('github', 'search'))
Protect with auth
import { jwt } from '@routecraft/routecraft'
craft()
.id('private-tool')
.authorize({ scopes: ['tools:read'] })
.from(mcp({
auth: jwt({ jwksUri: '...' }),
}))
.to(log())
// Use WorkOS / Clerk presets via mcpPlugin
Claude Desktop config
{
"mcpServers": {
"routecraft": {
"command": "bunx",
"args": ["@routecraft/cli", "run",
"./capabilities/index.ts"]
}
}
}
Terminal
CLI & TUI
Run routes
# Run a route file
craft run ./my-route.ts
# With debug logging
craft run ./my-route.ts \
--log-level debug \
--log-file ./craft.log
Inspect with the TUI
Live event and exchange inspector. Requires the telemetry plugin enabled on the context.
# Launch the TUI (reads the telemetry DB)
craft tui
# Or point it at a specific DB
craft tui --db ./app/telemetry.db