https://github.com/egeozcan/ppipe
pipes values through functions, an alternative to using the proposed pipe operator ( |> ) for ES
https://github.com/egeozcan/ppipe
async chain pipe pipe-operator pipes-values promise
Last synced: 5 months ago
JSON representation
pipes values through functions, an alternative to using the proposed pipe operator ( |> ) for ES
- Host: GitHub
- URL: https://github.com/egeozcan/ppipe
- Owner: egeozcan
- License: isc
- Created: 2016-05-12T17:45:37.000Z (about 10 years ago)
- Default Branch: master
- Last Pushed: 2025-09-07T04:29:17.000Z (9 months ago)
- Last Synced: 2026-01-17T21:26:02.623Z (5 months ago)
- Topics: async, chain, pipe, pipe-operator, pipes-values, promise
- Language: JavaScript
- Size: 490 KB
- Stars: 193
- Watchers: 4
- Forks: 8
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Contributing: .github/CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# [](https://github.com/egeozcan/ppipe)
[](https://travis-ci.org/egeozcan/ppipe)
[](https://coveralls.io/github/egeozcan/ppipe?branch=master)
[](https://www.npmjs.com/package/ppipe)
[](https://www.npmjs.com/package/ppipe)
[](https://github.com/egeozcan/ppipe/blob/master/LICENSE)
**Strictly-typed pipes for values through functions**, an alternative to using the
[proposed pipe operator](https://github.com/tc39/proposal-pipeline-operator) ( |> ) for ES.
Version 3.0 is a complete TypeScript rewrite with **maximum type safety** - no `any` in the public API, full IDE autocomplete support, and correct type inference throughout the chain.
## Installation
```bash
npm install ppipe
```
## Quick Start
```typescript
import ppipe, { _ } from 'ppipe';
const add = (x: number, y: number) => x + y;
const square = (x: number) => x * x;
const divide = (x: number, y: number) => x / y;
const double = (x: number) => x * 2;
// Basic piping
const result = ppipe(1)
.pipe(add, _, 1) // 2
.pipe(double) // 4
.pipe(square) // 16
.pipe(divide, _, 8) // 2
.pipe(add, _, 1) // 3
.value;
console.log(result); // 3
```
## Features
### Basic Piping
Chain functions together, passing the result of each to the next:
```typescript
ppipe('hello')
.pipe(s => s.toUpperCase())
.pipe(s => s + '!')
.value; // 'HELLO!'
```
### Placeholder Positioning
Use `_` to control where the piped value is inserted:
```typescript
const _ = ppipe._;
// Value inserted at placeholder position
ppipe(10)
.pipe(divide, _, 2) // divide(10, 2) = 5
.value;
// Without placeholder, value is appended at the end
ppipe(10)
.pipe(divide, 100) // divide(100, 10) = 10
.value;
// Multiple placeholders insert the same value multiple times
ppipe(5)
.pipe((a, b) => a + b, _, _) // 5 + 5 = 10
.value;
```
### Async/Promise Support
Promises are automatically handled - the chain waits for resolution and passes the unwrapped value to the next function:
```typescript
async function fetchUser(id: number) {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
const userName = await ppipe(1)
.pipe(fetchUser)
.pipe(user => user.name)
.pipe(name => name.toUpperCase());
// Or use .then()/.catch()
ppipe(1)
.pipe(fetchUser)
.pipe(user => user.name)
.then(name => console.log(name))
.catch(err => console.error(err));
```
### Value Extraction
Get the current value with `.value` (or `.val`):
```typescript
// Sync value
const num = ppipe(5).pipe(x => x * 2).value; // 10
// Async value (returns Promise)
const asyncNum = await ppipe(Promise.resolve(5)).pipe(x => x * 2).value;
```
### Typed Extensions
Create reusable pipe extensions with full type inference:
```typescript
const mathPipe = ppipe.extend({
double: (x: number) => x * 2,
square: (x: number) => x * x,
add: (x: number, y: number) => x + y,
});
const result = mathPipe(5)
.double() // 10 - return type inferred as number
.square() // 100
.add(5) // 105
.value;
// Extensions can be chained
const extendedPipe = mathPipe.extend({
stringify: (x: number) => String(x),
});
const str = extendedPipe(5)
.double()
.stringify() // '10' - return type inferred as string
.value;
```
### Generic Pass-Through Extensions
Generic identity functions like `log` or `tap` preserve the pipe's type automatically:
```typescript
const pp = ppipe.extend({
log: (value: T, label?: string): T => {
console.log(label ?? 'value:', value);
return value;
},
});
// Type is preserved through .log() - no type loss!
pp(8)
.log('start') // logs: "start: 8"
.pipe(x => x + 3) // x is number, not unknown
.log('end') // logs: "end: 11"
.value; // 11
pp('hello')
.log()
.pipe(s => s.toUpperCase()) // s is string
.value; // 'HELLO'
```
## API Reference
### `ppipe(value)`
Creates a new pipe with the given initial value.
```typescript
const pipe = ppipe(initialValue);
```
### `.pipe(fn, ...args)`
Pipes the current value through a function. The value is inserted at the placeholder position, or appended at the end if no placeholder is used.
```typescript
pipe.pipe(fn) // fn(value)
pipe.pipe(fn, _, arg2) // fn(value, arg2)
pipe.pipe(fn, arg1) // fn(arg1, value)
pipe.pipe(fn, arg1, _) // fn(arg1, value)
```
### `.value` / `.val`
Gets the current value from the chain. Returns a Promise if any function in the chain was async.
### `.then(onFulfilled?, onRejected?)`
Standard Promise `then` interface. Always available for consistent async handling.
### `.catch(onRejected?)`
Standard Promise `catch` interface. Always available for consistent async handling.
### `ppipe._`
The placeholder symbol for argument positioning.
### `ppipe.extend(extensions)`
Creates a new ppipe factory with additional methods:
```typescript
const extended = ppipe.extend({
methodName: (value, ...args) => result,
});
```
Extension functions receive the piped value as their first argument.
## Migration from v2.x
Version 3.0 is a TypeScript rewrite that prioritizes type safety. Some dynamic features that couldn't be strictly typed have been removed:
### Removed Features
| Feature | v2.x | v3.x Alternative |
|---------|------|------------------|
| Deep property access | `_.a.b.c` | `.pipe(x => x.a.b.c)` |
| Array spreading | `..._` | `.pipe(arr => fn(...arr))` |
| Direct method access | `.map(fn)` | `.pipe(arr => arr.map(fn))` |
| Context binding | `.with(ctx)` | `.pipe(fn.bind(ctx))` |
| Callable syntax | `ppipe(val)(fn)` | `ppipe(val).pipe(fn)` |
### Why These Changes?
These features relied on Proxy magic that returned `any` types, breaking TypeScript's ability to infer types correctly. The v3.x API ensures:
- Full IDE autocomplete support
- Correct type inference throughout the chain
- No `any` types in the public API
- Compile-time error detection
## Type Safety
ppipe v3.x provides complete type inference:
```typescript
// Types are inferred correctly through the chain
const result = ppipe(5)
.pipe(x => x * 2) // Pipe
.pipe(x => x.toString()) // Pipe
.pipe(x => x.length) // Pipe
.value; // number
// Async types are tracked
const asyncResult = ppipe(Promise.resolve(5))
.pipe(x => x * 2) // Pipe
.value; // Promise
// Extension return types are inferred
const myPipe = ppipe.extend({
toArray: (x: T) => [x],
});
myPipe(5).toArray().value; // number[]
// Generic identity extensions preserve the pipe's type
const debugPipe = ppipe.extend({
log: (value: T): T => { console.log(value); return value; },
});
debugPipe(5).log().pipe(x => x * 2).value; // x is number, result is number
```
## Testing
100% test coverage is maintained. To run tests:
```bash
npm install
npm test
```
## Contributing
See [CONTRIBUTING](https://github.com/egeozcan/ppipe/blob/master/.github/CONTRIBUTING.md).
## Changelog
See [CHANGELOG.md](CHANGELOG.md) for version history.
## License
ISC