@jesscss/awaitable-pipe 2.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +185 -0
- package/lib/helpers.d.ts +16 -0
- package/lib/helpers.js +111 -0
- package/lib/helpers.js.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -0
- package/lib/pipe.d.ts +32 -0
- package/lib/pipe.js +126 -0
- package/lib/pipe.js.map +1 -0
- package/lib/utils.d.ts +13 -0
- package/lib/utils.js +47 -0
- package/lib/utils.js.map +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Matthew Dean
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# awaitable-pipe
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A tiny, zero-dependency pipe with friendly types that “just works”: it stays sync when everything is sync, and turns into a Promise only when something is async. No wrappers, no ceremony.
|
|
6
|
+
|
|
7
|
+
- **Stays sync when it can**: all-sync pipelines return a plain value
|
|
8
|
+
- **Goes async when it must**: any async input/step returns a Promise
|
|
9
|
+
- **One place for errors**: `safePipe` gives you a single `onError` + optional `fallback`
|
|
10
|
+
- **Steps-only API**: start with an initializer step (() => value | Promise), or omit it entirely
|
|
11
|
+
- **Typed nicely**: TypeScript keeps the sync/async shape without Result-like wrappers
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add @jesscss/awaitable-pipe
|
|
17
|
+
# or
|
|
18
|
+
npm i @jesscss/awaitable-pipe
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { pipe, safePipe } from '@jesscss/awaitable-pipe';
|
|
25
|
+
|
|
26
|
+
// Sync stays sync
|
|
27
|
+
const upper = (s: string) => s.toUpperCase();
|
|
28
|
+
const exclaim = (s: string) => s + '!';
|
|
29
|
+
const out = pipe(() => 'ok', upper, exclaim); // 'OK!'
|
|
30
|
+
|
|
31
|
+
// Mixed becomes Promise
|
|
32
|
+
const load = async (s: string) => s + '!';
|
|
33
|
+
const outP = pipe(() => 'ok', upper, load); // Promise<string>
|
|
34
|
+
const result = await outP; // 'OK!'
|
|
35
|
+
|
|
36
|
+
// Start without an initial value
|
|
37
|
+
const s2 = pipe((x?: number) => (x ?? 2) * 3); // 6
|
|
38
|
+
|
|
39
|
+
// Single-point error handling (never throws)
|
|
40
|
+
const boom = () => { throw new Error('nope'); };
|
|
41
|
+
const safe = safePipe({ onError: console.error, fallback: 'X' }, () => 'ok', boom, upper);
|
|
42
|
+
// 'X'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Why would you want this?
|
|
46
|
+
JavaScript Promises are great, but they aren’t free. Every async hop schedules work, allocates objects, and pushes errors across an async boundary. In hot paths, that overhead can add up.
|
|
47
|
+
|
|
48
|
+
That said, consider this a micro-optimization! This is really only a faster approach than forced async / await when a very small percentage of unknown function calls result in Promises (< 10% of steps returning Promises in performance testing). If a greater percentage of steps would normally return promises, then using an async / await pattern on all unknown results (regardless of whether or not those results are a Promise, which JavaScript is fine with) will generally be faster than using this library.
|
|
49
|
+
|
|
50
|
+
### Features
|
|
51
|
+
|
|
52
|
+
- **Zero extra overhead for sync work**: when your steps are synchronous, you get plain values—no microtasks, no `await`, no extra Promise allocations.
|
|
53
|
+
- **Seamless async when you need it**: if any step is async, the pipeline naturally promotes to a Promise—no special handling required.
|
|
54
|
+
- **Cleaner stacks**: sync-only flows keep straightforward stack traces and easier debugging.
|
|
55
|
+
- **Simple error strategy**: prefer natural throw/reject with `pipe`, or centralize it once with `safePipe` without wrapping results.
|
|
56
|
+
|
|
57
|
+
## API
|
|
58
|
+
|
|
59
|
+
### pipe(...steps)
|
|
60
|
+
- **Return shape**: sync returns a value; any async → Promise
|
|
61
|
+
- **Errors**: sync errors throw; async errors reject
|
|
62
|
+
- **Inputs**: value, Promise, thunk (() => value|Promise), or omit (first step gets `undefined`)
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// compose sync functions → string
|
|
66
|
+
const a = pipe(() => 'hi', (s) => s.trim(), (s) => s.toUpperCase());
|
|
67
|
+
|
|
68
|
+
// mix in async → Promise<string>
|
|
69
|
+
const b = pipe(() => 'hi', async (s) => s + '!', (s) => s + '?');
|
|
70
|
+
|
|
71
|
+
// no initial value
|
|
72
|
+
const c = pipe((x?: number) => (x ?? 1) + 1, (n) => n * 10); // 20
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### safePipe(options, ...steps)
|
|
76
|
+
If you prefer not to throw or reject, `safePipe` centralizes error handling. You get an optional `onError` callback and a `fallback` value (or thunk). On error, the pipeline returns the fallback (or `undefined` if you didn’t provide one).
|
|
77
|
+
|
|
78
|
+
- **Never throws**: errors are caught and routed to `onError`
|
|
79
|
+
- **Return shape preserved**: still sync-if-sync, async-if-async
|
|
80
|
+
- **Start forms**: options-first (no explicit initial value) or steps-only
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// Sync-only path
|
|
84
|
+
const r1 = safePipe({ onError: console.warn, fallback: 'X' }, () => 'ok', (s: string) => s.toUpperCase()); // 'OK'
|
|
85
|
+
|
|
86
|
+
// Sync error → fallback
|
|
87
|
+
const r2 = safePipe({ onError: console.warn, fallback: 'X' },
|
|
88
|
+
() => { throw new Error('boom'); },
|
|
89
|
+
(s: string) => s.toUpperCase()
|
|
90
|
+
); // 'X'
|
|
91
|
+
|
|
92
|
+
// Async path → Promise<string>
|
|
93
|
+
const r3 = await safePipe({ onError: console.warn, fallback: 'X' },
|
|
94
|
+
() => 'ok',
|
|
95
|
+
async (s: string) => s + '!',
|
|
96
|
+
(s: string) => s + '?'
|
|
97
|
+
); // 'ok!?'
|
|
98
|
+
|
|
99
|
+
// No fallback provided. On error, returns undefined (never throws).
|
|
100
|
+
const r5 = safePipe({ onError: console.warn },
|
|
101
|
+
() => { throw new Error('boom'); },
|
|
102
|
+
(s: string) => s.toUpperCase()
|
|
103
|
+
); // undefined
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Composing pipes
|
|
107
|
+
You can feed the output of one pipe (value or Promise) into another. Types keep up with you.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const p1 = pipe(() => 'hi', (s: string) => s.toUpperCase()); // string
|
|
111
|
+
const p2 = pipe(() => p1, (s) => s + '!'); // 'HI!'
|
|
112
|
+
|
|
113
|
+
const p3 = pipe(() => 'hi', async s => s + '!'); // Promise<string>
|
|
114
|
+
const p4 = pipe(() => p3, (s) => s + '?'); // Promise<string>
|
|
115
|
+
const fin = await p4; // 'hi!?'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Per-step helpers
|
|
119
|
+
Sometimes you want to guard or handle errors at a specific step without switching the whole pipeline to safe mode. Use these helpers as steps inside `pipe` or `safePipe`:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { pipe, tryStep, guard, serialForEach, serialReduce } from '@jesscss/awaitable-pipe';
|
|
123
|
+
|
|
124
|
+
// tryStep: catch at this step only, with optional onError and fallback
|
|
125
|
+
const step = tryStep((n: number) => {
|
|
126
|
+
if (n < 0) throw new Error('no negatives');
|
|
127
|
+
return n * 2;
|
|
128
|
+
}, {
|
|
129
|
+
onError: (err, n) => console.warn('bad number:', n, err),
|
|
130
|
+
fallback: 0 // could also be (err, n) => 0
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const out = pipe(() => 5, step); // 10
|
|
134
|
+
const out2 = pipe(() => -1, step); // 0
|
|
135
|
+
|
|
136
|
+
// onError can throw to rethrow the error (or a transformed error) for upstream handling
|
|
137
|
+
const stepWithRethrow = tryStep((input: string) => {
|
|
138
|
+
if (input === 'bad') throw new ReferenceError('not found');
|
|
139
|
+
return input.toUpperCase();
|
|
140
|
+
}, {
|
|
141
|
+
onError: (error, input) => {
|
|
142
|
+
// Conditionally rethrow based on error type
|
|
143
|
+
if (error instanceof ReferenceError) {
|
|
144
|
+
throw error; // Re-throw for upstream handling
|
|
145
|
+
}
|
|
146
|
+
// Otherwise, just log - fallback will be used
|
|
147
|
+
console.log('Handled error:', error);
|
|
148
|
+
},
|
|
149
|
+
fallback: 'default'
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const result1 = pipe(() => 'good', stepWithRethrow); // 'GOOD'
|
|
153
|
+
const result2 = pipe(() => 'bad', stepWithRethrow); // Throws ReferenceError
|
|
154
|
+
const result3 = pipe(() => 'other', stepWithRethrow); // 'default' (if step throws non-ReferenceError)
|
|
155
|
+
|
|
156
|
+
// Or use rethrow: true to always rethrow after onError
|
|
157
|
+
const alwaysRethrow = tryStep((n: number) => {
|
|
158
|
+
if (n < 0) throw new Error('no negatives');
|
|
159
|
+
return n * 2;
|
|
160
|
+
}, {
|
|
161
|
+
onError: (err, n) => console.warn('Error:', err),
|
|
162
|
+
rethrow: true // Always rethrow the original error after onError
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// guard: ensure a condition holds at this step (sync or async)
|
|
166
|
+
const positive = guard((n: number) => n > 0, (n) => new Error(`not positive: ${n}`));
|
|
167
|
+
const ok = pipe(() => 3, positive); // 3
|
|
168
|
+
// pipe(() => -2, positive) would throw: Error('not positive: -2')
|
|
169
|
+
|
|
170
|
+
// serialForEach: sync-first loop that promotes to async if a step returns a Promise
|
|
171
|
+
const items = [1, 2, 3];
|
|
172
|
+
await serialForEach(items, async (n, i) => {
|
|
173
|
+
if (i === 1) await new Promise(r => setTimeout(r, 10));
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// serialReduce: sync-first reduce that promotes to async on demand
|
|
177
|
+
const sum = await serialReduce(items, 0, async (acc, n, i) => {
|
|
178
|
+
if (i === 2) await Promise.resolve();
|
|
179
|
+
return acc + n;
|
|
180
|
+
});
|
|
181
|
+
// sum === 6
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
MIT
|
package/lib/helpers.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MaybePromise } from './utils.js';
|
|
2
|
+
export { serialForEach, serialReduce } from './utils.js';
|
|
3
|
+
export type StepErrorOptions<TIn, R> = {
|
|
4
|
+
onError?: (error: unknown, input: TIn) => void;
|
|
5
|
+
fallback?: R | ((error: unknown, input: TIn) => R);
|
|
6
|
+
rethrow?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare function tryStep<TIn, R>(fn: (input: TIn) => MaybePromise<R>, options: StepErrorOptions<TIn, R> & {
|
|
9
|
+
rethrow: true;
|
|
10
|
+
}): (input: TIn) => MaybePromise<R>;
|
|
11
|
+
export declare function tryStep<R>(fn: () => MaybePromise<R>, options: StepErrorOptions<undefined, R> & {
|
|
12
|
+
rethrow: true;
|
|
13
|
+
}): () => MaybePromise<R>;
|
|
14
|
+
export declare function tryStep<R>(fn: () => MaybePromise<R>, options?: StepErrorOptions<undefined, R | undefined>): () => MaybePromise<R | undefined>;
|
|
15
|
+
export declare function tryStep<TIn, R>(fn: (input: TIn) => MaybePromise<R>, options?: StepErrorOptions<TIn, R | undefined>): (input: TIn) => MaybePromise<R | undefined>;
|
|
16
|
+
export declare function guard<T>(predicate: (value: T) => MaybePromise<boolean>, errorFactory?: (value: T) => unknown): (value: T) => MaybePromise<T>;
|
package/lib/helpers.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { isThenable } from './utils.js';
|
|
2
|
+
export { serialForEach, serialReduce } from './utils.js';
|
|
3
|
+
// Type guard to help TypeScript narrow no-arg functions
|
|
4
|
+
function isNoArgFunction(fn) {
|
|
5
|
+
return fn.length === 0;
|
|
6
|
+
}
|
|
7
|
+
export function tryStep(fn, options = {}) {
|
|
8
|
+
// Check if fn takes no arguments (for first step)
|
|
9
|
+
if (isNoArgFunction(fn)) {
|
|
10
|
+
const noInputFn = fn; // Type guard narrows this to () => MaybePromise<R>
|
|
11
|
+
// Even though the function takes no args, the wrapper should accept input for pipe chaining
|
|
12
|
+
// and pass it to fallback/onError
|
|
13
|
+
const resultFn = (input) => {
|
|
14
|
+
try {
|
|
15
|
+
const out = noInputFn();
|
|
16
|
+
if (isThenable(out)) {
|
|
17
|
+
return out.catch((e) => {
|
|
18
|
+
try {
|
|
19
|
+
options.onError?.(e, input);
|
|
20
|
+
}
|
|
21
|
+
catch (onErrorThrown) {
|
|
22
|
+
// Swallow onError errors and continue to fallback
|
|
23
|
+
}
|
|
24
|
+
if (options.rethrow === true) {
|
|
25
|
+
return Promise.reject(e);
|
|
26
|
+
}
|
|
27
|
+
const fb = options.fallback;
|
|
28
|
+
return typeof fb === 'function' ? fb(e, input) : fb;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
try {
|
|
35
|
+
options.onError?.(e, input);
|
|
36
|
+
}
|
|
37
|
+
catch (onErrorThrown) {
|
|
38
|
+
// Swallow onError errors and continue to fallback
|
|
39
|
+
}
|
|
40
|
+
if (options.rethrow === true) {
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
const fb = options.fallback;
|
|
44
|
+
return typeof fb === 'function' ? fb(e, input) : fb;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
// Cast to match overload return types
|
|
48
|
+
// Overloads say () => MaybePromise<R>, but implementation accepts optional input for fallback/onError
|
|
49
|
+
// At runtime, JavaScript allows calling () => A with an argument, so this works
|
|
50
|
+
// TypeScript sees () => MaybePromise<R> to match pipe's first overload
|
|
51
|
+
if (options.rethrow === true) {
|
|
52
|
+
// Type assertion: TypeScript sees () => MaybePromise<R>, runtime accepts optional input
|
|
53
|
+
return resultFn;
|
|
54
|
+
}
|
|
55
|
+
return resultFn;
|
|
56
|
+
}
|
|
57
|
+
// Original implementation for functions that take input
|
|
58
|
+
const inputFn = fn;
|
|
59
|
+
return (input) => {
|
|
60
|
+
try {
|
|
61
|
+
const out = fn(input);
|
|
62
|
+
if (isThenable(out)) {
|
|
63
|
+
return out.catch((e) => {
|
|
64
|
+
try {
|
|
65
|
+
options.onError?.(e, input);
|
|
66
|
+
}
|
|
67
|
+
catch (onErrorThrown) {
|
|
68
|
+
// Swallow onError errors and continue to fallback
|
|
69
|
+
}
|
|
70
|
+
if (options.rethrow === true) {
|
|
71
|
+
return Promise.reject(e);
|
|
72
|
+
}
|
|
73
|
+
const fb = options.fallback;
|
|
74
|
+
return typeof fb === 'function' ? fb(e, input) : fb;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
try {
|
|
81
|
+
options.onError?.(e, input);
|
|
82
|
+
}
|
|
83
|
+
catch (onErrorThrown) {
|
|
84
|
+
// Swallow onError errors and continue to fallback
|
|
85
|
+
}
|
|
86
|
+
if (options.rethrow === true) {
|
|
87
|
+
throw e;
|
|
88
|
+
}
|
|
89
|
+
const fb = options.fallback;
|
|
90
|
+
return typeof fb === 'function' ? fb(e, input) : fb;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export function guard(predicate, errorFactory = (v) => new Error('ensure failed')) {
|
|
95
|
+
return (value) => {
|
|
96
|
+
const ok = predicate(value);
|
|
97
|
+
if (isThenable(ok)) {
|
|
98
|
+
return ok.then(passed => {
|
|
99
|
+
if (!passed) {
|
|
100
|
+
throw errorFactory(value);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (!ok) {
|
|
106
|
+
throw errorFactory(value);
|
|
107
|
+
}
|
|
108
|
+
return value;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { SafePipeOptions } from './pipe.js';
|
|
2
|
+
export { pipe, safePipe } from './pipe.js';
|
|
3
|
+
export { tryStep, guard, serialForEach, serialReduce } from './helpers.js';
|
|
4
|
+
export type { MaybePromise } from './utils.js';
|
|
5
|
+
export { isThenable, isPromise } from './utils.js';
|
package/lib/index.js
ADDED
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE3E,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
|
package/lib/pipe.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type Unwrap<T> = T extends Promise<infer U> ? U : T;
|
|
2
|
+
type RetOf<F> = F extends (a: any) => infer R ? R : never;
|
|
3
|
+
type ParamOf<F> = F extends (...args: infer P) => any ? (P extends [infer A, ...any[]] ? A : never) : never;
|
|
4
|
+
type Apply<In, F> = [
|
|
5
|
+
ParamOf<F>
|
|
6
|
+
] extends [never] ? RetOf<F> : In extends Promise<any> ? Promise<Awaited<RetOf<F>>> : RetOf<F>;
|
|
7
|
+
type PipeResult<In, Fns extends any[], Acc = In> = Fns extends [infer F, ...infer Rest] ? PipeResult<Apply<Acc, F>, Rest> : Acc;
|
|
8
|
+
export declare function pipe<A, R1>(fn1: () => A, fn2: (a: Unwrap<A>) => R1): PipeResult<undefined, [() => A, (a: Unwrap<A>) => R1]>;
|
|
9
|
+
export declare function pipe<A, R1>(fn1: (a?: A) => R1): PipeResult<undefined, [(a?: A) => R1]>;
|
|
10
|
+
export declare function pipe<A, R1, R2>(fn1: () => A, fn2: (a: Unwrap<A>) => R1, fn3: (b: Unwrap<R1>) => R2): PipeResult<undefined, [() => A, (a: Unwrap<A>) => R1, (b: Unwrap<R1>) => R2]>;
|
|
11
|
+
export declare function pipe<A, R1, R2, R3>(fn1: () => A, fn2: (a: Unwrap<A>) => R1, fn3: (b: Unwrap<R1>) => R2, fn4: (c: Unwrap<R2>) => R3): PipeResult<undefined, [() => A, (a: Unwrap<A>) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3]>;
|
|
12
|
+
export declare function pipe<A, R1, R2, R3, R4>(fn1: () => A, fn2: (a: Unwrap<A>) => R1, fn3: (b: Unwrap<R1>) => R2, fn4: (c: Unwrap<R2>) => R3, fn5: (d: Unwrap<R3>) => R4): PipeResult<undefined, [() => A, (a: Unwrap<A>) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3, (d: Unwrap<R3>) => R4]>;
|
|
13
|
+
export declare function pipe<A, R1, R2, R3, R4, R5>(fn1: () => A, fn2: (a: Unwrap<A>) => R1, fn3: (b: Unwrap<R1>) => R2, fn4: (c: Unwrap<R2>) => R3, fn5: (d: Unwrap<R3>) => R4, fn6: (e: Unwrap<R4>) => R5): PipeResult<undefined, [() => A, (a: Unwrap<A>) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3, (d: Unwrap<R3>) => R4, (e: Unwrap<R4>) => R5]>;
|
|
14
|
+
export declare function pipe<T, R1>(input: T | Promise<T> | (() => T) | (() => Promise<T>), fn1: (a: Unwrap<T>) => R1): PipeResult<T, [(a: Unwrap<T>) => R1]>;
|
|
15
|
+
export declare function pipe<T, R1, R2>(input: T | Promise<T> | (() => T) | (() => Promise<T>), fn1: (a: Unwrap<T>) => R1, fn2: (b: Unwrap<R1>) => R2): PipeResult<T, [(a: Unwrap<T>) => R1, (b: Unwrap<R1>) => R2]>;
|
|
16
|
+
export declare function pipe<T, R1, R2, R3>(input: T | Promise<T> | (() => T) | (() => Promise<T>), fn1: (a: Unwrap<T>) => R1, fn2: (b: Unwrap<R1>) => R2, fn3: (c: Unwrap<R2>) => R3): PipeResult<T, [(a: Unwrap<T>) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3]>;
|
|
17
|
+
export declare function pipe<T, R1, R2, R3, R4>(input: T | Promise<T> | (() => T) | (() => Promise<T>), fn1: (a: Unwrap<T>) => R1, fn2: (b: Unwrap<R1>) => R2, fn3: (c: Unwrap<R2>) => R3, fn4: (d: Unwrap<R3>) => R4): PipeResult<T, [(a: Unwrap<T>) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3, (d: Unwrap<R3>) => R4]>;
|
|
18
|
+
export declare function pipe<T, R1, R2, R3, R4, R5>(input: T | Promise<T> | (() => T) | (() => Promise<T>), fn1: (a: Unwrap<T>) => R1, fn2: (b: Unwrap<R1>) => R2, fn3: (c: Unwrap<R2>) => R3, fn4: (d: Unwrap<R3>) => R4, fn5: (e: Unwrap<R4>) => R5): PipeResult<T, [(a: Unwrap<T>) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3, (d: Unwrap<R3>) => R4, (e: Unwrap<R4>) => R5]>;
|
|
19
|
+
export declare function pipe<A>(input: A | Promise<A> | (() => A) | (() => Promise<A>), ...fns: Array<(a: A) => A | Promise<A>>): A | Promise<A>;
|
|
20
|
+
export type SafePipeOptions<R = unknown> = {
|
|
21
|
+
onError?: (error: unknown) => void;
|
|
22
|
+
fallback?: R | (() => R);
|
|
23
|
+
};
|
|
24
|
+
export declare function safePipe<A, R1>(options: SafePipeOptions<any>, fn1: (a?: A) => R1): PipeResult<undefined, [(a?: A) => R1]> extends Promise<any> ? Promise<R1 | undefined> : R1 | undefined;
|
|
25
|
+
export declare function safePipe<A, R1, R2>(options: SafePipeOptions<any>, fn1: (a?: A) => R1, fn2: (b: Unwrap<R1>) => R2): PipeResult<undefined, [(a?: A) => R1, (b: Unwrap<R1>) => R2]> extends Promise<any> ? Promise<R2 | undefined> : R2 | undefined;
|
|
26
|
+
export declare function safePipe<A, R1, R2, R3>(options: SafePipeOptions<any>, fn1: (a?: A) => R1, fn2: (b: Unwrap<R1>) => R2, fn3: (c: Unwrap<R2>) => R3): PipeResult<undefined, [(a?: A) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3]> extends Promise<any> ? Promise<R3 | undefined> : R3 | undefined;
|
|
27
|
+
export declare function safePipe<A, R1>(fn1: () => A, fn2: (a: A) => R1): PipeResult<undefined, [() => A, (a: A) => R1]> extends Promise<any> ? Promise<R1 | undefined> : R1 | undefined;
|
|
28
|
+
export declare function safePipe<A, R1, R2>(fn1: () => A, fn2: (a: A) => R1, fn3: (b: Unwrap<R1>) => R2): PipeResult<undefined, [() => A, (a: A) => R1, (b: Unwrap<R1>) => R2]> extends Promise<any> ? Promise<R2 | undefined> : R2 | undefined;
|
|
29
|
+
export declare function safePipe<A, R1, R2, R3>(fn1: () => A, fn2: (a: A) => R1, fn3: (b: Unwrap<R1>) => R2, fn4: (c: Unwrap<R2>) => R3): PipeResult<undefined, [() => A, (a: A) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3]> extends Promise<any> ? Promise<R3 | undefined> : R3 | undefined;
|
|
30
|
+
export declare function safePipe<A, R1, R2, R3, R4>(fn1: () => A, fn2: (a: A) => R1, fn3: (b: Unwrap<R1>) => R2, fn4: (c: Unwrap<R2>) => R3, fn5: (d: Unwrap<R3>) => R4): PipeResult<undefined, [() => A, (a: A) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3, (d: Unwrap<R3>) => R4]> extends Promise<any> ? Promise<R4 | undefined> : R4 | undefined;
|
|
31
|
+
export declare function safePipe<A, R1, R2, R3, R4, R5>(fn1: () => A, fn2: (a: A) => R1, fn3: (b: Unwrap<R1>) => R2, fn4: (c: Unwrap<R2>) => R3, fn5: (d: Unwrap<R3>) => R4, fn6: (e: Unwrap<R4>) => R5): PipeResult<undefined, [() => A, (a: A) => R1, (b: Unwrap<R1>) => R2, (c: Unwrap<R2>) => R3, (d: Unwrap<R3>) => R4, (e: Unwrap<R4>) => R5]> extends Promise<any> ? Promise<R5 | undefined> : R5 | undefined;
|
|
32
|
+
export {};
|
package/lib/pipe.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { isThenable } from './utils.js';
|
|
2
|
+
function runAsync(v, fns, startIndex) {
|
|
3
|
+
let p = Promise.resolve(v);
|
|
4
|
+
for (let i = startIndex; i < fns.length; i++) {
|
|
5
|
+
const fn = fns[i];
|
|
6
|
+
p = p.then(val => fn(val));
|
|
7
|
+
}
|
|
8
|
+
return p;
|
|
9
|
+
}
|
|
10
|
+
// Note: Intentionally omitting generic varargs overloads to improve contextual typing
|
|
11
|
+
export function pipe(...args) {
|
|
12
|
+
let input;
|
|
13
|
+
let fns;
|
|
14
|
+
if (args.length === 0)
|
|
15
|
+
return undefined;
|
|
16
|
+
const first = args[0];
|
|
17
|
+
const second = args[1];
|
|
18
|
+
const rest = args.slice(1);
|
|
19
|
+
// Disambiguation:
|
|
20
|
+
// - If first and second are both functions → treat as steps-only (no explicit input)
|
|
21
|
+
// - Else if more than one arg → treat first as input (value | Promise | thunk)
|
|
22
|
+
// - Else single function → steps-only
|
|
23
|
+
const stepsOnly = typeof first === 'function' && typeof second === 'function';
|
|
24
|
+
if (stepsOnly) {
|
|
25
|
+
input = undefined;
|
|
26
|
+
fns = args;
|
|
27
|
+
}
|
|
28
|
+
else if (args.length > 1) {
|
|
29
|
+
input = typeof first === 'function' ? first() : first;
|
|
30
|
+
fns = rest;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
input = undefined;
|
|
34
|
+
fns = [first];
|
|
35
|
+
}
|
|
36
|
+
if (isThenable(input)) {
|
|
37
|
+
return runAsync(input, fns, 0);
|
|
38
|
+
}
|
|
39
|
+
for (let i = 0; i < fns.length; i++) {
|
|
40
|
+
const fn = fns[i];
|
|
41
|
+
const out = fn(input);
|
|
42
|
+
if (isThenable(out)) {
|
|
43
|
+
return runAsync(out, fns, i + 1);
|
|
44
|
+
}
|
|
45
|
+
input = out;
|
|
46
|
+
}
|
|
47
|
+
return input;
|
|
48
|
+
}
|
|
49
|
+
function resolveFallback(fb) {
|
|
50
|
+
return typeof fb === 'function' ? fb() : fb;
|
|
51
|
+
}
|
|
52
|
+
function runAsyncSafe(v, fns, startIndex, opts) {
|
|
53
|
+
const { onError, fallback } = opts;
|
|
54
|
+
const callOnError = (e) => {
|
|
55
|
+
try {
|
|
56
|
+
onError?.(e);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Swallow errors from onError to keep guarantees: never throw
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
let p = Promise.resolve(v);
|
|
63
|
+
for (let i = startIndex; i < fns.length; i++) {
|
|
64
|
+
const fn = fns[i];
|
|
65
|
+
p = p.then(val => {
|
|
66
|
+
try {
|
|
67
|
+
return fn(val);
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
callOnError(e);
|
|
71
|
+
return resolveFallback(fallback);
|
|
72
|
+
}
|
|
73
|
+
}, e => {
|
|
74
|
+
callOnError(e);
|
|
75
|
+
return resolveFallback(fallback);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return p.catch(e => {
|
|
79
|
+
callOnError(e);
|
|
80
|
+
return resolveFallback(fallback);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export function safePipe(...args) {
|
|
84
|
+
let input;
|
|
85
|
+
let options;
|
|
86
|
+
let fns;
|
|
87
|
+
if (args.length === 0)
|
|
88
|
+
return undefined;
|
|
89
|
+
const first = args[0];
|
|
90
|
+
const second = args[1];
|
|
91
|
+
const looksLikeOptions = (x) => !!x && typeof x === 'object' && !Array.isArray(x);
|
|
92
|
+
// Special-case: options-first with no steps should return undefined
|
|
93
|
+
if (args.length === 1 && !!first && typeof first === 'object') {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
const bothFns = typeof first === 'function' && typeof second === 'function';
|
|
97
|
+
if (looksLikeOptions(first) || bothFns) {
|
|
98
|
+
// options-first or steps-only (no explicit input)
|
|
99
|
+
input = undefined;
|
|
100
|
+
options = looksLikeOptions(first) ? first : {};
|
|
101
|
+
fns = (looksLikeOptions(first) ? args.slice(1) : args);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
throw new TypeError('safePipe requires steps-only or options-first with steps');
|
|
105
|
+
}
|
|
106
|
+
input = undefined;
|
|
107
|
+
for (let i = 0; i < fns.length; i++) {
|
|
108
|
+
const fn = fns[i];
|
|
109
|
+
try {
|
|
110
|
+
const out = fn(input);
|
|
111
|
+
if (isThenable(out)) {
|
|
112
|
+
return runAsyncSafe(out, fns, i + 1, options);
|
|
113
|
+
}
|
|
114
|
+
input = out;
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
try {
|
|
118
|
+
options?.onError?.(e);
|
|
119
|
+
}
|
|
120
|
+
catch { }
|
|
121
|
+
return resolveFallback(options?.fallback);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return input;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=pipe.js.map
|
package/lib/pipe.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipe.js","sourceRoot":"","sources":["../src/pipe.ts"],"names":[],"mappings":"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"}
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
2
|
+
export declare function isThenable(x: unknown): x is Promise<unknown>;
|
|
3
|
+
export declare function isPromise<T = unknown>(x: unknown): x is Promise<T>;
|
|
4
|
+
/**
|
|
5
|
+
* Serial forEach over an array where the step may return a Promise.
|
|
6
|
+
* Runs synchronously until an async step is encountered, then switches to async for the remainder.
|
|
7
|
+
*/
|
|
8
|
+
export declare function serialForEach<T>(items: readonly T[], step: (item: T, index: number) => MaybePromise<void | undefined>): MaybePromise<void | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Serial reduce over an array where the step may return a Promise.
|
|
11
|
+
* Runs synchronously until an async step is encountered, then switches to async for the remainder.
|
|
12
|
+
*/
|
|
13
|
+
export declare function serialReduce<T, A>(items: readonly T[], seed: A, step: (acc: A, item: T, index: number) => MaybePromise<A>): MaybePromise<A>;
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function isThenable(x) {
|
|
2
|
+
return !!x && (typeof x === 'object' || typeof x === 'function') && typeof x.then === 'function';
|
|
3
|
+
}
|
|
4
|
+
export function isPromise(x) {
|
|
5
|
+
return !!x && typeof x === 'object' && typeof x.then === 'function' && typeof x.catch === 'function';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Serial forEach over an array where the step may return a Promise.
|
|
9
|
+
* Runs synchronously until an async step is encountered, then switches to async for the remainder.
|
|
10
|
+
*/
|
|
11
|
+
export function serialForEach(items, step) {
|
|
12
|
+
for (let i = 0; i < items.length; i++) {
|
|
13
|
+
const out = step(items[i], i);
|
|
14
|
+
if (isThenable(out)) {
|
|
15
|
+
return (async () => {
|
|
16
|
+
await out;
|
|
17
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
18
|
+
await step(items[j], j);
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
})();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Serial reduce over an array where the step may return a Promise.
|
|
28
|
+
* Runs synchronously until an async step is encountered, then switches to async for the remainder.
|
|
29
|
+
*/
|
|
30
|
+
export function serialReduce(items, seed, step) {
|
|
31
|
+
let acc = seed;
|
|
32
|
+
for (let i = 0; i < items.length; i++) {
|
|
33
|
+
const out = step(acc, items[i], i);
|
|
34
|
+
if (isThenable(out)) {
|
|
35
|
+
return (async () => {
|
|
36
|
+
acc = await out;
|
|
37
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
38
|
+
acc = await step(acc, items[j], j);
|
|
39
|
+
}
|
|
40
|
+
return acc;
|
|
41
|
+
})();
|
|
42
|
+
}
|
|
43
|
+
acc = out;
|
|
44
|
+
}
|
|
45
|
+
return acc;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=utils.js.map
|
package/lib/utils.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU,CAAC,CAAU;IACnC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,UAAU,CAAC,IAAI,OAAQ,CAAS,CAAC,IAAI,KAAK,UAAU,CAAC;AAC5G,CAAC;AAED,MAAM,UAAU,SAAS,CAAc,CAAU;IAC/C,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAQ,CAAS,CAAC,IAAI,KAAK,UAAU,IAAI,OAAQ,CAAS,CAAC,KAAK,KAAK,UAAU,CAAC;AACzH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAI,KAAmB,EAAE,IAAgE;IACpH,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC;QAC/B,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,IAAI,EAAE;gBACjB,MAAM,GAAG,CAAC;gBACV,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC1C,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC;gBAC3B,CAAC;gBACD,OAAO,SAAS,CAAC;YACnB,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAO,KAAmB,EAAE,IAAO,EAAE,IAAyD;IACxH,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,CAAC,CAAC;QACpC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACpB,OAAO,CAAC,KAAK,IAAI,EAAE;gBACjB,GAAG,GAAG,MAAM,GAAQ,CAAC;gBACrB,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC1C,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,CAAC,CAAM,CAAC;gBAC3C,CAAC;gBACD,OAAO,GAAG,CAAC;YACb,CAAC,CAAC,EAAE,CAAC;QACP,CAAC;QACD,GAAG,GAAG,GAAQ,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jesscss/awaitable-pipe",
|
|
3
|
+
"version": "2.0.0-alpha.1",
|
|
4
|
+
"description": "A tiny, strongly-typed pipe that stays sync when possible and becomes a Promise when needed. With an optional single-point error handler.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./lib/index.js",
|
|
11
|
+
"types": "./lib/index.d.ts",
|
|
12
|
+
"source": "./src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"./*": {
|
|
15
|
+
"types": "./lib/*.d.ts",
|
|
16
|
+
"import": "./lib/*.js",
|
|
17
|
+
"source": "./src/*.ts"
|
|
18
|
+
},
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"lib",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/matthew-dean/jess.git"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"pipe",
|
|
32
|
+
"functional",
|
|
33
|
+
"async",
|
|
34
|
+
"sync",
|
|
35
|
+
"typescript",
|
|
36
|
+
"error-handling"
|
|
37
|
+
],
|
|
38
|
+
"author": "Matthew Dean <matthew-dean@users.noreply.github.com>",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"sideEffects": false,
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"typescript": "~5.8.2"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.build.json",
|
|
49
|
+
"dev": "tsc -w -p tsconfig.build.json",
|
|
50
|
+
"test": "vitest run",
|
|
51
|
+
"test:types": "tsc -p tsconfig.json --noEmit",
|
|
52
|
+
"test:watch": "vitest"
|
|
53
|
+
}
|
|
54
|
+
}
|