Skip to content

farrow-pipeline Complete API Reference ​

Table of Contents ​

  1. Core Concepts
  2. Complete Export List
  3. Pipeline Type System
  4. Context System
  5. Container System
  6. Async Tracing (AsyncTracer)
  7. Utility Functions
  8. Type Utilities
  9. FAQ

Core Concepts ​

Design Philosophy ​

farrow-pipeline is a type-safe middleware pipeline system with the following core design principles:

  1. Functional Composition - Middleware forms processing chains through functional composition
  2. Type Safety - Full TypeScript type inference throughout
  3. Context Isolation - Each run has an independent context container
  4. Async-Friendly - Async context passing based on AsyncLocalStorage
  5. Composability - Pipelines can be nested as middleware

Execution Model ​

Input → Middleware1 → Middleware2 → ... → MiddlewareN → Output
         ↓          ↓                ↓
       next()     next()          return

Onion Model:

typescript
// 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 → 4

Complete Export List ​

Main Entry (farrow-pipeline) ​

typescript
// 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) ​

typescript
// Node.js environment
export { enable, disable }

AsyncTracer Entry (farrow-pipeline/asyncTracerImpl.browser) ​

typescript
// Browser environment - throws error (not supported)
throw new Error(`No Implementation`)

Pipeline Type System ​

createPipeline - Create Synchronous Pipeline ​

Function Signature:

typescript
function createPipeline<I, O>(options?: PipelineOptions): Pipeline<I, O>

Type Parameters:

  • I - Input type
  • O - Output type

Parameters:

typescript
type PipelineOptions = {
  contexts?: ContextStorage  // Preset context storage
}

type ContextStorage = {
  [key: string]: Context<any>
}

Return Value:

typescript
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:

typescript
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:

typescript
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:

typescript
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 = 9

createAsyncPipeline - Create Asynchronous Pipeline ​

Function Signature:

typescript
function createAsyncPipeline<I, O>(options?: PipelineOptions): AsyncPipeline<I, O>

Return Value:

typescript
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:

typescript
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:

typescript
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:

  1. First Call: Execute thunk function to get middleware
  2. Caching: Cache loaded middleware to variable
  3. Subsequent Calls: Directly use cached middleware
  4. Promise Handling: Automatically handle sync and async loading
typescript
// 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:

typescript
function usePipeline<I, O>(pipeline: Pipeline<I, O>):
  (input: I, options?: RunPipelineOptions<I, O>) => O

Description:

  • Run another Pipeline within middleware
  • Automatically inherits current container, maintaining context passing
  • Avoid context loss from directly calling pipeline.run()

Why usePipeline is Needed?

typescript
// ❌ 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:

typescript
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:

typescript
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:

typescript
type Middleware<I = unknown, O = unknown> = (input: I, next: Next<I, O>) => O

type Next<I = unknown, O = unknown> = (input?: I) => O

Description:

  • input - Current middleware input
  • next - Function to call next middleware
  • Return value - Middleware output, must match Pipeline output type

Middleware Writing Patterns:

typescript
// 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:

typescript
type MiddlewareInput<I = unknown, O = unknown> =
  | Middleware<I, O>
  | { middleware: Middleware<I, O> }

Description:

  • Supports directly passing middleware function
  • Supports passing object with middleware property

Usage Example:

typescript
// 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:

typescript
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:

typescript
const fn = (input, next) => next(input)
const obj = { middleware: fn }

getMiddleware(fn)   // Returns fn
getMiddleware(obj)  // Returns obj.middleware

Pipeline Method Details ​

pipeline.use - Add Middleware ​

Method Signature:

typescript
use(...inputs: MiddlewareInput<I, O>[]): Pipeline<I, O>

Description:

  • Accepts any number of middleware
  • Supports method chaining
  • Middleware executes in order added

Example:

typescript
// 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:

typescript
run(input: I, options?: RunPipelineOptions<I, O>): O

Parameters:

typescript
type RunPipelineOptions<I = unknown, O = unknown> = {
  container?: Container      // Specify run container
  onLast?: (input: I) => O  // Last middleware callback
}

Description:

  • input - Pipeline input
  • options.container - Specify container (defaults to Pipeline creation container)
  • options.onLast - Callback executed when all middleware call next()

Basic Example:

typescript
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 = 12

Using onLast Example:

typescript
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:

typescript
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:

typescript
middleware: Middleware<I, O>

Description:

  • Convert current Pipeline to middleware function
  • Can nest into other Pipelines
  • Automatically inherits parent Pipeline container

Example:

typescript
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:

typescript
// 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:

typescript
function isPipeline(obj: any): obj is Pipeline

Description:

  • Type guard function
  • Check if object has Pipeline identifier

Usage Example:

typescript
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:

typescript
type PipelineInput<T extends Pipeline> = T extends Pipeline<infer I> ? I : never

Usage Example:

typescript
const pipeline = createPipeline<number, string>()

type Input = PipelineInput<typeof pipeline>  // number

PipelineOutput - Extract Output Type ​

Type Definition:

typescript
type PipelineOutput<T extends Pipeline> = T extends Pipeline<any, infer O> ? O : never

Usage Example:

typescript
const pipeline = createPipeline<number, string>()

type Output = PipelineOutput<typeof pipeline>  // string

Context System ​

createContext - Create Context ​

Function Signature:

typescript
function createContext<T>(defaultValue: T): Context<T>

Type Definition:

typescript
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:

typescript
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 ​

typescript
get(): T

Description:

  • Get context value from current container
  • Returns default value if not in container

Example:

typescript
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 ​

typescript
set(value: T): void

Description:

  • Set context value in current container
  • Overwrites previous value

Example:

typescript
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 ​

typescript
assert(): Exclude<T, undefined | null>

Description:

  • Assert context value is not null
  • Throws error if null or undefined
  • Returns non-null type

Example:

typescript
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 ​

typescript
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:

typescript
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:

typescript
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 0

Multiple Context Example:

typescript
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:

typescript
function isContext(input: any): input is Context

Usage Example:

typescript
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:

typescript
function assertContext(input: any): asserts input is Context

Description:

  • TypeScript assertion function
  • Throws error if not Context

Usage Example:

typescript
function useContextValue(ctx: any) {
  assertContext(ctx)
  // After this, TypeScript knows ctx is Context type
  return ctx.get()
}

Container System ​

createContainer - Create Container ​

Function Signature:

typescript
function createContainer(contextStorage?: ContextStorage): Container

Type Definition:

typescript
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:

typescript
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:

typescript
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:

typescript
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:

typescript
read<V>(context: Context<V>): V

Description:

  • Read specified context value from container
  • Returns Context default value if not in container

Example:

typescript
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:

typescript
write<V>(context: Context<V>, value: V): void

Description:

  • Write specified context value to container
  • Overwrites previous value

Example:

typescript
const CountContext = createContext(0)

const container = createContainer()

container.write(CountContext, 10)
const count = container.read(CountContext)  // 10

Container Utility Functions ​

useContainer - Get Current Container ​

Function Signature:

typescript
function useContainer(): Container

Description:

  • Get container in current async tracing context
  • Must be called when Pipeline is running
  • Used to access container in middleware

Usage Example:

typescript
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:

typescript
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:

typescript
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:

typescript
function isContainer(input: any): input is Container

Usage Example:

typescript
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:

typescript
function assertContainer(input: any): asserts input is Container

Usage Example:

typescript
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:

  1. Async Context Passing - Maintain context in async operations like async/await, Promise, setTimeout
  2. Request-Level Isolation - Each request/run has independent container, avoid concurrent pollution
  3. 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:

typescript
function enable(): void

Description:

  • 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:

typescript
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:

typescript
function disable(): void

Description:

  • Disable async tracing
  • Used for resource cleanup

Usage Example:

typescript
// 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:

typescript
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 compose behavior
  • Internally implemented using farrow-pipeline

Usage Example:

typescript
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)  // 2

Type 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:

typescript
const pipeline = createPipeline<number, string>()
type Input = PipelineInput<typeof pipeline>  // number

PipelineOutput<T> - Extract Pipeline Output Type ​

Extract output type from Pipeline type:

typescript
const pipeline = createPipeline<number, string>()
type Output = PipelineOutput<typeof pipeline>  // string

MiddlewareType<T> - Extract Middleware Type ​

Extract middleware type from middleware input:

typescript
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:

typescript
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:

typescript
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:

typescript
// ❌ 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:

typescript
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:

typescript
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.

This is a third-party Farrow documentation site | Built with ❤️ and TypeScript