Basic Usage
Master the basic operations of Pipeline.
Creating Pipeline
Basic Creation
typescript
import { createPipeline } from 'farrow-pipeline'
// Specify input/output types
const pipeline = createPipeline<InputType, OutputType>()
// Example: string to number
const parser = createPipeline<string, number>()
// Example: user validation
interface User {
name: string
age: number
}
const userValidator = createPipeline<unknown, User | null>()Type Inference
typescript
// TypeScript will infer types based on middleware
const pipeline = createPipeline<number, string>()
.use((x, next) => next(x + 1)) // x: number
.use((x) => `Result: ${x}`) // x: number, returns stringAdding Middleware
Method 1: Single Addition
typescript
const pipeline = createPipeline<number, string>()
pipeline.use((input, next) => {
console.log('Middleware 1')
return next(input)
})
pipeline.use((input, next) => {
console.log('Middleware 2')
return next(input)
})
pipeline.use((input) => {
console.log('Last one')
return `Result: ${input}`
})Method 2: Batch Addition
typescript
const middleware1 = (input, next) => next(input + 1)
const middleware2 = (input, next) => next(input * 2)
const middleware3 = (input) => `Value: ${input}`
pipeline.use(middleware1, middleware2, middleware3)Method 3: Chain Calls
typescript
const pipeline = createPipeline<number, string>()
.use((x, next) => {
console.log('Step 1:', x)
return next(x + 1)
})
.use((x, next) => {
console.log('Step 2:', x)
return next(x * 2)
})
.use((x) => {
console.log('Step 3:', x)
return `Completed: ${x}`
})Method 4: Extract Middleware Functions
typescript
// Define reusable middleware
function validateInput(input: number, next) {
if (input < 0) {
throw new Error('Input must be positive')
}
return next(input)
}
function doubleValue(input: number, next) {
return next(input * 2)
}
function formatResult(input: number) {
return `Result: ${input}`
}
// Combine and use
const pipeline = createPipeline<number, string>()
.use(validateInput)
.use(doubleValue)
.use(formatResult)Running Pipeline
Basic Run
typescript
const pipeline = createPipeline<number, string>()
.use((x, next) => next(x * 2))
.use((x) => `Value: ${x}`)
const result = pipeline.run(5)
console.log(result) // "Value: 10"Run with Options
typescript
const pipeline = createPipeline<number, string>()
.use((x, next) => next(x * 2))
// Note: no final middleware
// Provide onLast as default handler
const result = pipeline.run(5, {
onLast: (input) => `Default: ${input}`
})
console.log(result) // "Default: 10"Purpose of onLast
typescript
// Scenario 1: All middleware call next
const pipeline1 = createPipeline<number, string>()
.use((x, next) => next(x + 1))
.use((x, next) => next(x * 2))
// No termination middleware
pipeline1.run(5, {
onLast: (x) => `Final value: ${x}` // Will be called
}) // "Final value: 12"
// Scenario 2: Has termination middleware
const pipeline2 = createPipeline<number, string>()
.use((x, next) => next(x + 1))
.use((x) => `Early end: ${x}`) // Termination middleware
pipeline2.run(5, {
onLast: (x) => `Final value: ${x}` // Will not be called
}) // "Early end: 6"Middleware Order
Execution Order Matters
typescript
// Order A
const pipelineA = createPipeline<number, number>()
.use((x, next) => next(x + 10)) // Add first
.use((x, next) => next(x * 2)) // Multiply second
console.log(pipelineA.run(5, { onLast: x => x })) // (5 + 10) * 2 = 30
// Order B
const pipelineB = createPipeline<number, number>()
.use((x, next) => next(x * 2)) // Multiply first
.use((x, next) => next(x + 10)) // Add second
console.log(pipelineB.run(5, { onLast: x => x })) // (5 * 2) + 10 = 20Pre and Post Processing
typescript
const pipeline = createPipeline<number, number>()
// Middleware 1: Pre/post processing
pipeline.use((input, next) => {
console.log('Pre 1:', input) // 5
const result = next(input + 1) // Pass 6
console.log('Post 1:', result) // 14
return result + 1 // Return 15
})
// Middleware 2: Pre/post processing
pipeline.use((input, next) => {
console.log('Pre 2:', input) // 6
const result = next(input * 2) // Pass 12
console.log('Post 2:', result) // 12
return result + 2 // Return 14
})
// Middleware 3: Termination
pipeline.use((input) => {
console.log('Terminate:', input) // 12
return input // Return 12
})
pipeline.run(5)
// Output:
// Pre 1: 5
// Pre 2: 6
// Terminate: 12
// Post 2: 12
// Post 1: 14
// Final result: 15Conditional Execution
Decide whether to continue based on input
typescript
const pipeline = createPipeline<number, string>()
.use((input, next) => {
if (input < 0) {
return 'Error: Negative number' // Don't call next, return early
}
if (input === 0) {
return 'Zero'
}
return next(input) // Only continue for positive numbers
})
.use((input, next) => {
if (input > 100) {
return 'Error: Too large'
}
return next(input)
})
.use((input) => {
return `Normal value: ${input}`
})
console.log(pipeline.run(-5)) // "Error: Negative number"
console.log(pipeline.run(0)) // "Zero"
console.log(pipeline.run(50)) // "Normal value: 50"
console.log(pipeline.run(200)) // "Error: Too large"Multi-path Branching
typescript
type Action =
| { type: 'add', value: number }
| { type: 'multiply', value: number }
| { type: 'reset' }
const calculator = createPipeline<{ state: number, action: Action }, number>()
.use(({ state, action }, next) => {
switch (action.type) {
case 'add':
return next({ state: state + action.value, action })
case 'multiply':
return next({ state: state * action.value, action })
case 'reset':
return 0 // Return directly, don't continue
default:
return next({ state, action })
}
})
.use(({ state }) => state)
console.log(calculator.run({ state: 10, action: { type: 'add', value: 5 } })) // 15
console.log(calculator.run({ state: 10, action: { type: 'multiply', value: 3 } })) // 30
console.log(calculator.run({ state: 10, action: { type: 'reset' } })) // 0Error Handling
try-catch Wrapper
typescript
const safePipeline = createPipeline<number, string>()
// Global error handling middleware
safePipeline.use((input, next) => {
try {
return next(input)
} catch (error) {
console.error('Pipeline error:', error)
return 'Execution failed'
}
})
// Middleware that might throw exception
safePipeline.use((input, next) => {
if (input < 0) {
throw new Error('Negative numbers not allowed')
}
return next(input * 2)
})
safePipeline.use((input) => {
return `Success: ${input}`
})
console.log(safePipeline.run(5)) // "Success: 10"
console.log(safePipeline.run(-5)) // "Execution failed" (catches exception)Error Passing
typescript
type Result<T> = { ok: true, value: T } | { ok: false, error: string }
const pipeline = createPipeline<number, Result<number>>()
.use((input, next) => {
if (input < 0) {
return { ok: false, error: 'Negative number' }
}
return next(input)
})
.use((input, next) => {
if (input > 100) {
return { ok: false, error: 'Too large' }
}
return next(input)
})
.use((input) => {
return { ok: true, value: input * 2 }
})
const result = pipeline.run(50)
if (result.ok) {
console.log('Result:', result.value)
} else {
console.log('Error:', result.error)
}Practical Examples
Example 1: Data Transformation Pipeline
typescript
interface RawUser {
name: string
age: string
email: string
}
interface User {
name: string
age: number
email: string
isAdult: boolean
}
const userTransformer = createPipeline<RawUser, User>()
.use((input, next) => {
// Convert age
return next({
...input,
age: parseInt(input.age, 10)
} as any)
})
.use((input: any) => {
// Add isAdult field
return {
name: input.name,
age: input.age,
email: input.email,
isAdult: input.age >= 18
}
})
const raw = { name: 'Alice', age: '25', email: 'alice@example.com' }
const user = userTransformer.run(raw)
console.log(user) // { name: 'Alice', age: 25, email: 'alice@example.com', isAdult: true }Example 2: Request Processing Pipeline
typescript
interface Request {
url: string
method: string
body?: any
}
interface Response {
status: number
body: any
}
const requestPipeline = createPipeline<Request, Response>()
// Logging
.use((req, next) => {
console.log(`${req.method} ${req.url}`)
return next(req)
})
// Authentication
.use((req, next) => {
if (!req.headers?.authorization) {
return { status: 401, body: { error: 'Unauthorized' } }
}
return next(req)
})
// Routing
.use((req) => {
if (req.url === '/users') {
return { status: 200, body: { users: [] } }
}
return { status: 404, body: { error: 'Not found' } }
})