Skip to content

Commit 141283b

Browse files
committed
chore: initial commit
0 parents  commit 141283b

25 files changed

+730
-0
lines changed

.editorconfig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_style = tab
7+
insert_final_newline = true
8+
trim_trailing_whitespace = true
9+
10+
[*.yml]
11+
indent_style = space
12+
indent_size = 2

.github/workflows/main.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: CI
2+
on:
3+
- push
4+
- pull_request
5+
permissions:
6+
contents: read
7+
jobs:
8+
test:
9+
name: Node.js ${{ matrix.node-version }}
10+
runs-on: ubuntu-latest
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
node-version:
15+
- 18
16+
- 20
17+
- 22
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: actions/setup-node@v4
21+
with:
22+
node-version: ${{ matrix.node-version }}
23+
- run: npm install
24+
- run: npm test

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
coverage
3+
distribution

.husky/commit-msg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npx --no -- commitlint --edit ${1}

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npx --no -- lint-staged

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

CONTRIBUTING.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Contributing
2+
3+
First time contributing, **please note we enforce** [commit message conventions](#commit-message).
4+
5+
### Commit message
6+
7+
```
8+
feat(example): migrate S3 URL to virtual hosted-style
9+
^--^ ^-----^ ^------------------------------------^
10+
| | |
11+
| | +-> Summary in present tense.
12+
| |
13+
| +-----------> Optional scope.
14+
|
15+
+----------------> Type: build, ci, chore, docs, feat, fix, perf, refactor, style, or test.
16+
```
17+
18+
For more info about message body, see:
19+
20+
- [Writing git commit messages](http://365git.tumblr.com/post/3308646748/writing-git-commit-messages)
21+
- [A note about git commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
22+
23+
More examples at [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/).

LICENSE

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Copyright (c) 2023 Diogo Azevedo <diogoazevedos@gmail.com>
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without
5+
modification, are permitted provided that the following conditions are met:
6+
7+
1. Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
10+
2. Redistributions in binary form must reproduce the above copyright notice,
11+
this list of conditions and the following disclaimer in the documentation
12+
and/or other materials provided with the distribution.
13+
14+
3. Neither the name of the copyright holder nor the names of its
15+
contributors may be used to endorse or promote products derived from
16+
this software without specific prior written permission.
17+
18+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# pipe-fun
2+
3+
> Build and compose pipelines with ease
4+
5+
## Install
6+
7+
```sh
8+
npm install -S pipe-fun
9+
```
10+
11+
## Usage
12+
13+
```ts
14+
import {_, pipe} from 'pipe-fun';
15+
16+
const pipeline = pipe(() => ({name: 'John Smith'}), user => user.name);
17+
18+
await pipeline(_);
19+
//=> John Smith
20+
```

main.ts

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
type Awaitable<T> = T | Promise<T>;
2+
export type Pipe<Output, Input> = (input: Input) => Awaitable<Output>;
3+
export type Step<Output, T> = <Input extends T>(input: Input) => Awaitable<Output>;
4+
5+
/**
6+
* Special step that assign the input and output.
7+
*
8+
* @example
9+
* ```
10+
* const addHobby = middleware(() => ({hobby: 'Coding'}));
11+
*
12+
* await addHobby({name: 'John Smith'});
13+
* //=> {name: 'John Smith', hobby: 'Coding'}
14+
* ```
15+
*/
16+
export function middleware<Output, T>(step: Pipe<Output, T>) {
17+
return async <Input extends T>(input: Input) => ({...input, ...(await step(input))});
18+
}
19+
20+
/**
21+
* Compose multiple steps.
22+
*
23+
* @example
24+
* ```
25+
* const getUser = async () => ({name: 'John Smith'});
26+
* const addHobby = middleware(() => ({hobby: 'Coding'}));
27+
*
28+
* const pipeline = pipe(getUser, addHobby);
29+
*
30+
* await pipeline();
31+
* //=> {name: 'John Smith', hobby: 'Coding'}
32+
* ```
33+
*/
34+
export function pipe<Output, A, Input>(
35+
s1: Pipe<A, Input>,
36+
s2: Pipe<Output, A>
37+
): Pipe<Output, Input>;
38+
export function pipe<Output, B, A, Input>(
39+
s1: Pipe<A, Input>,
40+
s2: Pipe<B, A>,
41+
s3: Pipe<Output, B>
42+
): Pipe<Output, Input>;
43+
export function pipe<Output, C, B, A, Input>(
44+
s1: Pipe<A, Input>,
45+
s2: Pipe<B, A>,
46+
s3: Pipe<C, B>,
47+
s4: Pipe<Output, C>
48+
): Pipe<Output, Input>;
49+
export function pipe<Output, D, C, B, A, Input>(
50+
s1: Pipe<A, Input>,
51+
s2: Pipe<B, A>,
52+
s3: Pipe<C, B>,
53+
s4: Pipe<D, C>,
54+
s5: Pipe<Output, D>
55+
): Pipe<Output, Input>;
56+
export function pipe<Output, E, D, C, B, A, Input>(
57+
s1: Pipe<A, Input>,
58+
s2: Pipe<B, A>,
59+
s3: Pipe<C, B>,
60+
s4: Pipe<D, C>,
61+
s5: Pipe<E, D>,
62+
s6: Pipe<Output, E>
63+
): Pipe<Output, Input>;
64+
export function pipe<Output, F, E, D, C, B, A, Input>(
65+
s1: Pipe<A, Input>,
66+
s2: Pipe<B, A>,
67+
s3: Pipe<C, B>,
68+
s4: Pipe<D, C>,
69+
s5: Pipe<E, D>,
70+
s6: Pipe<F, E>,
71+
s7: Pipe<Output, F>
72+
): Pipe<Output, Input>;
73+
export function pipe<Input>(
74+
init: Pipe<unknown, Input>,
75+
...steps: Array<Pipe<unknown, unknown>>
76+
): Pipe<unknown, Input> {
77+
// eslint-disable-next-line unicorn/no-array-reduce
78+
return input => steps.reduce(async (promise, step) => step(await promise), init(input));
79+
}
80+
81+
/**
82+
* Execute steps in parallel.
83+
*
84+
* @example
85+
* ```
86+
* const getUser = async () => ({name: 'John Smith'});
87+
* const getSession = async () => ({id: '4a53'});
88+
*
89+
* const pipeline = parallel(getUser, getSession);
90+
*
91+
* await pipeline();
92+
* //=> [{name: 'John Smith'}, {id: '4a53'}]
93+
* ```
94+
*/
95+
export function parallel<D, C, B, A>(
96+
s1: Pipe<B, A>,
97+
s2: Pipe<D, C>
98+
): Pipe<[B, D], A & C>;
99+
export function parallel<F, E, D, C, B, A>(
100+
s1: Pipe<B, A>,
101+
s2: Pipe<D, C>,
102+
s3: Pipe<F, E>
103+
): Pipe<[B, D, F], A & C & E>;
104+
export function parallel<H, G, F, E, D, C, B, A>(
105+
s1: Pipe<B, A>,
106+
s2: Pipe<D, C>,
107+
s3: Pipe<F, E>,
108+
s4: Pipe<H, G>
109+
): Pipe<[B, D, F, H], A & C & E & G>;
110+
export function parallel<J, I, H, G, F, E, D, C, B, A>(
111+
s1: Pipe<B, A>,
112+
s2: Pipe<D, C>,
113+
s3: Pipe<F, E>,
114+
s4: Pipe<H, G>,
115+
s5: Pipe<J, I>
116+
): Pipe<[B, D, F, H, J], A & C & E & G & I>;
117+
export function parallel<L, K, J, I, H, G, F, E, D, C, B, A>(
118+
s1: Pipe<B, A>,
119+
s2: Pipe<D, C>,
120+
s3: Pipe<F, E>,
121+
s4: Pipe<H, G>,
122+
s5: Pipe<J, I>,
123+
s6: Pipe<L, K>
124+
): Pipe<[B, D, F, H, J, L], A & C & E & G & I & K>;
125+
export function parallel<N, M, L, K, J, I, H, G, F, E, D, C, B, A>(
126+
s1: Pipe<B, A>,
127+
s2: Pipe<D, C>,
128+
s3: Pipe<F, E>,
129+
s4: Pipe<H, G>,
130+
s5: Pipe<J, I>,
131+
s6: Pipe<L, K>,
132+
s7: Pipe<N, M>
133+
): Pipe<[B, D, F, H, J, L, N], A & C & E & G & I & K & M>;
134+
export function parallel<Input>(...steps: Array<Pipe<unknown, Input>>): Pipe<unknown[], Input> {
135+
return async input => Promise.all(steps.map(step => step(input)));
136+
}
137+
138+
type Assign<T extends unknown[]> = T extends [infer Head, ...infer Tail]
139+
? Head & Assign<Tail>
140+
: unknown;
141+
142+
/**
143+
* Assign the output of a parallel step.
144+
*
145+
* @example
146+
* ```
147+
* const getUser = async () => ({name: 'John Smith'});
148+
* const addHobby = middleware(() => ({hobby: 'Coding'}));
149+
*
150+
* const pipeline = assign(parallel(getUser, addHobby));
151+
*
152+
* await pipeline();
153+
* //=> {name: 'John Smith', hobby: 'Coding'}
154+
* ```
155+
*/
156+
export function assign<Output extends unknown[], Input>(
157+
step: Step<Output, Input>,
158+
): Pipe<Assign<Output>, Input> {
159+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
160+
return async input => Object.assign({}, ...(await step(input)));
161+
}
162+
163+
/**
164+
* Special step that passthrough the input.
165+
*
166+
* @example
167+
* ```
168+
* type User = {name: string};
169+
*
170+
* const notifyLogin = (user: User) => console.log(`User ${user.name} logged in`);
171+
*
172+
* const pipeline = passthrough(notifyLogin);
173+
*
174+
* await pipeline({name: 'John Smith'});
175+
* //=> {name: 'John Smith'}
176+
* ```
177+
*/
178+
export function passthrough<T>(step: Pipe<unknown, T>) {
179+
// eslint-disable-next-line no-sequences
180+
return async <Input extends T>(input: Input) => (await step(input), input);
181+
}
182+
183+
/**
184+
* Placeholder for step without input.
185+
*
186+
* @example
187+
* ```
188+
* const pipeline = pipe(() => ({name: 'John Smith'}), user => user.name);
189+
*
190+
* await pipeline(_);
191+
* //=> 'John Smith'
192+
* ```
193+
*/
194+
export const _ = {};

0 commit comments

Comments
 (0)