@nlozgachev/pipelined 0.42.0 → 0.44.0
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/README.md +10 -10
- package/dist/InternalTypes-7o9-yrHq.d.ts +118 -0
- package/dist/InternalTypes-BL23H8Qr.d.mts +118 -0
- package/dist/{Task-BprUabHP.d.mts → Validation-DM2eh6wj.d.ts} +877 -113
- package/dist/{Task-Dt3ZMen6.d.ts → Validation-Dz70wtcG.d.mts} +877 -113
- package/dist/{chunk-GBB6LVLI.mjs → chunk-74JKKJ4R.mjs} +0 -4
- package/dist/{chunk-6VYLZTAM.mjs → chunk-LKTOK5IT.mjs} +157 -69
- package/dist/{chunk-OAP765G3.mjs → chunk-ND476266.mjs} +179 -134
- package/dist/{chunk-F6SBU7GB.mjs → chunk-X6XQX3OZ.mjs} +3 -3
- package/dist/composition.d.mts +45 -44
- package/dist/composition.d.ts +45 -44
- package/dist/composition.js +2 -2
- package/dist/composition.mjs +2 -2
- package/dist/core.d.mts +11 -813
- package/dist/core.d.ts +11 -813
- package/dist/core.js +176 -132
- package/dist/core.mjs +2 -2
- package/dist/index.d.mts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +332 -203
- package/dist/index.mjs +5 -7
- package/dist/types.d.mts +112 -17
- package/dist/types.d.ts +112 -17
- package/dist/types.js +2 -7
- package/dist/types.mjs +3 -5
- package/dist/utils.d.mts +345 -89
- package/dist/utils.d.ts +345 -89
- package/dist/utils.js +429 -107
- package/dist/utils.mjs +3 -3
- package/package.json +6 -6
- package/dist/Duration-BTeT9D-q.d.mts +0 -127
- package/dist/Duration-BTeT9D-q.d.ts +0 -127
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npm add @nlozgachev/pipelined
|
|
|
17
17
|
In mainstream TypeScript, code is often burdened by implicit control flow: unchecked exceptions,
|
|
18
18
|
manual null propagation, and unhandled asynchronous failures. `pipelined` turns these complex
|
|
19
19
|
runtime states into simple, transparent data structures that compose. By representing optionality as
|
|
20
|
-
`Maybe`, failures as `Result`, lazy asynchronous pipelines as `
|
|
20
|
+
`Maybe`, failures as `Result`, lazy asynchronous pipelines as `Task.Result`, and repeated stateful
|
|
21
21
|
interactions as `Op`, the library helps disentangle business logic from control mechanics.
|
|
22
22
|
|
|
23
23
|
## Documentation
|
|
@@ -55,18 +55,18 @@ Every step that sees `None` is skipped. The fallback runs once, at the end.
|
|
|
55
55
|
## Example: typed async errors
|
|
56
56
|
|
|
57
57
|
In JavaScript, asynchronous exceptions bypass the static type system, leaving unhandled rejections
|
|
58
|
-
as invisible runtime risks. `
|
|
58
|
+
as invisible runtime risks. `Task.Result<E, A>` represents fallible asynchronous computations as
|
|
59
59
|
lazy, infallible tasks that resolve to a typed `Result`. The error type is explicitly tracked in the
|
|
60
60
|
function signature, ensuring that failures are handled before compile time:
|
|
61
61
|
|
|
62
62
|
```ts
|
|
63
63
|
import { pipe } from "@nlozgachev/pipelined/composition";
|
|
64
|
-
import { Result,
|
|
64
|
+
import { Result, Task } from "@nlozgachev/pipelined/core";
|
|
65
65
|
|
|
66
66
|
type ApiError = { status: number; message: string };
|
|
67
67
|
|
|
68
|
-
const fetchUser = (id: string):
|
|
69
|
-
|
|
68
|
+
const fetchUser = (id: string): Task.Result<ApiError, User> =>
|
|
69
|
+
Task.Result.tryCatch(
|
|
70
70
|
(signal) =>
|
|
71
71
|
fetch(`/users/${id}`, { signal }).then((r) => {
|
|
72
72
|
if (!r.ok) throw { status: r.status, message: r.statusText };
|
|
@@ -75,8 +75,8 @@ const fetchUser = (id: string): TaskResult<ApiError, User> =>
|
|
|
75
75
|
(e) => e as ApiError,
|
|
76
76
|
);
|
|
77
77
|
|
|
78
|
-
const fetchPosts = (userId: string):
|
|
79
|
-
|
|
78
|
+
const fetchPosts = (userId: string): Task.Result<ApiError, Post[]> =>
|
|
79
|
+
Task.Result.tryCatch(
|
|
80
80
|
(signal) =>
|
|
81
81
|
fetch(`/users/${userId}/posts`, { signal }).then((r) => r.json()),
|
|
82
82
|
(e) => e as ApiError,
|
|
@@ -86,10 +86,10 @@ const fetchPosts = (userId: string): TaskResult<ApiError, Post[]> =>
|
|
|
86
86
|
const userWithPosts = (id: string) =>
|
|
87
87
|
pipe(
|
|
88
88
|
fetchUser(id),
|
|
89
|
-
|
|
89
|
+
Task.Result.chain((user) =>
|
|
90
90
|
pipe(
|
|
91
91
|
fetchPosts(user.id),
|
|
92
|
-
|
|
92
|
+
Task.Result.map((posts) => ({ ...user, posts })),
|
|
93
93
|
)
|
|
94
94
|
),
|
|
95
95
|
);
|
|
@@ -314,7 +314,7 @@ provides a strongly-typed, immutable two-element pair.
|
|
|
314
314
|
### Asynchronous operations
|
|
315
315
|
|
|
316
316
|
`Task` represents a lazy, infallible asynchronous computation. Fallible asynchronous workflows are
|
|
317
|
-
handled by `
|
|
317
|
+
handled by `Task.Result`, `Task.Maybe`, and `Task.Validation`. For managing stateful, recurring
|
|
318
318
|
asynchronous operations with complex scheduling, `Op` implements named concurrency strategies such
|
|
319
319
|
as `restartable`, `exclusive`, `debounced`, `throttled`, and `queue`, handling retries, timeouts,
|
|
320
320
|
and signal propagation automatically. `Deferred` represents a lightweight, infallible asynchronous
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Duration } from './types.js';
|
|
2
|
+
|
|
3
|
+
declare const _deferred: unique symbol;
|
|
4
|
+
/**
|
|
5
|
+
* A nominally typed, one-shot async value that supports `await` but enforces infallibility.
|
|
6
|
+
*
|
|
7
|
+
* Two design choices work together to make the guarantee structural rather than documentary:
|
|
8
|
+
*
|
|
9
|
+
* - The phantom `[_deferred]` symbol makes the type **nominal**: only values produced by
|
|
10
|
+
* `Deferred.fromPromise` satisfy it. A plain object `{ then: ... }` does not.
|
|
11
|
+
* - The single-parameter `.then()` **excludes rejection handlers** by construction. There is
|
|
12
|
+
* no second argument to pass, so chaining and `.catch()` are impossible.
|
|
13
|
+
*
|
|
14
|
+
* This makes `Deferred<A>` the natural return type for `Task<A>`, which is guaranteed to
|
|
15
|
+
* never reject.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const value = await Deferred.fromPromise(Promise.resolve(42));
|
|
20
|
+
* // value === 42
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
type Deferred<A> = {
|
|
24
|
+
readonly [_deferred]: A;
|
|
25
|
+
readonly then: (onfulfilled: (value: A) => unknown) => void;
|
|
26
|
+
};
|
|
27
|
+
declare namespace Deferred {
|
|
28
|
+
/**
|
|
29
|
+
* Wraps a `Promise` or `Deferred` into a `Deferred`, structurally excluding rejection handlers,
|
|
30
|
+
* `.catch()`, `.finally()`, and chainable `.then()`.
|
|
31
|
+
*
|
|
32
|
+
* **Precondition**: `p` must never reject. If `p` rejects, the returned `Deferred` will
|
|
33
|
+
* never resolve — `await`-ing it will hang indefinitely. Use `Task.Result.tryCatch` to
|
|
34
|
+
* handle operations that may fail before converting to a `Deferred`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const d = Deferred.fromPromise(Promise.resolve("hello"));
|
|
39
|
+
* const value = await d; // "hello"
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
const fromPromise: <A>(p: Thenable<A>) => Deferred<A>;
|
|
43
|
+
/**
|
|
44
|
+
* Converts a `Deferred` back into a `Promise`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const p = Deferred.toPromise(Deferred.fromPromise(Promise.resolve(42)));
|
|
49
|
+
* // p is Promise<42>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
const toPromise: <A>(d: Deferred<A>) => Promise<A>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Represents a promise-like object or a deferred value that can be resolved with `.then()`.
|
|
57
|
+
*/
|
|
58
|
+
type Thenable<A> = PromiseLike<A> | Deferred<A>;
|
|
59
|
+
/**
|
|
60
|
+
* Represents a value that can be either a synchronous value or a Thenable.
|
|
61
|
+
*/
|
|
62
|
+
type Awaitable<A> = A | Thenable<A>;
|
|
63
|
+
type NonEmptyArr<A> = readonly [A, ...A[]];
|
|
64
|
+
type WithKind<K extends string> = {
|
|
65
|
+
readonly kind: K;
|
|
66
|
+
};
|
|
67
|
+
type WithValue<T> = {
|
|
68
|
+
readonly value: T;
|
|
69
|
+
};
|
|
70
|
+
type WithError<T> = {
|
|
71
|
+
readonly error: T;
|
|
72
|
+
};
|
|
73
|
+
type WithErrors<T> = {
|
|
74
|
+
readonly errors: NonEmptyArr<T>;
|
|
75
|
+
};
|
|
76
|
+
type WithFirst<T> = {
|
|
77
|
+
readonly first: T;
|
|
78
|
+
};
|
|
79
|
+
type WithSecond<T> = {
|
|
80
|
+
readonly second: T;
|
|
81
|
+
};
|
|
82
|
+
type WithLog<T> = {
|
|
83
|
+
readonly log: ReadonlyArray<T>;
|
|
84
|
+
};
|
|
85
|
+
/** Retry policy for `Op.interpret`. */
|
|
86
|
+
type RetryOptions<E> = {
|
|
87
|
+
readonly attempts: number;
|
|
88
|
+
readonly backoff?: Duration | ((attempt: number) => Duration);
|
|
89
|
+
readonly when?: (error: E) => boolean;
|
|
90
|
+
};
|
|
91
|
+
/** Timeout policy for `Op.interpret`. Wraps the entire retry sequence. */
|
|
92
|
+
type TimeoutOptions<E> = {
|
|
93
|
+
readonly duration: Duration;
|
|
94
|
+
readonly onTimeout: () => E;
|
|
95
|
+
};
|
|
96
|
+
type WithTimeout<E> = {
|
|
97
|
+
readonly timeout?: TimeoutOptions<E>;
|
|
98
|
+
};
|
|
99
|
+
type WithDuration = {
|
|
100
|
+
readonly duration: Duration;
|
|
101
|
+
};
|
|
102
|
+
type WithN = {
|
|
103
|
+
readonly n: number;
|
|
104
|
+
};
|
|
105
|
+
type WithConcurrency = {
|
|
106
|
+
readonly concurrency?: number;
|
|
107
|
+
};
|
|
108
|
+
type WithSize = {
|
|
109
|
+
readonly size?: number;
|
|
110
|
+
};
|
|
111
|
+
type WithCooldown = {
|
|
112
|
+
readonly cooldown?: Duration;
|
|
113
|
+
};
|
|
114
|
+
type WithMinInterval = {
|
|
115
|
+
readonly minInterval?: Duration;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export { type Awaitable as A, Deferred as D, type NonEmptyArr as N, type RetryOptions as R, type Thenable as T, type WithConcurrency as W, type TimeoutOptions as a, type WithCooldown as b, type WithDuration as c, type WithError as d, type WithErrors as e, type WithFirst as f, type WithKind as g, type WithLog as h, type WithMinInterval as i, type WithN as j, type WithSecond as k, type WithSize as l, type WithTimeout as m, type WithValue as n };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Duration } from './types.mjs';
|
|
2
|
+
|
|
3
|
+
declare const _deferred: unique symbol;
|
|
4
|
+
/**
|
|
5
|
+
* A nominally typed, one-shot async value that supports `await` but enforces infallibility.
|
|
6
|
+
*
|
|
7
|
+
* Two design choices work together to make the guarantee structural rather than documentary:
|
|
8
|
+
*
|
|
9
|
+
* - The phantom `[_deferred]` symbol makes the type **nominal**: only values produced by
|
|
10
|
+
* `Deferred.fromPromise` satisfy it. A plain object `{ then: ... }` does not.
|
|
11
|
+
* - The single-parameter `.then()` **excludes rejection handlers** by construction. There is
|
|
12
|
+
* no second argument to pass, so chaining and `.catch()` are impossible.
|
|
13
|
+
*
|
|
14
|
+
* This makes `Deferred<A>` the natural return type for `Task<A>`, which is guaranteed to
|
|
15
|
+
* never reject.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const value = await Deferred.fromPromise(Promise.resolve(42));
|
|
20
|
+
* // value === 42
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
type Deferred<A> = {
|
|
24
|
+
readonly [_deferred]: A;
|
|
25
|
+
readonly then: (onfulfilled: (value: A) => unknown) => void;
|
|
26
|
+
};
|
|
27
|
+
declare namespace Deferred {
|
|
28
|
+
/**
|
|
29
|
+
* Wraps a `Promise` or `Deferred` into a `Deferred`, structurally excluding rejection handlers,
|
|
30
|
+
* `.catch()`, `.finally()`, and chainable `.then()`.
|
|
31
|
+
*
|
|
32
|
+
* **Precondition**: `p` must never reject. If `p` rejects, the returned `Deferred` will
|
|
33
|
+
* never resolve — `await`-ing it will hang indefinitely. Use `Task.Result.tryCatch` to
|
|
34
|
+
* handle operations that may fail before converting to a `Deferred`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const d = Deferred.fromPromise(Promise.resolve("hello"));
|
|
39
|
+
* const value = await d; // "hello"
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
const fromPromise: <A>(p: Thenable<A>) => Deferred<A>;
|
|
43
|
+
/**
|
|
44
|
+
* Converts a `Deferred` back into a `Promise`.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const p = Deferred.toPromise(Deferred.fromPromise(Promise.resolve(42)));
|
|
49
|
+
* // p is Promise<42>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
const toPromise: <A>(d: Deferred<A>) => Promise<A>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Represents a promise-like object or a deferred value that can be resolved with `.then()`.
|
|
57
|
+
*/
|
|
58
|
+
type Thenable<A> = PromiseLike<A> | Deferred<A>;
|
|
59
|
+
/**
|
|
60
|
+
* Represents a value that can be either a synchronous value or a Thenable.
|
|
61
|
+
*/
|
|
62
|
+
type Awaitable<A> = A | Thenable<A>;
|
|
63
|
+
type NonEmptyArr<A> = readonly [A, ...A[]];
|
|
64
|
+
type WithKind<K extends string> = {
|
|
65
|
+
readonly kind: K;
|
|
66
|
+
};
|
|
67
|
+
type WithValue<T> = {
|
|
68
|
+
readonly value: T;
|
|
69
|
+
};
|
|
70
|
+
type WithError<T> = {
|
|
71
|
+
readonly error: T;
|
|
72
|
+
};
|
|
73
|
+
type WithErrors<T> = {
|
|
74
|
+
readonly errors: NonEmptyArr<T>;
|
|
75
|
+
};
|
|
76
|
+
type WithFirst<T> = {
|
|
77
|
+
readonly first: T;
|
|
78
|
+
};
|
|
79
|
+
type WithSecond<T> = {
|
|
80
|
+
readonly second: T;
|
|
81
|
+
};
|
|
82
|
+
type WithLog<T> = {
|
|
83
|
+
readonly log: ReadonlyArray<T>;
|
|
84
|
+
};
|
|
85
|
+
/** Retry policy for `Op.interpret`. */
|
|
86
|
+
type RetryOptions<E> = {
|
|
87
|
+
readonly attempts: number;
|
|
88
|
+
readonly backoff?: Duration | ((attempt: number) => Duration);
|
|
89
|
+
readonly when?: (error: E) => boolean;
|
|
90
|
+
};
|
|
91
|
+
/** Timeout policy for `Op.interpret`. Wraps the entire retry sequence. */
|
|
92
|
+
type TimeoutOptions<E> = {
|
|
93
|
+
readonly duration: Duration;
|
|
94
|
+
readonly onTimeout: () => E;
|
|
95
|
+
};
|
|
96
|
+
type WithTimeout<E> = {
|
|
97
|
+
readonly timeout?: TimeoutOptions<E>;
|
|
98
|
+
};
|
|
99
|
+
type WithDuration = {
|
|
100
|
+
readonly duration: Duration;
|
|
101
|
+
};
|
|
102
|
+
type WithN = {
|
|
103
|
+
readonly n: number;
|
|
104
|
+
};
|
|
105
|
+
type WithConcurrency = {
|
|
106
|
+
readonly concurrency?: number;
|
|
107
|
+
};
|
|
108
|
+
type WithSize = {
|
|
109
|
+
readonly size?: number;
|
|
110
|
+
};
|
|
111
|
+
type WithCooldown = {
|
|
112
|
+
readonly cooldown?: Duration;
|
|
113
|
+
};
|
|
114
|
+
type WithMinInterval = {
|
|
115
|
+
readonly minInterval?: Duration;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export { type Awaitable as A, Deferred as D, type NonEmptyArr as N, type RetryOptions as R, type Thenable as T, type WithConcurrency as W, type TimeoutOptions as a, type WithCooldown as b, type WithDuration as c, type WithError as d, type WithErrors as e, type WithFirst as f, type WithKind as g, type WithLog as h, type WithMinInterval as i, type WithN as j, type WithSecond as k, type WithSize as l, type WithTimeout as m, type WithValue as n };
|