gluify
v2.0.0
Published
A type-safe pipeline library for TypeScript that glues functions from different libraries together
Maintainers
Readme
Gluify
A type-safe pipeline library for TypeScript that glues functions from different libraries together.
Why Gluify?
JavaScript lacks native pipe operators (like R's |> or PowerShell's |). While TC39's F# pipeline proposal is in the works, Gluify provides a production-ready solution today for creating type-safe, composable data pipelines.
Unlike Lodash (which only chains its own methods) or Ramda (which requires curried functions), Gluify lets you chain ANY functions from different libraries together.
Features
✅ Lazy Evaluation - Nothing executes until .run() or .runAsync()
✅ Async Support - Mix sync/async functions freely with .pipeAsync(), single await at end
✅ Error Handling - Built-in .catch(), .recover(), .when() for robust pipelines
✅ 27+ Utility Methods - Built-in, type-safe, no separate imports
✅ Type Safety - Full TypeScript generics, conditional types
✅ Clean API - Single import, intuitive chaining
✅ Zero Dependencies - Lightweight and fast
Installation
npm install gluifyQuick Start
import { gluify } from 'gluify';
// Basic pipeline
const result = gluify(() => [1, 2, 3, 4, 5])
.filter(n => n % 2 === 0)
.map(n => n * 2)
.reduce((sum, n) => sum + n, 0)
.run(); // 12
// Async pipeline
const user = await gluify(fetchUser, userId)
.pipe(validateUser)
.pipe(enrichProfile)
.catch(error => defaultUser)
.runAsync();
// String processing
const result = gluify(() => ' hello world ')
.trim()
.toUpperCase()
.replace('WORLD', 'GLUIFY')
.run(); // "HELLO GLUIFY"Core Concepts
1. Lazy Evaluation
Operations are stored and only executed when you call .run() or .runAsync():
const chain = gluify(expensiveFunction)
.pipe(operation1)
.pipe(operation2); // Nothing executed yet!
const result = chain.run(); // NOW it executes2. Type-Safe Chaining
TypeScript knows which methods are available based on the current type:
gluify(() => [1, 2, 3])
.map(n => n * 2) // ✅ Available - T is array
.filter(n => n > 2) // ✅ Available - still array
.trim() // ❌ Error - trim only for strings3. Async Made Easy
No more nested await or .then() chains. Use .pipeAsync() to handle Promises in the chain:
// Before (painful)
const data = await fetchData(id);
const processed = await processData(data);
const validated = await validate(processed);
// After (clean) - pipeAsync awaits Promises automatically
const result = await gluify(fetchData, id)
.pipeAsync(data => processData(data)) // Awaits fetchData result
.pipeAsync(processed => validate(processed)) // Awaits processData result
.runAsync();API Reference
Core Methods
gluify(fn, ...args)
Start a pipeline with an initial function and arguments.
gluify(() => 42)
gluify(Math.sqrt, 16)
gluify(fetchUser, userId).pipe(fn, ...args)
Add any function to the pipeline.
.pipe(x => x * 2)
.pipe(myFunction)
.pipe(add, 10) // Additional args.pipeAsync(fn, ...args)
Async pipe - awaits Promises before applying the function. Use this when the previous operation returns a Promise and you need the resolved value.
// When initial function returns Promise<User>
await gluify(fetchUser, userId)
.pipeAsync(user => user.profile) // Awaits Promise, receives User
.pipe(profile => profile.name) // Regular pipe for sync transform
.runAsync();.run()
Execute the pipeline synchronously and return the result.
const result = gluify(() => 5)
.pipe(x => x * 2)
.run(); // 10.runAsync()
Execute the pipeline asynchronously (handles both sync and async functions).
const result = await gluify(asyncFn)
.pipeAsync(resolvedValue => transform(resolvedValue))
.pipe(syncFn)
.runAsync();.tap(fn)
Execute a side effect without changing the value (useful for logging).
gluify(() => 42)
.tap(x => console.log('Debug:', x))
.pipe(x => x * 2)
.run(); // Logs: "Debug: 42", returns 84Error Handling
.catch(handler)
Catch errors and provide recovery logic.
gluify(mightFail)
.pipe(processData)
.catch(error => ({ fallback: 'data' }))
.run();.recover(fallbackValue)
Simple fallback value on error.
gluify(parseJSON, invalidJSON)
.recover({ error: true })
.run();.when(predicate, fn)
Conditionally execute a function.
gluify(() => user)
.when(u => u.age >= 18, markAsAdult)
.run();Array Utilities
map(fn)- Transform each elementfilter(predicate)- Keep elements matching predicatereduce(fn, initial)- Reduce to single valuefind(predicate)- Find first matching elementsome(predicate)- Check if any element matchesevery(predicate)- Check if all elements matchtake(n)- Get first n elementsskip(n)- Skip first n elementssort(compareFn?)- Sort arrayreverse()- Reverse arrayflat()- Flatten one levelunique()- Get unique elements
Object Utilities
pick(...keys)- Select specific keysomit(...keys)- Exclude specific keyskeys()- Get object keysvalues()- Get object valuesentries()- Get key-value pairsmerge(other)- Merge with another object
String Utilities
trim()- Remove whitespacesplit(separator)- Split into arrayjoin(separator?)- Join array into stringreplace(search, replace)- Replace substringtoUpperCase()- Convert to uppercasetoLowerCase()- Convert to lowercase
General Utilities
defaultTo(fallback)- Provide default for null/undefinedisNil()- Check if null or undefinedclone()- Shallow clone
Real-World Examples
Data Processing Pipeline
interface Product {
id: number;
name: string;
price: number;
category: string;
inStock: boolean;
}
const topProducts = gluify(() => products)
.filter(p => p.inStock)
.filter(p => p.category === 'Electronics')
.map(p => ({ name: p.name, price: p.price }))
.sort((a, b) => b.price - a.price)
.take(10)
.map(p => `${p.name} ($${p.price})`)
.join(', ')
.run();API Error Handling
const userData = await gluify(fetchUser, userId)
.catch(error => {
console.error('Fetch failed:', error);
return getCachedUser(userId);
})
.pipe(enrichUserData)
.when(user => user.needsValidation, validateUser)
.runAsync();String Processing
const processedText = gluify(() => 'apple,banana,cherry')
.split(',')
.map(fruit => fruit.trim())
.map(fruit => fruit.toUpperCase())
.filter(fruit => fruit.length > 5)
.join(' | ')
.run(); // "BANANA | CHERRY"Limitations
Piping to non-first arguments: Gluify always pipes to the first argument. For other positions, use a lambda:
// ❌ Not supported
.pipe(add, 10) // Where does piped value go?
// ✅ Use a lambda instead
.pipe(x => add(10, x)) // Clear and explicitThis keeps the library simple, type-safe, and easy to understand.
Why Not Lodash or Ramda?
| Feature | Gluify | Lodash | Ramda | |---------|--------|--------|-------| | Chain any function | ✅ | ❌ (only Lodash methods) | ⚠️ (requires currying) | | Lazy evaluation | ✅ | ✅ | ❌ | | Built-in async support | ✅ | ❌ | ❌ | | Type safety | ✅ | ⚠️ (limited) | ⚠️ (complex) | | Error handling | ✅ | ❌ | ❌ | | Zero dependencies | ✅ | ❌ | ❌ |
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT © oharu121
