@tanaygoyal1111/fn-pipe
v1.0.0
Published
Zero-dependency, type-safe function composition with async support, error handling & retry logic
Downloads
103
Maintainers
Readme
fn-pipe
Zero-dependency, type-safe function composition with async support, error handling & retry logic
fn-pipe is a lightweight, zero-dependency utility library for composing functions in JavaScript/TypeScript. It provides a clean, functional API for building data pipelines with full type safety, async support, error handling, retry logic, and conditional execution — all in a tiny package.
✨ Features
- 🚀 Zero Dependencies — No external dependencies, ever
- 🔒 Type Safe — Full TypeScript inference with up to 9 function overloads
- ⚡ Sync & Async — Seamlessly mix synchronous and asynchronous functions
- 🛡️ Error Handling — Built-in
Result<T, E>type for explicit error handling - 🔄 Retry Logic — Automatic retry with exponential backoff
- ⏱️ Timeout Support — Built-in timeout for async operations
- 🎯 Conditional Execution —
when,guard,filter,defaultToutilities - 📦 Tiny Bundle — < 2KB gzipped
- 🔧 ESM + CJS — Dual module support
- 🧪 Well Tested — 95%+ code coverage
📦 Installation
npm install @tanaygoyal1111/fn-pipe
# or
yarn add @tanaygoyal1111/fn-pipe
# or
pnpm add @tanaygoyal1111/fn-pipe🚀 Quick Start
Basic Synchronous Pipe
import { pipe } from 'fn-pipe'
const result = pipe(
5,
(x) => x + 1, // 6
(x) => x * 2, // 12
(x) => x.toString() // '12'
)
// result: '12'Async Pipe
import { pipeAsync } from 'fn-pipe'
const result = await pipeAsync(
'https://api.example.com/users',
async (url) => {
const res = await fetch(url)
return res.json()
},
(users) => users.filter((u: any) => u.active),
async (users) => {
await logToAnalytics(users.length)
return users
}
)Error Handling with Result
import { pipeResult, ok, err } from 'fn-pipe'
const result = pipeResult(
5,
(x) => ok(x + 1),
(x) => x > 10 ? err('Too big') : ok(x * 2),
(x) => ok(x.toString())
)
if (result.ok) {
console.log(result.value) // '12'
} else {
console.error(result.error) // 'Too big' (if condition fails)
}Retry Logic
import { pipeResultRetry } from 'fn-pipe'
const result = await pipeResultRetry(
'https://api.example.com/data',
async (url) => {
const res = await fetch(url)
return res.ok ? ok(await res.json()) : err('Failed')
},
{ retries: 3, delay: 1000, backoff: 2 }
)Conditional Execution
import { pipe, when, guard, tap } from 'fn-pipe'
const result = pipe(
{ user: { name: 'Alice', age: 25 } },
tap((data) => console.log('Processing:', data.user.name)),
(data) => data.user,
guard((user) => user.age >= 18, 'User must be adult'),
when(
(user) => user.age > 21,
(user) => ({ ...user, category: 'senior' })
),
(user) => ({ name: user.name, category: user.category || 'standard' })
)📖 API Reference
Core Functions
pipe(value, ...fns)
Compose synchronous functions left-to-right.
pipe(5, (x) => x + 1, (x) => x * 2) // 12pipeAsync(value, ...fns)
Compose async functions. Mix sync and async freely.
await pipeAsync(5, async (x) => x + 1, (x) => x * 2) // 12createPipe(...fns) / createPipeAsync(...fns)
Create reusable pipe functions.
const transform = createPipe(
(x: number) => x + 1,
(x) => x * 2,
(x) => x.toString()
)
transform(5) // '12'Result Handling
pipeResult(value, ...fns)
Pipe with Result<T, E> type for explicit error handling. Sync only.
import { ok, err } from 'fn-pipe'
pipeResult(
5,
(x) => ok(x + 1),
(x) => x > 10 ? err('Too big') : ok(x * 2)
)pipeResultAsync(value, ...fns)
Async version of pipeResult.
pipeResultRetry(value, ...fns, retryConfig)
Result pipe with automatic retry.
pipeResultRetry(
value,
fn1,
fn2,
{ retries: 3, delay: 1000, backoff: 2, retryIf: (e) => e.message !== 'fatal' }
)pipeResultTimeout(value, ...fns, timeoutConfig)
Result pipe with timeout.
pipeResultTimeout(
value,
fn1,
fn2,
{ ms: 5000, message: 'Operation timed out' }
)Conditional Utilities
| Function | Description |
|----------|-------------|
| when(condition, fn) | Execute fn only if condition is true |
| whenElse(condition, thenFn, elseFn) | Execute thenFn or elseFn based on condition |
| guard(condition, message) | Throw error if condition is not met |
| tap(fn) | Execute side effect without changing value |
| tapAsync(fn) | Async version of tap |
| map(fn) | Transform value (identity in pipe) |
| filter(predicate) | Throw if predicate fails |
| defaultTo(fn) | Provide fallback for null/undefined |
| tryCatch(fn, onError) | Catch errors and return fallback |
| tryCatchAsync(fn, onError) | Async version of tryCatch |
Utility Functions
| Function | Description |
|----------|-------------|
| withRetry(fn, config) | Retry async function with exponential backoff |
| withTimeout(promise, config) | Add timeout to any promise |
| sleep(ms) | Promise-based delay |
| isPromise(value) | Check if value is a promise |
🏗️ TypeScript
fn-pipe is written in TypeScript and provides full type inference:
import { pipe, createPipe } from 'fn-pipe'
// Type inference works automatically
const result = pipe(
'hello',
(s) => s.toUpperCase(), // s: string
(s) => s + ' WORLD', // s: string
(s) => s.split(' ') // s: string, returns: string[]
)
// result is inferred as string[]
// Reusable pipes with typed parameters
const formatNumber = createPipe(
(n: number) => n * 2,
(n) => n.toString(),
(s) => `$${s}`
)
// formatNumber: (n: number) => string📊 Benchmarks
| Library | Size (gzip) | Dependencies | Type Safe | Async | Retry | |---------|------------|--------------|-----------|-------|-------| | fn-pipe | ~1.8KB | 0 | ✅ | ✅ | ✅ | | remeda | ~15KB | 0 | ✅ | ✅ | ❌ | | radash | ~8KB | 0 | ⚠️ | ✅ | ❌ | | fp-ts | ~30KB | 0 | ✅ | ✅ | ❌ | | lodash/fp | ~24KB | 0 | ❌ | ❌ | ❌ |
🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details.
📄 License
MIT © Tanay Goyal
