Installation
Install stepkit using your preferred package manager:
Your First Pipeline
Let’s build a simple pipeline that fetches user data and processes it. Create a new file and add the following code:
import { stepkit } from 'stepkit'
const pipeline = stepkit<{ userId: string }>()
.step('fetch-user', ({ userId }) => {
return { userName: 'John Doe', email: '[email protected]' }
})
.step('fetch-settings', ({ userName }) => {
return { theme: 'dark', language: 'en' }
})
const result = await pipeline.run({ userId: '123' })
console.log(result)
// Output: { userId: '123', userName: 'John Doe', email: '[email protected]', theme: 'dark', language: 'en' }
Import stepkit
Start by importing the stepkit function from the package.import { stepkit } from 'stepkit'
Define your input type
Create a pipeline with a typed input. The input type flows through all steps.const pipeline = stepkit<{ userId: string }>()
Add steps
Each step receives the current context and returns an object. The returned object is merged into the context for subsequent steps..step('fetch-user', ({ userId }) => {
return { userName: 'John Doe', email: '[email protected]' }
})
.step('fetch-settings', ({ userName }) => {
return { theme: 'dark', language: 'en' }
})
Notice how the second step can access userName from the first step’s output. TypeScript automatically infers all available fields.
Run the pipeline
Execute the pipeline with your initial input:const result = await pipeline.run({ userId: '123' })
Understanding Context Flow
stepkit uses a context merging pattern. Each step:
- Receives the current context (input + all previous step outputs)
- Returns a plain object with new fields
- The output is merged into the context for the next step
This creates a cumulative context that grows as the pipeline executes:
const pipeline = stepkit<{ value: number }>()
.step('double', ({ value }) => ({ doubled: value * 2 }))
// Context is now: { value, doubled }
.step('add-ten', ({ doubled }) => ({ result: doubled + 10 }))
// Context is now: { value, doubled, result }
await pipeline.run({ value: 5 })
// Final result: { value: 5, doubled: 10, result: 20 }
Working with Async Operations
Step functions can be async. This is common when fetching data from APIs, databases, or other external sources:
import { stepkit } from 'stepkit'
const pipeline = stepkit<{ userId: string }>()
.step('fetch-user', async ({ userId }) => {
const response = await fetch(`https://api.example.com/users/${userId}`)
const user = await response.json()
return { userName: user.name, email: user.email }
})
.step('fetch-orders', async ({ userId }) => {
const response = await fetch(`https://api.example.com/orders?user=${userId}`)
const orders = await response.json()
return { orderCount: orders.length }
})
await pipeline.run({ userId: '123' })
Parallel Execution
Run multiple async operations concurrently by passing multiple functions to a single step:
import { stepkit } from 'stepkit'
const pipeline = stepkit<{ userId: string }>()
.step(
'fetch-data',
async ({ userId }) => {
const user = await fetchUser(userId)
return { user }
},
async ({ userId }) => {
const orders = await fetchOrders(userId)
return { orders }
},
async ({ userId }) => {
const settings = await fetchSettings(userId)
return { settings }
}
)
// All three functions run in parallel
.step('process', ({ user, orders, settings }) => {
return { summary: `${user.name} has ${orders.length} orders` }
})
await pipeline.run({ userId: '123' })
Parallel functions in the same step execute concurrently using Promise.all(). Their results are merged into the context before the next step runs.
Enable Logging
See what’s happening inside your pipeline with built-in structured logging:
const pipeline = stepkit<{ userId: string }>()
.step('fetch-user', async ({ userId }) => {
// Simulate delay
await new Promise(resolve => setTimeout(resolve, 100))
return { userName: 'John' }
})
.step('process', ({ userName }) => {
return { greeting: `Hello, ${userName}!` }
})
await pipeline.run(
{ userId: '123' },
{ log: { stopwatch: true } }
)
Output:
🚀 Starting pipeline with input: { userId: "123" }
📍 Step: fetch-user
✅ fetch-user completed in 102ms
Output: userName
📍 Step: process
✅ process completed in 0ms
Output: greeting
⏱️ Performance Summary:
┌──────────────────────────────────────────────────────┐
│ ✅ fetch-user 102ms │
│ ✅ process 0ms │
└──────────────────────────────────────────────────────┘
📊 Statistics:
Average: 51ms
Slowest: fetch-user (102ms)
Fastest: process (0ms)
⏰ Total Pipeline Time: 103ms
✨ Pipeline completed successfully
Error Handling
Control how errors propagate through your pipeline:
import { stepkit } from 'stepkit'
const pipeline = stepkit<{ userId: string }>()
.step(
{
name: 'fetch-optional-data',
onError: 'continue' // Don't throw, continue to next step
},
async ({ userId }) => {
const data = await fetchExternalAPI(userId) // Might fail
return { externalData: data }
}
)
.step('process', ({ userId, externalData }) => {
// externalData might be undefined if the previous step failed
return {
message: externalData
? `Got data for ${userId}`
: `No external data for ${userId}`
}
})
await pipeline.run({ userId: '123' })
When using onError: 'continue', TypeScript automatically marks the step’s outputs as optional in subsequent steps.
What’s Next?
You’ve learned the basics of stepkit. Here’s what to explore next:
Core Concepts
Deep dive into context merging, type safety, and step configuration
Examples
See real-world patterns including branching, transforms, and checkpoints
API Reference
Complete reference for all methods and configuration options
Type Helpers
Learn how to extract types from your pipelines