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