farrow-pipeline Complete API Reference
Table of Contents
- Core Concepts
- Complete Export List
- Pipeline Type System
- Context System
- Container System
- Async Tracing (AsyncTracer)
- Utility Functions
- Type Utilities
- FAQ
Core Concepts
Design Philosophy
farrow-pipeline is a type-safe middleware pipeline system with the following core design principles:
- Functional Composition - Middleware forms processing chains through functional composition
- Type Safety - Full TypeScript type inference throughout
- Context Isolation - Each run has an independent context container
- Async-Friendly - Async context passing based on AsyncLocalStorage
- Composability - Pipelines can be nested as middleware
Execution Model
Input → Middleware1 → Middleware2 → ... → MiddlewareN → Output
↓ ↓ ↓
next() next() returnOnion Model:
// Execution order example
pipeline.use((input, next) => {
console.log('1: Enter') // 1
const result = next(input)
console.log('4: Exit') // 4
return result
})
pipeline.use((input, next) => {
console.log('2: Enter') // 2
const result = next(input)
console.log('3: Exit') // 3
return result
})
// Output order: 1 → 2 → 3 → 4Complete Export List
Main Entry (farrow-pipeline)
// Pipeline creation functions
export { createPipeline, createAsyncPipeline }
// Context system
export { createContext, createContainer }
// Container utilities
export { useContainer, runWithContainer }
// Type assertions
export { assertContainer, assertContext }
// Type guards
export { isContext, isContainer, isPipeline }
// Utility functions
export { usePipeline, getMiddleware }
// Type exports
export type {
// Pipeline types
Pipeline,
AsyncPipeline,
PipelineOptions,
RunPipelineOptions,
PipelineInput,
PipelineOutput,
// Middleware types
Middleware,
Middlewares,
MiddlewareInput,
MiddlewareType,
ThunkMiddlewareInput,
// Context types
Context,
ContextStorage,
Container,
// Utility types
Next,
MaybeAsync,
}AsyncTracer Entry (farrow-pipeline/asyncTracerImpl.node)
// Node.js environment
export { enable, disable }AsyncTracer Entry (farrow-pipeline/asyncTracerImpl.browser)
// Browser environment - throws error (not supported)
throw new Error(`No Implementation`)Pipeline Type System
createPipeline - Create Synchronous Pipeline
Function Signature:
function createPipeline<I, O>(options?: PipelineOptions): Pipeline<I, O>Type Parameters:
I- Input typeO- Output type
Parameters:
type PipelineOptions = {
contexts?: ContextStorage // Preset context storage
}
type ContextStorage = {
[key: string]: Context<any>
}Return Value:
type Pipeline<I = unknown, O = unknown> = {
[PipelineSymbol]: true
// Add middleware, supports method chaining
use: (...inputs: MiddlewareInput<I, O>[]) => Pipeline<I, O>
// Run pipeline
run: (input: I, options?: RunPipelineOptions<I, O>) => O
// Use as middleware
middleware: Middleware<I, O>
}
type RunPipelineOptions<I = unknown, O = unknown> = {
container?: Container // Specify container
onLast?: (input: I) => O // Callback for last middleware
}Basic Example:
import { createPipeline } from 'farrow-pipeline'
// Create simple number processing pipeline
const pipeline = createPipeline<number, string>()
pipeline.use((input, next) => {
console.log('Input:', input)
return next(input * 2)
})
pipeline.use((input) => {
return `Result: ${input}`
})
const result = pipeline.run(5) // "Result: 10"Preset Context Example:
import { createContext, createPipeline } from 'farrow-pipeline'
// Create context
const UserContext = createContext({ name: 'Guest' })
// Create pipeline with preset context
const pipeline = createPipeline<string, string>({
contexts: {
user: UserContext.create({ name: 'Admin' })
}
})
pipeline.use((input, next) => {
const user = UserContext.get() // { name: 'Admin' }
return next(`${user.name}: ${input}`)
})
const result = pipeline.run('Hello') // "Admin: Hello"Method Chaining Example:
const pipeline = createPipeline<number, number>()
.use((x, next) => next(x + 1))
.use((x, next) => next(x * 2))
.use((x) => x - 3)
const result = pipeline.run(5) // (5 + 1) * 2 - 3 = 9createAsyncPipeline - Create Asynchronous Pipeline
Function Signature:
function createAsyncPipeline<I, O>(options?: PipelineOptions): AsyncPipeline<I, O>Return Value:
type AsyncPipeline<I = unknown, O = unknown> = Pipeline<I, MaybeAsync<O>> & {
// Lazy-load middleware
useLazy: (thunk: ThunkMiddlewareInput<I, O>) => AsyncPipeline<I, O>
}
type MaybeAsync<T> = T | Promise<T>
type ThunkMiddlewareInput<I, O> = () => MaybeAsync<MiddlewareInput<I, MaybeAsync<O>>>Description:
- Supports async middleware (returns Promise)
- Supports lazy-loading middleware (load on demand)
- Automatically handles Promise chains
Async Middleware Example:
const pipeline = createAsyncPipeline<string, User>()
// Async database query
pipeline.use(async (userId, next) => {
const user = await db.findUser(userId)
if (!user) throw new Error('User not found')
return next(user)
})
// Async logging
pipeline.use(async (user, next) => {
await logger.log(`User ${user.id} accessed`)
return next(user)
})
// Final processing
pipeline.use((user) => {
return user
})
// Run (returns Promise)
const user = await pipeline.run('user123')Lazy-Loading Middleware Example:
const pipeline = createAsyncPipeline<Request, Response>()
// Lazy-load heavy dependency
pipeline.useLazy(async () => {
// Only load when first needed
const { imageProcessor } = await import('./heavy-image-lib')
return (req, next) => {
if (req.url.startsWith('/image')) {
const processed = imageProcessor(req)
return next(processed)
}
return next(req)
}
})
// Conditional lazy-loading
pipeline.useLazy(() => {
if (process.env.NODE_ENV === 'development') {
return async (req, next) => {
console.log('Dev mode:', req)
return next(req)
}
}
// Skip in production
return (req, next) => next(req)
})Lazy-Loading Mechanism:
- First Call: Execute thunk function to get middleware
- Caching: Cache loaded middleware to variable
- Subsequent Calls: Directly use cached middleware
- Promise Handling: Automatically handle sync and async loading
// Internal implementation overview
useLazy: (thunk) => {
let middleware: Middleware | null = null
let promise: Promise<void> | null = null
pipeline.use((input, next) => {
// Already loaded, use directly
if (middleware) return next(input)
// First load
if (!promise) {
promise = Promise.resolve(thunk()).then((result) => {
middleware = getMiddleware(result)
})
}
// Wait for loading to complete
return promise.then(() => next(input))
})
// Call loaded middleware
pipeline.use((input, next) => {
if (!middleware) throw new Error(`Failed to load middleware`)
return middleware(input, next)
})
return asyncPipeline
}usePipeline - Run Pipeline with Inherited Container
Function Signature:
function usePipeline<I, O>(pipeline: Pipeline<I, O>):
(input: I, options?: RunPipelineOptions<I, O>) => ODescription:
- Run another Pipeline within middleware
- Automatically inherits current container, maintaining context passing
- Avoid context loss from directly calling
pipeline.run()
Why usePipeline is Needed?
// ❌ Wrong: Direct run creates new container, context lost
pipeline.use((input, next) => {
const result = subPipeline.run(input) // New container
return next(result)
})
// ✅ Correct: Inherit current container
pipeline.use((input, next) => {
const runSubPipeline = usePipeline(subPipeline)
const result = runSubPipeline(input) // Inherited container
return next(result)
})Complete Example:
import { createContext, createPipeline, usePipeline } from 'farrow-pipeline'
// Create context
const UserContext = createContext<User | null>(null)
// Auth pipeline
const authPipeline = createPipeline<Request, User>()
authPipeline.use((req) => {
const user = authenticate(req)
UserContext.set(user) // Set context
return user
})
// Business pipeline
const businessPipeline = createPipeline<User, Response>()
businessPipeline.use((user) => {
const currentUser = UserContext.get() // Get context
return { status: 200, user: currentUser }
})
// Main pipeline
const mainPipeline = createPipeline<Request, Response>()
mainPipeline.use((req, next) => {
// Use usePipeline to maintain context
const runAuth = usePipeline(authPipeline)
const runBusiness = usePipeline(businessPipeline)
const user = runAuth(req) // Inherited container
const response = runBusiness(user) // Inherited container
return response // Context correctly passed
})
// Run
const response = mainPipeline.run(request)Error Handling Composition:
mainPipeline.use((input, next) => {
const runValidation = usePipeline(validationPipeline)
const runProcessing = usePipeline(processingPipeline)
try {
const validated = runValidation(input)
const result = runProcessing(validated)
return { status: 200, result }
} catch (error) {
return { status: 400, error: error.message }
}
})Middleware Types
Middleware - Middleware Function Type
Type Definition:
type Middleware<I = unknown, O = unknown> = (input: I, next: Next<I, O>) => O
type Next<I = unknown, O = unknown> = (input?: I) => ODescription:
input- Current middleware inputnext- Function to call next middleware- Return value - Middleware output, must match Pipeline output type
Middleware Writing Patterns:
// 1. Transform middleware - Modify input passed to next
pipeline.use((input, next) => {
const transformed = transform(input)
return next(transformed)
})
// 2. Enhance middleware - Process result
pipeline.use((input, next) => {
const result = next(input)
return enhance(result)
})
// 3. Intercept middleware - Conditional execution
pipeline.use((input, next) => {
if (!validate(input)) {
return errorResponse
}
return next(input)
})
// 4. Wrap middleware - Before and after processing
pipeline.use((input, next) => {
before(input)
const result = next(input)
after(result)
return result
})
// 5. Terminal middleware - Don't call next
pipeline.use((input) => {
return finalResult
})MiddlewareInput - Middleware Input Type
Type Definition:
type MiddlewareInput<I = unknown, O = unknown> =
| Middleware<I, O>
| { middleware: Middleware<I, O> }Description:
- Supports directly passing middleware function
- Supports passing object with
middlewareproperty
Usage Example:
// Method 1: Direct function
pipeline.use((input, next) => next(input))
// Method 2: Pass object
const middlewareObj = {
middleware: (input, next) => next(input)
}
pipeline.use(middlewareObj)
// Method 3: Pass Pipeline (Pipeline implements middleware property)
const subPipeline = createPipeline()
pipeline.use(subPipeline) // Equivalent to pipeline.use(subPipeline.middleware)getMiddleware - Extract Middleware Function
Function Signature:
function getMiddleware<I, O>(input: MiddlewareInput<I, O>): Middleware<I, O>Description:
- Extract actual middleware function from
MiddlewareInput - Handle both function and object forms
Usage Example:
const fn = (input, next) => next(input)
const obj = { middleware: fn }
getMiddleware(fn) // Returns fn
getMiddleware(obj) // Returns obj.middlewarePipeline Method Details
pipeline.use - Add Middleware
Method Signature:
use(...inputs: MiddlewareInput<I, O>[]): Pipeline<I, O>Description:
- Accepts any number of middleware
- Supports method chaining
- Middleware executes in order added
Example:
// Single middleware
pipeline.use((input, next) => next(input))
// Multiple middleware
pipeline.use(
middleware1,
middleware2,
middleware3
)
// Method chaining
pipeline
.use(middleware1)
.use(middleware2)
.use(middleware3)
// Nested Pipeline
const subPipeline = createPipeline()
pipeline.use(subPipeline)
// Conditional middleware
if (enableAuth) {
pipeline.use(authMiddleware)
}pipeline.run - Run Pipeline
Method Signature:
run(input: I, options?: RunPipelineOptions<I, O>): OParameters:
type RunPipelineOptions<I = unknown, O = unknown> = {
container?: Container // Specify run container
onLast?: (input: I) => O // Last middleware callback
}Description:
input- Pipeline inputoptions.container- Specify container (defaults to Pipeline creation container)options.onLast- Callback executed when all middleware callnext()
Basic Example:
const pipeline = createPipeline<number, number>()
.use((x, next) => next(x + 1))
.use((x, next) => next(x * 2))
// Basic run
const result = pipeline.run(5) // (5 + 1) * 2 = 12Using onLast Example:
const pipeline = createPipeline<string, string>()
.use((input, next) => {
console.log('Middleware executing')
return next(input)
})
// All middleware called next, triggers onLast
const result = pipeline.run('test', {
onLast: (input) => {
console.log('Reached end:', input)
return `Default: ${input}`
}
})
// Output:
// Middleware executing
// Reached end: test
// Returns: "Default: test"Specify Container Example:
const UserContext = createContext<User | null>(null)
const pipeline = createPipeline<string, string>()
.use((input) => {
const user = UserContext.get()
return `${user?.name}: ${input}`
})
// Create custom container
const container = createContainer({
user: UserContext.create({ name: 'Alice' })
})
// Run with custom container
const result = pipeline.run('Hello', { container })
// "Alice: Hello"pipeline.middleware - Use as Middleware
Property Signature:
middleware: Middleware<I, O>Description:
- Convert current Pipeline to middleware function
- Can nest into other Pipelines
- Automatically inherits parent Pipeline container
Example:
const subPipeline = createPipeline<number, number>()
.use((x, next) => next(x + 1))
.use((x, next) => next(x * 2))
const mainPipeline = createPipeline<number, string>()
.use(subPipeline.middleware) // Nest subPipeline
.use((x) => `Result: ${x}`)
const result = mainPipeline.run(5) // "Result: 12"Equivalent Approaches:
// Approach 1: Use .middleware property
mainPipeline.use(subPipeline.middleware)
// Approach 2: Pass Pipeline directly (internally calls .middleware)
mainPipeline.use(subPipeline)
// Approach 3: Use usePipeline (recommended, clear intent)
mainPipeline.use((input, next) => {
const runSubPipeline = usePipeline(subPipeline)
const result = runSubPipeline(input)
return next(result)
})Pipeline Guard Functions
isPipeline - Check if Pipeline
Function Signature:
function isPipeline(obj: any): obj is PipelineDescription:
- Type guard function
- Check if object has Pipeline identifier
Usage Example:
import { isPipeline, createPipeline } from 'farrow-pipeline'
const pipeline = createPipeline()
const fn = () => {}
if (isPipeline(pipeline)) {
// TypeScript knows pipeline is Pipeline type
pipeline.run(input)
}
// Dynamic handling
function handle(handler: Pipeline | Function, input: any) {
if (isPipeline(handler)) {
return handler.run(input)
} else {
return handler(input)
}
}Type Extraction Utilities
PipelineInput - Extract Input Type
Type Definition:
type PipelineInput<T extends Pipeline> = T extends Pipeline<infer I> ? I : neverUsage Example:
const pipeline = createPipeline<number, string>()
type Input = PipelineInput<typeof pipeline> // numberPipelineOutput - Extract Output Type
Type Definition:
type PipelineOutput<T extends Pipeline> = T extends Pipeline<any, infer O> ? O : neverUsage Example:
const pipeline = createPipeline<number, string>()
type Output = PipelineOutput<typeof pipeline> // stringContext System
createContext - Create Context
Function Signature:
function createContext<T>(defaultValue: T): Context<T>Type Definition:
type Context<T = any> = {
id: symbol // Context unique identifier
[ContextSymbol]: T // Default value (internal use)
create: (value: T) => Context<T> // Create new instance
get: () => T // Get current value
set: (value: T) => void // Set current value
assert: () => Exclude<T, undefined | null> // Assert non-null
}Description:
- Create type-safe context object
- Each Context has unique
id - Provides CRUD operation methods
Basic Example:
import { createContext, createPipeline } from 'farrow-pipeline'
// Create context
const UserContext = createContext<{ name: string } | null>(null)
const pipeline = createPipeline<string, string>()
// Set context
pipeline.use((input, next) => {
UserContext.set({ name: 'Alice' })
return next(input)
})
// Read context
pipeline.use((input) => {
const user = UserContext.get()
return `${user?.name}: ${input}`
})
const result = pipeline.run('Hello') // "Alice: Hello"Context Method Details:
get() - Get Context Value
get(): TDescription:
- Get context value from current container
- Returns default value if not in container
Example:
const CountContext = createContext(0)
pipeline.use((input, next) => {
const count = CountContext.get() // 0 (default value)
CountContext.set(count + 1)
return next(input)
})
pipeline.use((input) => {
const count = CountContext.get() // 1
return `Count: ${count}`
})set(value) - Set Context Value
set(value: T): voidDescription:
- Set context value in current container
- Overwrites previous value
Example:
const UserContext = createContext<User | null>(null)
pipeline.use((input, next) => {
const user = authenticate(input)
UserContext.set(user) // Set user
return next(input)
})assert() - Assert Non-Null
assert(): Exclude<T, undefined | null>Description:
- Assert context value is not null
- Throws error if
nullorundefined - Returns non-null type
Example:
const UserContext = createContext<User | null>(null)
pipeline.use((input) => {
try {
const user = UserContext.assert() // Assert non-null
// user type is User (not including null)
return user.id
} catch (error) {
return 'Unauthorized'
}
})create(value) - Create New Instance
create(value: T): Context<T>Description:
- Create a new Context instance
- New instance has same
id, but different default value - Used to create preset containers
Example:
const ConfigContext = createContext({ debug: false })
// Create dev environment config
const devConfig = ConfigContext.create({ debug: true })
// Create prod environment config
const prodConfig = ConfigContext.create({ debug: false })
// Create different environment containers
const devContainer = createContainer({
config: devConfig
})
const prodContainer = createContainer({
config: prodConfig
})Context Isolation Example:
const CounterContext = createContext(0)
const pipeline = createPipeline<string, string>()
pipeline.use((input, next) => {
const count = CounterContext.get()
CounterContext.set(count + 1)
return next(`${input}:${CounterContext.get()}`)
})
// Concurrent execution, each has independent counter
const results = await Promise.all([
pipeline.run('A'), // "A:1"
pipeline.run('B'), // "B:1"
pipeline.run('C') // "C:1"
])
// Each run starts from default value 0Multiple Context Example:
const UserContext = createContext<User | null>(null)
const RequestIdContext = createContext('')
const LoggerContext = createContext<Logger>(consoleLogger)
const pipeline = createPipeline<Request, Response>()
// Set multiple contexts
pipeline.use((req, next) => {
UserContext.set(authenticate(req))
RequestIdContext.set(generateId())
LoggerContext.set(createLogger())
return next(req)
})
// Use multiple contexts
pipeline.use((req) => {
const user = UserContext.get()
const requestId = RequestIdContext.get()
const logger = LoggerContext.get()
logger.log(`User ${user?.id} - Request ${requestId}`)
return { user, requestId }
})Context Guard Functions
isContext - Check if Context
Function Signature:
function isContext(input: any): input is ContextUsage Example:
import { isContext, createContext } from 'farrow-pipeline'
const ctx = createContext(0)
if (isContext(ctx)) {
// TypeScript knows ctx is Context type
ctx.get()
}assertContext - Assert is Context
Function Signature:
function assertContext(input: any): asserts input is ContextDescription:
- TypeScript assertion function
- Throws error if not Context
Usage Example:
function useContextValue(ctx: any) {
assertContext(ctx)
// After this, TypeScript knows ctx is Context type
return ctx.get()
}Container System
createContainer - Create Container
Function Signature:
function createContainer(contextStorage?: ContextStorage): ContainerType Definition:
type Container = {
[ContainerSymbol]: true
read: <V>(context: Context<V>) => V
write: <V>(context: Context<V>, value: V) => void
}
type ContextStorage = {
[key: string]: Context<any>
}Description:
- Container is storage for Context
- Each
pipeline.run()creates independent container - Contexts within container don't interfere
Basic Example:
import { createContext, createContainer } from 'farrow-pipeline'
const UserContext = createContext<User | null>(null)
// Create custom container
const container = createContainer({
user: UserContext.create({ id: 1, name: 'Alice' })
})
// Read from container
const user = container.read(UserContext) // { id: 1, name: 'Alice' }
// Write to container
container.write(UserContext, { id: 2, name: 'Bob' })Testing Scenario Example:
const DatabaseContext = createContext<Database>(productionDB)
const LoggerContext = createContext<Logger>(consoleLogger)
// Create test container
const testContainer = createContainer({
db: DatabaseContext.create(mockDatabase),
logger: LoggerContext.create(silentLogger)
})
// Use in tests
const result = pipeline.run(testInput, { container: testContainer })Multi-Environment Config Example:
const ConfigContext = createContext({ env: 'development' })
const environments = {
development: createContainer({
config: ConfigContext.create({ env: 'development', debug: true })
}),
production: createContainer({
config: ConfigContext.create({ env: 'production', debug: false })
}),
test: createContainer({
config: ConfigContext.create({ env: 'test', debug: false })
})
}
const currentContainer = environments[process.env.NODE_ENV || 'development']
const result = pipeline.run(input, { container: currentContainer })Container Method Details
container.read - Read Context
Method Signature:
read<V>(context: Context<V>): VDescription:
- Read specified context value from container
- Returns Context default value if not in container
Example:
const UserContext = createContext<User | null>(null)
const container = createContainer({
user: UserContext.create({ id: 1, name: 'Alice' })
})
const user = container.read(UserContext) // { id: 1, name: 'Alice' }container.write - Write Context
Method Signature:
write<V>(context: Context<V>, value: V): voidDescription:
- Write specified context value to container
- Overwrites previous value
Example:
const CountContext = createContext(0)
const container = createContainer()
container.write(CountContext, 10)
const count = container.read(CountContext) // 10Container Utility Functions
useContainer - Get Current Container
Function Signature:
function useContainer(): ContainerDescription:
- Get container in current async tracing context
- Must be called when Pipeline is running
- Used to access container in middleware
Usage Example:
import { useContainer } from 'farrow-pipeline'
pipeline.use((input, next) => {
const container = useContainer()
// Directly operate container
const user = container.read(UserContext)
container.write(LogContext, logger)
return next(input)
})runWithContainer - Run Function in Specified Container
Function Signature:
function runWithContainer<F extends (...args: any) => any>(
f: F,
container: Container
): ReturnType<F>Description:
- Run function in specified container context
- Function can use
useContainer()to get that container - Used to manually control container scope
Usage Example:
import { runWithContainer } from 'farrow-pipeline'
const UserContext = createContext<User | null>(null)
const container = createContainer({
user: UserContext.create({ id: 1, name: 'Alice' })
})
// Run function in container
const result = runWithContainer(() => {
const user = UserContext.get() // { id: 1, name: 'Alice' }
return `User: ${user?.name}`
}, container)isContainer - Check if Container
Function Signature:
function isContainer(input: any): input is ContainerUsage Example:
import { isContainer, createContainer } from 'farrow-pipeline'
const container = createContainer()
if (isContainer(container)) {
// TypeScript knows container is Container type
container.read(UserContext)
}assertContainer - Assert is Container
Function Signature:
function assertContainer(input: any): asserts input is ContainerUsage Example:
function useContainerValue(container: any, context: Context) {
assertContainer(container)
// After this, TypeScript knows container is Container type
return container.read(context)
}Async Tracing (AsyncTracer)
Overview
AsyncTracer is the core mechanism of farrow-pipeline for maintaining context passing during async operations.
Core Functions:
- Async Context Passing - Maintain context in async operations like async/await, Promise, setTimeout
- Request-Level Isolation - Each request/run has independent container, avoid concurrent pollution
- Automatic Propagation - No manual container passing, framework manages automatically
Implementation Principle:
- Node.js: Based on
AsyncLocalStorage(async_hooks) - Browser: Not supported (throws error)
Node.js Environment (farrow-pipeline/asyncTracerImpl.node)
enable - Enable Async Tracing
Function Signature:
function enable(): voidDescription:
- Enable async context tracing in Node.js environment
- farrow-http calls automatically, no manual enabling needed
- Pure farrow-pipeline environment needs manual call
Usage Example:
import * as asyncTracerImpl from 'farrow-pipeline/asyncTracerImpl.node'
// Call at app startup
asyncTracerImpl.enable()
// Now can use Context in async operations
const pipeline = createPipeline()
pipeline.use(async (input, next) => {
UserContext.set(user)
await delay(1000) // Async wait
const user = UserContext.get() // ✅ Correctly retrieved
return next(input)
})disable - Disable Async Tracing
Function Signature:
function disable(): voidDescription:
- Disable async tracing
- Used for resource cleanup
Usage Example:
// When app shuts down
process.on('exit', () => {
asyncTracerImpl.disable()
})Browser Environment (farrow-pipeline/asyncTracerImpl.browser)
Description:
- Browser environment completely unsupported for farrow-pipeline
- Importing this module directly throws error:
No Implementation - farrow-pipeline is only for Node.js server environment
Utility Functions
Koa Compatibility
compose - Koa Middleware Composer
Function Signature:
function compose<T, U>(
middlewares: KoaMiddleware<T, U>[]
): (context: T, next?: KoaMiddleware<T, U>) => Promise<U | void | undefined>
type KoaMiddleware<T, U = void> = Middleware<T, MiddlewareReturnType<U>>
type MiddlewareReturnType<T> = void | undefined | T | Promise<T | void | undefined>Description:
- Compose Koa-style middleware array into single function
- Consistent with Koa's
composebehavior - Internally implemented using farrow-pipeline
Usage Example:
import { compose } from 'farrow-pipeline'
const middleware1 = async (ctx, next) => {
ctx.state.count = 1
await next()
}
const middleware2 = async (ctx, next) => {
ctx.state.count += 1
await next()
}
const composed = compose([middleware1, middleware2])
const ctx = { state: {} }
await composed(ctx)
console.log(ctx.state.count) // 2Type Utilities
farrow-pipeline exports some type utilities for extracting information from existing types:
PipelineInput<T> - Extract Pipeline Input Type
Extract input type from Pipeline type:
const pipeline = createPipeline<number, string>()
type Input = PipelineInput<typeof pipeline> // numberPipelineOutput<T> - Extract Pipeline Output Type
Extract output type from Pipeline type:
const pipeline = createPipeline<number, string>()
type Output = PipelineOutput<typeof pipeline> // stringMiddlewareType<T> - Extract Middleware Type
Extract middleware type from middleware input:
type MyMiddlewareInput = Middleware<Request, Response> | { middleware: Middleware<Request, Response> }
type ExtractedMiddleware = MiddlewareType<MyMiddlewareInput> // Middleware<Request, Response>MaybeAsync<T> - Possibly Async Type
Represents a value that may be sync or Promise:
type MaybeAsync<T> = T | Promise<T>
// Usage example
function getValue(): MaybeAsync<number> {
// Can return sync value
return 42
// Or async value
return Promise.resolve(42)
}FAQ
Q: Context lost in async operations?
A: Ensure AsyncTracer is enabled:
import * as asyncTracerImpl from 'farrow-pipeline/asyncTracerImpl.node'
// Required in Node.js environment
asyncTracerImpl.enable()
// Now Context automatically passes in async operations
pipeline.use(async (input, next) => {
UserContext.set(user)
await delay(1000) // Async wait
const user = UserContext.get() // ✅ Correctly retrieved
return next(input)
})Q: When to use usePipeline?
A: When you need to run sub-Pipeline in middleware while maintaining context:
// ❌ Wrong: Context lost
mainPipeline.use((input, next) => {
const result = subPipeline.run(input) // New container
return next(result)
})
// ✅ Correct: Inherit context
mainPipeline.use((input, next) => {
const runSubPipeline = usePipeline(subPipeline)
const result = runSubPipeline(input) // Inherited container
return next(result)
})Q: Can Pipeline be reused?
A: Yes, each run() is independent execution:
const pipeline = createPipeline<number, number>()
.use(x => x + 1)
// Use same Pipeline multiple times
const result1 = pipeline.run(1) // 2
const result2 = pipeline.run(5) // 6
// Concurrent execution is also safe
await Promise.all([
pipeline.run(1),
pipeline.run(2),
pipeline.run(3)
])Q: How to debug Pipeline?
A: Use logging middleware:
const debugMiddleware = (input: any, next: Next<any>) => {
console.log('Input:', input)
const result = next(input)
console.log('Output:', result)
return result
}
pipeline.use(debugMiddleware)Q: Browser environment support?
A: Not supported. farrow-pipeline depends on Node.js AsyncLocalStorage (async_hooks), browser environment cannot use async context tracing functionality, importing throws error. farrow-pipeline is only for Node.js server environment.
Summary
farrow-pipeline provides a powerful and flexible middleware pipeline system, enabling you to:
- Type Safety - Full TypeScript type inference
- Easy Composition - Pipelines can be nested as middleware
- Context Isolation - Independent container for each run
- Async-Friendly - Async context passing based on AsyncLocalStorage
- Testable - Container mechanism facilitates unit testing
- High Performance - Lightweight implementation, excellent performance
Through these features, farrow-pipeline helps you build type-safe, maintainable, high-performance applications.
