@nlozgachev/pipelined 0.32.0 → 0.34.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 +67 -37
- package/dist/{Task-CjYKLeTY.d.ts → Task-DXsuurnc.d.mts} +107 -58
- package/dist/{Task-CJZfcOkO.d.mts → Task-zAY4kSVB.d.ts} +107 -58
- package/dist/{chunk-L3NC44SN.mjs → chunk-5AWUAG7G.mjs} +449 -299
- package/dist/{chunk-NRF2FVPZ.mjs → chunk-AHEZFTMT.mjs} +64 -32
- package/dist/{chunk-TK5ZCGP2.mjs → chunk-DLBHVYII.mjs} +110 -47
- package/dist/{chunk-EHQFUWZW.mjs → chunk-IJFFWBKW.mjs} +223 -62
- package/dist/chunk-IPP4XFYH.mjs +0 -0
- package/dist/chunk-VWVPHDZO.mjs +29 -0
- package/dist/composition.d.mts +8 -7
- package/dist/composition.d.ts +8 -7
- package/dist/composition.js +64 -32
- package/dist/composition.mjs +1 -1
- package/dist/core.d.mts +227 -149
- package/dist/core.d.ts +227 -149
- package/dist/core.js +576 -345
- package/dist/core.mjs +3 -2
- package/dist/index.d.mts +3 -4
- package/dist/index.d.ts +3 -4
- package/dist/index.js +865 -446
- package/dist/index.mjs +10 -7
- package/dist/types.d.mts +104 -2
- package/dist/types.d.ts +104 -2
- package/dist/types.js +20 -0
- package/dist/types.mjs +6 -3
- package/dist/utils.d.mts +48 -5
- package/dist/utils.d.ts +48 -5
- package/dist/utils.js +353 -108
- package/dist/utils.mjs +3 -2
- package/package.json +9 -9
- package/dist/NonEmptyList-BlGFjor5.d.mts +0 -30
- package/dist/NonEmptyList-BlGFjor5.d.ts +0 -30
- package/dist/chunk-BYWKZLHM.mjs +0 -10
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# pipelined
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@nlozgachev/pipelined)
|
|
3
|
+
[](https://www.npmjs.com/package/@nlozgachev/pipelined)
|
|
4
|
+
[](https://github.com/nlozgachev/pipelined/actions/workflows/publish.yml)
|
|
5
|
+
[](https://app.codecov.io/github/nlozgachev/pipelined)
|
|
4
6
|
|
|
5
7
|
Opinionated functional abstractions for TypeScript.
|
|
6
8
|
|
|
@@ -12,11 +14,11 @@ npm add @nlozgachev/pipelined
|
|
|
12
14
|
|
|
13
15
|
## Possibly maybe
|
|
14
16
|
|
|
15
|
-
**pipelined** names every possible state and gives you operations that compose. `Maybe<A>` for
|
|
16
|
-
that may or may not be there. `Result<E, A>` for operations that succeed or fail with a typed
|
|
17
|
-
`TaskResult<E, A>` for async operations that keep failures as typed values and propagate
|
|
18
|
-
automatically. `Op<I, E, A>` for managing repeated async interactions — retry, timeout,
|
|
19
|
-
concurrency strategy in one place. And, of course, there is more than that.
|
|
17
|
+
**pipelined** names every possible state and gives you operations that compose. `Maybe<A>` for
|
|
18
|
+
values that may or may not be there. `Result<E, A>` for operations that succeed or fail with a typed
|
|
19
|
+
error. `TaskResult<E, A>` for async operations that keep failures as typed values and propagate
|
|
20
|
+
cancellation automatically. `Op<I, E, A>` for managing repeated async interactions — retry, timeout,
|
|
21
|
+
and concurrency strategy in one place. And, of course, there is more than that.
|
|
20
22
|
|
|
21
23
|
## Documentation
|
|
22
24
|
|
|
@@ -59,7 +61,7 @@ values — the error type is part of the signature, not a runtime surprise.
|
|
|
59
61
|
import { pipe } from "@nlozgachev/pipelined/composition";
|
|
60
62
|
import { Result, TaskResult } from "@nlozgachev/pipelined/core";
|
|
61
63
|
|
|
62
|
-
type ApiError = { status: number; message: string
|
|
64
|
+
type ApiError = { status: number; message: string };
|
|
63
65
|
|
|
64
66
|
const fetchUser = (id: string): TaskResult<ApiError, User> =>
|
|
65
67
|
TaskResult.tryCatch(
|
|
@@ -73,7 +75,8 @@ const fetchUser = (id: string): TaskResult<ApiError, User> =>
|
|
|
73
75
|
|
|
74
76
|
const fetchPosts = (userId: string): TaskResult<ApiError, Post[]> =>
|
|
75
77
|
TaskResult.tryCatch(
|
|
76
|
-
(signal) =>
|
|
78
|
+
(signal) =>
|
|
79
|
+
fetch(`/users/${userId}/posts`, { signal }).then((r) => r.json()),
|
|
77
80
|
(e) => e as ApiError,
|
|
78
81
|
);
|
|
79
82
|
|
|
@@ -115,13 +118,17 @@ import { pipe } from "@nlozgachev/pipelined/composition";
|
|
|
115
118
|
import { Maybe } from "@nlozgachev/pipelined/core";
|
|
116
119
|
import { Arr, Num, Rec, Str } from "@nlozgachev/pipelined/utils";
|
|
117
120
|
|
|
118
|
-
type RawItem = { name: string; price: string; category: string
|
|
119
|
-
type Item = { name: string; price: number; category: string
|
|
121
|
+
type RawItem = { name: string; price: string; category: string };
|
|
122
|
+
type Item = { name: string; price: number; category: string };
|
|
120
123
|
|
|
121
124
|
const normalise = (raw: RawItem): Maybe<Item> =>
|
|
122
125
|
pipe(
|
|
123
126
|
Num.parse(raw.price), // "9.99" → Some(9.99), "n/a" → None
|
|
124
|
-
Maybe.map((price) => ({
|
|
127
|
+
Maybe.map((price) => ({
|
|
128
|
+
name: Str.trim(raw.name),
|
|
129
|
+
price,
|
|
130
|
+
category: raw.category,
|
|
131
|
+
})),
|
|
125
132
|
);
|
|
126
133
|
|
|
127
134
|
const cheapestByCategory = (items: RawItem[]) =>
|
|
@@ -135,8 +142,9 @@ const cheapestByCategory = (items: RawItem[]) =>
|
|
|
135
142
|
```
|
|
136
143
|
|
|
137
144
|
`filterMap` applies a function that returns `Maybe` and collects only the `Some` results — one step
|
|
138
|
-
replaces a `map` followed by a `filter`. `Arr.head` returns `Maybe<Item>` rather than
|
|
139
|
-
so the absence is explicit in the type and the rest of the pipeline handles it
|
|
145
|
+
replaces a `map` followed by a `filter`. `Arr.head` returns `Maybe<Item>` rather than
|
|
146
|
+
`Item | undefined`, so the absence is explicit in the type and the rest of the pipeline handles it
|
|
147
|
+
the same way.
|
|
140
148
|
|
|
141
149
|
## Example: retry, timeout, and cancellation
|
|
142
150
|
|
|
@@ -144,8 +152,8 @@ A careful, production-minded attempt at "fetch with retry, timeout, and cancella
|
|
|
144
152
|
|
|
145
153
|
```ts
|
|
146
154
|
type UserResult =
|
|
147
|
-
| { ok: true; user: User
|
|
148
|
-
| { ok: false; error: "Timeout" | "NetworkError"
|
|
155
|
+
| { ok: true; user: User }
|
|
156
|
+
| { ok: false; error: "Timeout" | "NetworkError" };
|
|
149
157
|
|
|
150
158
|
async function fetchUser(
|
|
151
159
|
id: string,
|
|
@@ -177,8 +185,8 @@ async function fetchUser(
|
|
|
177
185
|
```
|
|
178
186
|
|
|
179
187
|
The signal is forwarded by hand. The timeout needs its own controller. Timed-out aborts are
|
|
180
|
-
distinguished from external cancellation by checking `signal?.aborted`. The retry is recursive
|
|
181
|
-
|
|
188
|
+
distinguished from external cancellation by checking `signal?.aborted`. The retry is recursive to
|
|
189
|
+
thread the attempt count.
|
|
182
190
|
|
|
183
191
|
With **pipelined**:
|
|
184
192
|
|
|
@@ -220,7 +228,7 @@ fetchUser.abort();
|
|
|
220
228
|
|
|
221
229
|
Real UIs make the same call many times — a search input fires on every keystroke, a submit button
|
|
222
230
|
gets clicked twice, a polling loop needs to stop when something newer starts. Each scenario has a
|
|
223
|
-
different answer to the same question:
|
|
231
|
+
different answer to the same question: *what happens to the previous call when a new one arrives?*
|
|
224
232
|
|
|
225
233
|
`Op` makes that question a one-word configuration choice.
|
|
226
234
|
|
|
@@ -231,7 +239,9 @@ import { Op } from "@nlozgachev/pipelined/core";
|
|
|
231
239
|
|
|
232
240
|
const searchOp = Op.create(
|
|
233
241
|
(signal) => (query: string) =>
|
|
234
|
-
fetch(`/search?q=${query}`, { signal }).then((r) =>
|
|
242
|
+
fetch(`/search?q=${query}`, { signal }).then((r) =>
|
|
243
|
+
r.json() as Promise<SearchResult[]>
|
|
244
|
+
),
|
|
235
245
|
(e) => new SearchError(e),
|
|
236
246
|
);
|
|
237
247
|
|
|
@@ -244,7 +254,7 @@ search.subscribe((state) => {
|
|
|
244
254
|
if (Op.isPending(state)) showSpinner();
|
|
245
255
|
if (Op.isRetrying(state)) showSpinner(`retrying… attempt ${state.attempt}`);
|
|
246
256
|
if (Op.isOk(state)) showResults(state.value);
|
|
247
|
-
if (Op.
|
|
257
|
+
if (Op.isErr(state)) showError(state.error);
|
|
248
258
|
});
|
|
249
259
|
|
|
250
260
|
input.addEventListener("input", (e) => search.run(e.currentTarget.value));
|
|
@@ -255,7 +265,9 @@ input.addEventListener("input", (e) => search.run(e.currentTarget.value));
|
|
|
255
265
|
```ts
|
|
256
266
|
const submitOp = Op.create(
|
|
257
267
|
(signal) => (data: FormData) =>
|
|
258
|
-
fetch("/orders", { method: "POST", body: data, signal }).then((r) =>
|
|
268
|
+
fetch("/orders", { method: "POST", body: data, signal }).then((r) =>
|
|
269
|
+
r.json()
|
|
270
|
+
),
|
|
259
271
|
(e) => new ApiError(e),
|
|
260
272
|
);
|
|
261
273
|
|
|
@@ -266,7 +278,7 @@ const submit = Op.interpret(submitOp, {
|
|
|
266
278
|
submit.subscribe((state) => {
|
|
267
279
|
submitButton.disabled = Op.isPending(state);
|
|
268
280
|
if (Op.isOk(state)) showConfirmation(state.value);
|
|
269
|
-
if (Op.
|
|
281
|
+
if (Op.isErr(state)) showError(state.error);
|
|
270
282
|
});
|
|
271
283
|
|
|
272
284
|
form.addEventListener("submit", (e) => {
|
|
@@ -275,32 +287,48 @@ form.addEventListener("submit", (e) => {
|
|
|
275
287
|
});
|
|
276
288
|
```
|
|
277
289
|
|
|
278
|
-
`restartable`, `exclusive`, `debounced`, `throttled`, `queue`, `buffered`, `concurrent`, `keyed`,
|
|
290
|
+
`restartable`, `exclusive`, `debounced`, `throttled`, `queue`, `buffered`, `concurrent`, `keyed`,
|
|
291
|
+
`once` — each strategy is a complete, tested answer to one concurrency scenario. Swap the word, keep
|
|
292
|
+
the rest of the code.
|
|
279
293
|
|
|
280
294
|
## What's included?
|
|
281
295
|
|
|
282
|
-
The library covers the states you encounter in real applications: values that may be absent,
|
|
296
|
+
The library covers the states you encounter in real applications: values that may be absent,
|
|
297
|
+
operations that accumulate multiple errors, data that moves through
|
|
298
|
+
`NotAsked >> Loading >> ( Success | Failure )`, async interactions with concurrency policies, nested
|
|
299
|
+
immutable updates, and computations that share a common environment. Every type follows the same
|
|
300
|
+
conventions — `map`, `chain`, `match`, `getOrElse` — so moving between them feels familiar.
|
|
283
301
|
|
|
284
302
|
### pipelined/core
|
|
285
303
|
|
|
286
304
|
- **`Maybe<A>`** — a value that may not exist; propagates absence without null checks.
|
|
287
305
|
- **`Result<E, A>`** — an operation that succeeds or fails with a typed error.
|
|
288
|
-
- **`Validation<E, A>`** — like `Result`, but accumulates every failure instead of stopping at the
|
|
306
|
+
- **`Validation<E, A>`** — like `Result`, but accumulates every failure instead of stopping at the
|
|
307
|
+
first.
|
|
289
308
|
- **`Task<A>`** — a lazy, infallible async operation; nothing runs until called.
|
|
290
309
|
- **`TaskResult<E, A>`** — a lazy async operation that can fail with a typed error.
|
|
291
310
|
- **`TaskMaybe<A>`** — a lazy async operation that may produce nothing.
|
|
292
311
|
- **`TaskValidation<E, A>`** — a lazy async operation that accumulates validation errors.
|
|
293
|
-
- **`Op<I, E, A>`** — a managed async operation with a named concurrency strategy: `restartable`,
|
|
294
|
-
|
|
312
|
+
- **`Op<I, E, A>`** — a managed async operation with a named concurrency strategy: `restartable`,
|
|
313
|
+
`exclusive`, `debounced`, `throttled`, `queue`, `buffered`, `concurrent`, `keyed`, or `once`.
|
|
314
|
+
Handles retry, timeout, cancellation, and state in one place.
|
|
315
|
+
- **`RemoteData<E, A>`** — the four states of a data fetch: `NotAsked`, `Loading`, `Failure`,
|
|
316
|
+
`Success`.
|
|
295
317
|
- **`These<A, B>`** — an inclusive OR: holds a first value, a second, or both at once.
|
|
296
|
-
- **`Lens<S, A>`** — focus on a required field in a nested structure. Read, set, and modify
|
|
318
|
+
- **`Lens<S, A>`** — focus on a required field in a nested structure. Read, set, and modify
|
|
319
|
+
immutably.
|
|
297
320
|
- **`Optional<S, A>`** — like `Lens`, but the target may be absent (nullable fields, array indices).
|
|
298
|
-
- **`Reader<R, A>`** — a computation that depends on an environment `R`, supplied once at the
|
|
299
|
-
|
|
300
|
-
- **`
|
|
321
|
+
- **`Reader<R, A>`** — a computation that depends on an environment `R`, supplied once at the
|
|
322
|
+
boundary.
|
|
323
|
+
- **`State<S, A>`** — a computation that reads and updates a state value, threaded explicitly
|
|
324
|
+
through the chain.
|
|
325
|
+
- **`Logged<W, A>`** — a computation that accumulates a log alongside its value; no console output,
|
|
326
|
+
just data.
|
|
301
327
|
- **`Predicate<A>`** — a typed boolean function, composable with `and`, `or`, `not`, and `using`.
|
|
302
|
-
- **`Refinement<A, B>`** — a type predicate that narrows `A` to `B` at runtime; composes with
|
|
303
|
-
|
|
328
|
+
- **`Refinement<A, B>`** — a type predicate that narrows `A` to `B` at runtime; composes with
|
|
329
|
+
`Predicate`.
|
|
330
|
+
- **`Resource<E, A>`** — an acquire/release pair for safe resource management in `TaskResult`
|
|
331
|
+
pipelines.
|
|
304
332
|
- **`Deferred<A>`** — an infallible async value: a thenable that always resolves, never rejects.
|
|
305
333
|
- **`Tuple<A, B>`** — a typed pair with `first`, `second`, `map`, `swap`, and `fold`.
|
|
306
334
|
|
|
@@ -311,14 +339,16 @@ Everyday utilities for built-in JS types.
|
|
|
311
339
|
- **`Arr`** — array utilities, data-last, returning `Maybe` instead of `undefined`.
|
|
312
340
|
- **`Rec`** — record/object utilities, data-last, with `Maybe`-returning key lookup.
|
|
313
341
|
- **`Dict`** — `ReadonlyMap<K, V>` utilities: `lookup`, `groupBy`, `upsert`, set operations.
|
|
314
|
-
- **`Uniq`** — `ReadonlySet<A>` utilities: `insert`, `remove`, `union`, `intersection`,
|
|
342
|
+
- **`Uniq`** — `ReadonlySet<A>` utilities: `insert`, `remove`, `union`, `intersection`,
|
|
343
|
+
`difference`.
|
|
315
344
|
- **`Num`** — number utilities: `range`, `clamp`, `between`, safe `parse`, and curried arithmetic.
|
|
316
|
-
- **`Str`** — string utilities: `split`, `trim`, `words`, `lines`, and safe `parse.int` /
|
|
345
|
+
- **`Str`** — string utilities: `split`, `trim`, `words`, `lines`, and safe `parse.int` /
|
|
346
|
+
`parse.float`.
|
|
317
347
|
|
|
318
348
|
Every utility is benchmarked against its native equivalent. The data-last currying adds a function
|
|
319
349
|
call; that is the expected cost of composability. Operations that exceeded a reasonable overhead
|
|
320
|
-
have custom implementations that in several cases run faster than the native method they replace.
|
|
321
|
-
[benchmarks page](https://pipelined.lozgachev.dev/appendix/benchmarks) for the methodology.
|
|
350
|
+
have custom implementations that in several cases run faster than the native method they replace.
|
|
351
|
+
See the [benchmarks page](https://pipelined.lozgachev.dev/appendix/benchmarks) for the methodology.
|
|
322
352
|
|
|
323
353
|
### pipelined/types
|
|
324
354
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NonEmptyList, Duration } from './types.mjs';
|
|
2
2
|
|
|
3
3
|
type WithKind<K extends string> = {
|
|
4
4
|
readonly kind: K;
|
|
@@ -24,19 +24,19 @@ type WithLog<T> = {
|
|
|
24
24
|
/** Retry policy for `Op.interpret`. */
|
|
25
25
|
type RetryOptions<E> = {
|
|
26
26
|
readonly attempts: number;
|
|
27
|
-
readonly backoff?:
|
|
27
|
+
readonly backoff?: Duration | ((attempt: number) => Duration);
|
|
28
28
|
readonly when?: (error: E) => boolean;
|
|
29
29
|
};
|
|
30
30
|
/** Timeout policy for `Op.interpret`. Wraps the entire retry sequence. */
|
|
31
31
|
type TimeoutOptions<E> = {
|
|
32
|
-
readonly
|
|
32
|
+
readonly duration: Duration;
|
|
33
33
|
readonly onTimeout: () => E;
|
|
34
34
|
};
|
|
35
35
|
type WithTimeout<E> = {
|
|
36
36
|
readonly timeout?: TimeoutOptions<E>;
|
|
37
37
|
};
|
|
38
|
-
type
|
|
39
|
-
readonly
|
|
38
|
+
type WithDuration = {
|
|
39
|
+
readonly duration: Duration;
|
|
40
40
|
};
|
|
41
41
|
type WithN = {
|
|
42
42
|
readonly n: number;
|
|
@@ -48,22 +48,22 @@ type WithSize = {
|
|
|
48
48
|
readonly size?: number;
|
|
49
49
|
};
|
|
50
50
|
type WithCooldown = {
|
|
51
|
-
readonly cooldown?:
|
|
51
|
+
readonly cooldown?: Duration;
|
|
52
52
|
};
|
|
53
53
|
type WithMinInterval = {
|
|
54
|
-
readonly minInterval?:
|
|
54
|
+
readonly minInterval?: Duration;
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
type Ok<A> = WithKind<"Ok"> & WithValue<A>;
|
|
58
|
-
type
|
|
58
|
+
type Err<E> = WithKind<"Err"> & WithError<E>;
|
|
59
59
|
/**
|
|
60
|
-
* Result represents a value that can be one of two types: a success (Ok) or a failure (
|
|
60
|
+
* Result represents a value that can be one of two types: a success (Ok) or a failure (Err).
|
|
61
61
|
* Use Result when an operation can fail with a meaningful error value.
|
|
62
62
|
*
|
|
63
63
|
* @example
|
|
64
64
|
* ```ts
|
|
65
65
|
* const divide = (a: number, b: number): Result<string, number> =>
|
|
66
|
-
* b === 0 ? Result.
|
|
66
|
+
* b === 0 ? Result.err("Division by zero") : Result.ok(a / b);
|
|
67
67
|
*
|
|
68
68
|
* pipe(
|
|
69
69
|
* divide(10, 2),
|
|
@@ -72,7 +72,7 @@ type Error<E> = WithKind<"Error"> & WithError<E>;
|
|
|
72
72
|
* ); // 10
|
|
73
73
|
* ```
|
|
74
74
|
*/
|
|
75
|
-
type Result<E, A> = Ok<A> |
|
|
75
|
+
type Result<E, A> = Ok<A> | Err<E>;
|
|
76
76
|
declare namespace Result {
|
|
77
77
|
/**
|
|
78
78
|
* Creates a successful Result with the given value.
|
|
@@ -81,17 +81,17 @@ declare namespace Result {
|
|
|
81
81
|
/**
|
|
82
82
|
* Creates a failed Result with the given error.
|
|
83
83
|
*/
|
|
84
|
-
const
|
|
84
|
+
const err: <E>(e: E) => Err<E>;
|
|
85
85
|
/**
|
|
86
|
-
* Type guard that checks if
|
|
86
|
+
* Type guard that checks if a Result is Ok.
|
|
87
87
|
*/
|
|
88
88
|
const isOk: <E, A>(data: Result<E, A>) => data is Ok<A>;
|
|
89
89
|
/**
|
|
90
|
-
* Type guard that checks if
|
|
90
|
+
* Type guard that checks if a Result is Err.
|
|
91
91
|
*/
|
|
92
|
-
const
|
|
92
|
+
const isErr: <E, A>(data: Result<E, A>) => data is Err<E>;
|
|
93
93
|
/**
|
|
94
|
-
* Creates
|
|
94
|
+
* Creates a Result from a function that may throw.
|
|
95
95
|
* Catches any errors and transforms them using the onError function.
|
|
96
96
|
*
|
|
97
97
|
* @example
|
|
@@ -105,21 +105,21 @@ declare namespace Result {
|
|
|
105
105
|
*/
|
|
106
106
|
const tryCatch: <E, A>(f: () => A, onError: (e: unknown) => E) => Result<E, A>;
|
|
107
107
|
/**
|
|
108
|
-
* Transforms the success value inside
|
|
108
|
+
* Transforms the success value inside a Result.
|
|
109
109
|
*
|
|
110
110
|
* @example
|
|
111
111
|
* ```ts
|
|
112
112
|
* pipe(Result.ok(5), Result.map(n => n * 2)); // Ok(10)
|
|
113
|
-
* pipe(Result.
|
|
113
|
+
* pipe(Result.err("error"), Result.map(n => n * 2)); // Err("error")
|
|
114
114
|
* ```
|
|
115
115
|
*/
|
|
116
116
|
const map: <E, A, B>(f: (a: A) => B) => (data: Result<E, A>) => Result<E, B>;
|
|
117
117
|
/**
|
|
118
|
-
* Transforms the error value inside
|
|
118
|
+
* Transforms the error value inside a Result.
|
|
119
119
|
*
|
|
120
120
|
* @example
|
|
121
121
|
* ```ts
|
|
122
|
-
* pipe(Result.
|
|
122
|
+
* pipe(Result.err("oops"), Result.mapError(e => e.toUpperCase())); // Err("OOPS")
|
|
123
123
|
* ```
|
|
124
124
|
*/
|
|
125
125
|
const mapError: <E, F, A>(f: (e: E) => F) => (data: Result<E, A>) => Result<F, A>;
|
|
@@ -130,15 +130,15 @@ declare namespace Result {
|
|
|
130
130
|
* @example
|
|
131
131
|
* ```ts
|
|
132
132
|
* const validatePositive = (n: number): Result<string, number> =>
|
|
133
|
-
* n > 0 ? Result.ok(n) : Result.
|
|
133
|
+
* n > 0 ? Result.ok(n) : Result.err("Must be positive");
|
|
134
134
|
*
|
|
135
135
|
* pipe(Result.ok(5), Result.chain(validatePositive)); // Ok(5)
|
|
136
|
-
* pipe(Result.ok(-1), Result.chain(validatePositive)); //
|
|
136
|
+
* pipe(Result.ok(-1), Result.chain(validatePositive)); // Err("Must be positive")
|
|
137
137
|
* ```
|
|
138
138
|
*/
|
|
139
139
|
const chain: <E, A, B>(f: (a: A) => Result<E, B>) => (data: Result<E, A>) => Result<E, B>;
|
|
140
140
|
/**
|
|
141
|
-
* Extracts the value from
|
|
141
|
+
* Extracts the value from a Result by providing handlers for both cases.
|
|
142
142
|
*
|
|
143
143
|
* @example
|
|
144
144
|
* ```ts
|
|
@@ -178,8 +178,8 @@ declare namespace Result {
|
|
|
178
178
|
* @example
|
|
179
179
|
* ```ts
|
|
180
180
|
* pipe(Result.ok(5), Result.getOrElse(() => 0)); // 5
|
|
181
|
-
* pipe(Result.
|
|
182
|
-
* pipe(Result.
|
|
181
|
+
* pipe(Result.err("error"), Result.getOrElse(() => 0)); // 0
|
|
182
|
+
* pipe(Result.err("error"), Result.getOrElse(() => null)); // null — typed as number | null
|
|
183
183
|
* ```
|
|
184
184
|
*/
|
|
185
185
|
const getOrElse: <E, A, B>(defaultValue: () => B) => (data: Result<E, A>) => A | B;
|
|
@@ -204,7 +204,7 @@ declare namespace Result {
|
|
|
204
204
|
* @example
|
|
205
205
|
* ```ts
|
|
206
206
|
* pipe(
|
|
207
|
-
* Result.
|
|
207
|
+
* Result.err("not found"),
|
|
208
208
|
* Result.tapError(e => console.error("validation failed:", e)),
|
|
209
209
|
* Result.chain(save),
|
|
210
210
|
* )
|
|
@@ -218,11 +218,49 @@ declare namespace Result {
|
|
|
218
218
|
* @example
|
|
219
219
|
* ```ts
|
|
220
220
|
* pipe(5, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); // Ok(5)
|
|
221
|
-
* pipe(-1, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); //
|
|
222
|
-
* pipe("", Result.fromPredicate(s => s.length > 0, () => "empty string")); //
|
|
221
|
+
* pipe(-1, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); // Err("-1 is not positive")
|
|
222
|
+
* pipe("", Result.fromPredicate(s => s.length > 0, () => "empty string")); // Err("empty string")
|
|
223
223
|
* ```
|
|
224
224
|
*/
|
|
225
225
|
const fromPredicate: <E, A>(pred: (a: A) => boolean, onFalse: (a: A) => E) => (a: A) => Result<E, A>;
|
|
226
|
+
/**
|
|
227
|
+
* Creates a Result from a nullable value.
|
|
228
|
+
* Returns Ok if the value is not null or undefined, error from onNull otherwise.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* pipe(null, Result.fromNullable(() => "is null")); // Err("is null")
|
|
233
|
+
* pipe(42, Result.fromNullable(() => "is null")); // Ok(42)
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
const fromNullable: <E>(onNull: () => E) => <A>(value: A | null | undefined) => Result<E, A>;
|
|
237
|
+
/**
|
|
238
|
+
* Creates a Result from a Maybe.
|
|
239
|
+
* Some becomes Ok, None becomes error from onNone.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* pipe(Maybe.none(), Result.fromMaybe(() => "is none")); // Err("is none")
|
|
244
|
+
* pipe(Maybe.some(42), Result.fromMaybe(() => "is none")); // Ok(42)
|
|
245
|
+
* ```
|
|
246
|
+
*/
|
|
247
|
+
const fromMaybe: <E>(onNone: () => E) => <A>(maybe: Maybe<A>) => Result<E, A>;
|
|
248
|
+
/**
|
|
249
|
+
* Wraps a throwing function of any arguments, returning a new function
|
|
250
|
+
* that catches errors and returns a Result.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts
|
|
254
|
+
* const safeParse = Result.fromThrowable(
|
|
255
|
+
* (s: string) => JSON.parse(s),
|
|
256
|
+
* (e) => new Error(`Parse error: ${e}`)
|
|
257
|
+
* );
|
|
258
|
+
*
|
|
259
|
+
* safeParse('{"a":1}'); // Ok({ a: 1 })
|
|
260
|
+
* safeParse('invalid'); // Err(Error)
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
const fromThrowable: <Args extends readonly unknown[], A, E>(f: (...args: Args) => A, onError: (e: unknown) => E) => (...args: Args) => Result<E, A>;
|
|
226
264
|
/**
|
|
227
265
|
* Recovers from an error by providing a fallback Result.
|
|
228
266
|
* The fallback can produce a different success type, widening the result to `Result<E, A | B>`.
|
|
@@ -235,25 +273,25 @@ declare namespace Result {
|
|
|
235
273
|
* @example
|
|
236
274
|
* ```ts
|
|
237
275
|
* pipe(
|
|
238
|
-
* Result.
|
|
276
|
+
* Result.err(new Error("not found")),
|
|
239
277
|
* Result.recoverUnless(e => e.message === "fatal", () => Result.ok(0))
|
|
240
278
|
* ); // Ok(0)
|
|
241
279
|
* ```
|
|
242
280
|
*/
|
|
243
281
|
const recoverUnless: <E, A, B>(isBlocked: (e: E) => boolean, fallback: () => Result<E, B>) => (data: Result<E, A>) => Result<E, A | B>;
|
|
244
282
|
/**
|
|
245
|
-
* Converts a Result to
|
|
283
|
+
* Converts a Result to a Maybe.
|
|
246
284
|
* Ok becomes Some, Err becomes None (the error is discarded).
|
|
247
285
|
*
|
|
248
286
|
* @example
|
|
249
287
|
* ```ts
|
|
250
288
|
* Result.toMaybe(Result.ok(42)); // Some(42)
|
|
251
|
-
* Result.toMaybe(Result.
|
|
289
|
+
* Result.toMaybe(Result.err("oops")); // None
|
|
252
290
|
* ```
|
|
253
291
|
*/
|
|
254
292
|
const toMaybe: <E, A>(data: Result<E, A>) => Maybe<A>;
|
|
255
293
|
/**
|
|
256
|
-
* Applies a function wrapped in
|
|
294
|
+
* Applies a function wrapped in a Result to a value wrapped in a Result.
|
|
257
295
|
*
|
|
258
296
|
* @example
|
|
259
297
|
* ```ts
|
|
@@ -276,14 +314,13 @@ type None = WithKind<"None">;
|
|
|
276
314
|
*
|
|
277
315
|
* @example
|
|
278
316
|
* ```ts
|
|
279
|
-
* const
|
|
280
|
-
* users.has(id) ? Maybe.some(users.get(id)!) : Maybe.none();
|
|
317
|
+
* const user = { name: "Alice", email: Maybe.some("alice@example.com") };
|
|
281
318
|
*
|
|
282
319
|
* pipe(
|
|
283
|
-
*
|
|
284
|
-
* Maybe.map(
|
|
285
|
-
* Maybe.getOrElse(() => "
|
|
286
|
-
* );
|
|
320
|
+
* user.email,
|
|
321
|
+
* Maybe.map(email => email.toUpperCase()),
|
|
322
|
+
* Maybe.getOrElse(() => "NO EMAIL")
|
|
323
|
+
* ); // "ALICE@EXAMPLE.COM"
|
|
287
324
|
* ```
|
|
288
325
|
*/
|
|
289
326
|
type Maybe<T> = Some<T> | None;
|
|
@@ -338,7 +375,7 @@ declare namespace Maybe {
|
|
|
338
375
|
*/
|
|
339
376
|
const fromPredicate: <A>(pred: (a: A) => boolean) => (a: A) => Maybe<A>;
|
|
340
377
|
/**
|
|
341
|
-
* Converts
|
|
378
|
+
* Converts a Maybe to a Result.
|
|
342
379
|
* Some becomes Ok, None becomes Err with the provided error.
|
|
343
380
|
*
|
|
344
381
|
* @example
|
|
@@ -356,13 +393,13 @@ declare namespace Maybe {
|
|
|
356
393
|
*/
|
|
357
394
|
const toResult: <E>(onNone: () => E) => <A>(data: Maybe<A>) => Result<E, A>;
|
|
358
395
|
/**
|
|
359
|
-
* Creates
|
|
396
|
+
* Creates a Maybe from a Result.
|
|
360
397
|
* Ok becomes Some, Err becomes None (the error is discarded).
|
|
361
398
|
*
|
|
362
399
|
* @example
|
|
363
400
|
* ```ts
|
|
364
401
|
* Maybe.fromResult(Result.ok(42)); // Some(42)
|
|
365
|
-
* Maybe.fromResult(Result.
|
|
402
|
+
* Maybe.fromResult(Result.err("oops")); // None
|
|
366
403
|
* ```
|
|
367
404
|
*/
|
|
368
405
|
const fromResult: <E, A>(data: Result<E, A>) => Maybe<A>;
|
|
@@ -426,7 +463,7 @@ declare namespace Maybe {
|
|
|
426
463
|
some: (a: A) => B;
|
|
427
464
|
}) => (data: Maybe<A>) => B;
|
|
428
465
|
/**
|
|
429
|
-
* Returns the value inside
|
|
466
|
+
* Returns the value inside a Maybe, or a default value if None.
|
|
430
467
|
* The default is a thunk `() => B` — evaluated only when the Maybe is None.
|
|
431
468
|
* The default can be a different type, widening the result to `A | B`.
|
|
432
469
|
*
|
|
@@ -844,37 +881,37 @@ declare namespace Task {
|
|
|
844
881
|
*/
|
|
845
882
|
const all: <T extends readonly Task<unknown>[]>(tasks: T) => Task<{ [K in keyof T]: T[K] extends Task<infer A> ? A : never; }>;
|
|
846
883
|
/**
|
|
847
|
-
* Delays the execution of a Task by the specified
|
|
884
|
+
* Delays the execution of a Task by the specified duration.
|
|
848
885
|
* Useful for debouncing or rate limiting.
|
|
849
886
|
*
|
|
850
887
|
* @example
|
|
851
888
|
* ```ts
|
|
852
889
|
* pipe(
|
|
853
890
|
* Task.resolve(42),
|
|
854
|
-
* Task.delay(
|
|
891
|
+
* Task.delay(Duration.seconds(1))
|
|
855
892
|
* )(); // Resolves after 1 second
|
|
856
893
|
* ```
|
|
857
894
|
*/
|
|
858
|
-
const delay: (
|
|
895
|
+
const delay: (duration: Duration) => <A>(data: Task<A>) => Task<A>;
|
|
859
896
|
/**
|
|
860
897
|
* Runs a Task a fixed number of times sequentially, collecting all results into an array.
|
|
861
|
-
* An optional delay
|
|
898
|
+
* An optional delay duration can be inserted between runs.
|
|
862
899
|
*
|
|
863
900
|
* @example
|
|
864
901
|
* ```ts
|
|
865
902
|
* pipe(
|
|
866
903
|
* pollSensor,
|
|
867
|
-
* Task.repeat({ times: 5, delay:
|
|
904
|
+
* Task.repeat({ times: 5, delay: Duration.seconds(1) })
|
|
868
905
|
* )(); // Task<Reading[]> — 5 readings, one per second
|
|
869
906
|
* ```
|
|
870
907
|
*/
|
|
871
908
|
const repeat: (options: {
|
|
872
909
|
times: number;
|
|
873
|
-
delay?:
|
|
910
|
+
delay?: Duration;
|
|
874
911
|
}) => <A>(task: Task<A>) => Task<readonly A[]>;
|
|
875
912
|
/**
|
|
876
913
|
* Runs a Task repeatedly until the result satisfies a predicate, returning that result.
|
|
877
|
-
* An optional delay
|
|
914
|
+
* An optional delay duration can be inserted between runs.
|
|
878
915
|
* An optional `maxAttempts` cap stops the loop after N calls — the last value is returned
|
|
879
916
|
* regardless of whether the predicate was satisfied.
|
|
880
917
|
*
|
|
@@ -882,18 +919,19 @@ declare namespace Task {
|
|
|
882
919
|
* ```ts
|
|
883
920
|
* pipe(
|
|
884
921
|
* checkStatus,
|
|
885
|
-
* Task.repeatUntil({ when: (s) => s === "ready", delay: 500 })
|
|
922
|
+
* Task.repeatUntil({ when: (s) => s === "ready", delay: Duration.milliseconds(500) })
|
|
886
923
|
* )(); // polls every 500ms until status is "ready"
|
|
887
924
|
* ```
|
|
888
925
|
*/
|
|
889
926
|
const repeatUntil: <A>(options: {
|
|
890
927
|
when: (a: A) => boolean;
|
|
891
|
-
delay?:
|
|
928
|
+
delay?: Duration;
|
|
892
929
|
maxAttempts?: number;
|
|
893
930
|
}) => (task: Task<A>) => Task<A>;
|
|
894
931
|
/**
|
|
895
932
|
* Resolves with the value of the first Task to complete. All Tasks start
|
|
896
|
-
* immediately
|
|
933
|
+
* immediately. When one resolves, the other tasks are cancelled (aborted)
|
|
934
|
+
* downstream.
|
|
897
935
|
*
|
|
898
936
|
* @example
|
|
899
937
|
* ```ts
|
|
@@ -904,6 +942,17 @@ declare namespace Task {
|
|
|
904
942
|
* ```
|
|
905
943
|
*/
|
|
906
944
|
const race: <A>(tasks: ReadonlyArray<Task<A>>) => Task<A>;
|
|
945
|
+
/**
|
|
946
|
+
* Runs an array of Tasks concurrently and collects their results in an array.
|
|
947
|
+
* Forward-propagates the call site's AbortSignal to all subtasks concurrently.
|
|
948
|
+
*
|
|
949
|
+
* @example
|
|
950
|
+
* ```ts
|
|
951
|
+
* Task.sequence([loadConfig, detectLocale, loadTheme])();
|
|
952
|
+
* // Deferred<[Config, string, Theme]>
|
|
953
|
+
* ```
|
|
954
|
+
*/
|
|
955
|
+
const sequence: <A>(tasks: ReadonlyArray<Task<A>>) => Task<ReadonlyArray<A>>;
|
|
907
956
|
/**
|
|
908
957
|
* Runs an array of Tasks one at a time in order, collecting all results.
|
|
909
958
|
* Each Task starts only after the previous one resolves.
|
|
@@ -923,20 +972,20 @@ declare namespace Task {
|
|
|
923
972
|
const sequential: <A>(tasks: ReadonlyArray<Task<A>>) => Task<ReadonlyArray<A>>;
|
|
924
973
|
/**
|
|
925
974
|
* Converts a `Task<A>` into a `Task<Result<E, A>>`, resolving to `Err` if the
|
|
926
|
-
* Task does not complete within the given
|
|
927
|
-
* `AbortSignal` that fires when the deadline passes, so operations
|
|
975
|
+
* Task does not complete within the given duration. The inner Task receives an
|
|
976
|
+
* `AbortSignal` that fires when the deadline passes, so asynchronous operations
|
|
928
977
|
* that accept a signal are cancelled rather than left dangling.
|
|
929
978
|
*
|
|
930
979
|
* @example
|
|
931
980
|
* ```ts
|
|
932
981
|
* pipe(
|
|
933
982
|
* heavyComputation,
|
|
934
|
-
* Task.timeout(
|
|
983
|
+
* Task.timeout(Duration.seconds(5), () => "timed out"),
|
|
935
984
|
* TaskResult.chain(processResult)
|
|
936
985
|
* );
|
|
937
986
|
* ```
|
|
938
987
|
*/
|
|
939
|
-
const timeout: <E>(
|
|
988
|
+
const timeout: <E>(duration: Duration, onTimeout: () => E) => <A>(task: Task<A>) => Task<Result<E, A>>;
|
|
940
989
|
/**
|
|
941
990
|
* Creates a Task paired with an `abort` handle. Calling `abort()` cancels the
|
|
942
991
|
* current in-flight call immediately. Unlike a one-shot abort, calling `task()`
|
|
@@ -968,7 +1017,7 @@ declare namespace Task {
|
|
|
968
1017
|
* @example
|
|
969
1018
|
* ```ts
|
|
970
1019
|
* const name = await pipe(
|
|
971
|
-
*
|
|
1020
|
+
* loadConfig,
|
|
972
1021
|
* Task.map(config => config.name),
|
|
973
1022
|
* Task.run(),
|
|
974
1023
|
* );
|
|
@@ -977,4 +1026,4 @@ declare namespace Task {
|
|
|
977
1026
|
const run: (signal?: AbortSignal) => <A>(task: Task<A>) => Promise<A>;
|
|
978
1027
|
}
|
|
979
1028
|
|
|
980
|
-
export { Deferred as D, Equality as E, Maybe as M, type None as N, type Ok as O, Result as R, type Some as S, Task as T, type WithValue as W, type
|
|
1029
|
+
export { Deferred as D, Equality as E, Maybe as M, type None as N, type Ok as O, Result as R, type Some as S, Task as T, type WithValue as W, type Err as a, Ordering as b, type WithLog as c, type WithKind as d, type WithError as e, type RetryOptions as f, type TimeoutOptions as g, type WithTimeout as h, type WithMinInterval as i, type WithCooldown as j, type WithConcurrency as k, type WithSize as l, type WithDuration as m, type WithN as n, type WithErrors as o, type WithFirst as p, type WithSecond as q };
|