@nlozgachev/pipelined 0.27.0 → 0.29.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 +157 -35
- package/dist/{Task-vQb3-puQ.d.mts → Task-BDcKwFAj.d.mts} +52 -30
- package/dist/{Task-C8Pgm7EX.d.ts → Task-CnF22Q2o.d.ts} +52 -30
- package/dist/{chunk-SBTMTAZF.mjs → chunk-FWYOEWJ2.mjs} +12 -4
- package/dist/{chunk-YYVONF5X.mjs → chunk-PV7JOUKL.mjs} +127 -39
- package/dist/{chunk-HHCRWQYN.mjs → chunk-SDGDJ7CU.mjs} +25 -20
- package/dist/core.d.mts +305 -72
- package/dist/core.d.ts +305 -72
- package/dist/core.js +151 -58
- package/dist/core.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +162 -61
- package/dist/index.mjs +3 -3
- package/dist/utils.d.mts +28 -10
- package/dist/utils.d.ts +28 -10
- package/dist/utils.js +36 -23
- package/dist/utils.mjs +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@nlozgachev/pipelined) [](https://github.com/nlozgachev/pipelined/actions/workflows/publish.yml) [](https://app.codecov.io/github/nlozgachev/pipelined)
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
Opinionated functional abstractions for TypeScript.
|
|
7
6
|
|
|
8
7
|
> **Note:** pipelined is pre-1.0. The API may change between minor versions until the 1.0 release.
|
|
@@ -14,15 +13,132 @@ npm add @nlozgachev/pipelined
|
|
|
14
13
|
## Possibly maybe
|
|
15
14
|
|
|
16
15
|
**pipelined** names every possible state and gives you operations that compose. `Maybe<A>` for values
|
|
17
|
-
that may or may not be there. `Result<E, A>` for operations that succeed or fail
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
that may or may not be there. `Result<E, A>` for operations that succeed or fail with a typed error.
|
|
17
|
+
`TaskResult<E, A>` for async operations that keep failures as typed values and propagate cancellation
|
|
18
|
+
automatically. `Op<I, E, A>` for managing repeated async interactions — retry, timeout, and
|
|
19
|
+
concurrency strategy in one place. And, of course, there is more than that.
|
|
20
20
|
|
|
21
21
|
## Documentation
|
|
22
22
|
|
|
23
23
|
Full guides and API reference at **[pipelined.lozgachev.dev](https://pipelined.lozgachev.dev)**.
|
|
24
24
|
|
|
25
|
-
## Example:
|
|
25
|
+
## Example: composing optional values
|
|
26
|
+
|
|
27
|
+
`null` checks accumulate fast. Each one is a conditional branch that the type system can't help you
|
|
28
|
+
forget. `Maybe<A>` turns absence into a value that composes — the same operations apply whether or
|
|
29
|
+
not anything is there:
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { pipe } from "@nlozgachev/pipelined/composition";
|
|
33
|
+
import { Maybe } from "@nlozgachev/pipelined/core";
|
|
34
|
+
import { Num, Str } from "@nlozgachev/pipelined/utils";
|
|
35
|
+
|
|
36
|
+
const parseDiscount = (raw: string): string =>
|
|
37
|
+
pipe(
|
|
38
|
+
raw,
|
|
39
|
+
Str.trim,
|
|
40
|
+
Num.parse, // "10" → Some(10), "abc" → None
|
|
41
|
+
Maybe.filter((n) => n >= 0 && n <= 100), // out of range → None
|
|
42
|
+
Maybe.map((n) => `${n}% off`),
|
|
43
|
+
Maybe.getOrElse(() => "No discount"),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
parseDiscount(" 15 "); // "15% off"
|
|
47
|
+
parseDiscount("150"); // "No discount"
|
|
48
|
+
parseDiscount("abc"); // "No discount"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Every step that sees `None` is skipped. The fallback runs once, at the end.
|
|
52
|
+
|
|
53
|
+
## Example: typed async errors
|
|
54
|
+
|
|
55
|
+
Unhandled rejections are invisible until they crash. `TaskResult<E, A>` keeps failures as typed
|
|
56
|
+
values — the error type is part of the signature, not a runtime surprise.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { pipe } from "@nlozgachev/pipelined/composition";
|
|
60
|
+
import { Result, TaskResult } from "@nlozgachev/pipelined/core";
|
|
61
|
+
|
|
62
|
+
type ApiError = { status: number; message: string; };
|
|
63
|
+
|
|
64
|
+
const fetchUser = (id: string): TaskResult<ApiError, User> =>
|
|
65
|
+
TaskResult.tryCatch(
|
|
66
|
+
(signal) =>
|
|
67
|
+
fetch(`/users/${id}`, { signal }).then((r) => {
|
|
68
|
+
if (!r.ok) throw { status: r.status, message: r.statusText };
|
|
69
|
+
return r.json() as Promise<User>;
|
|
70
|
+
}),
|
|
71
|
+
(e) => e as ApiError,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const fetchPosts = (userId: string): TaskResult<ApiError, Post[]> =>
|
|
75
|
+
TaskResult.tryCatch(
|
|
76
|
+
(signal) => fetch(`/users/${userId}/posts`, { signal }).then((r) => r.json()),
|
|
77
|
+
(e) => e as ApiError,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Chain two requests — the AbortSignal propagates to both automatically
|
|
81
|
+
const userWithPosts = (id: string) =>
|
|
82
|
+
pipe(
|
|
83
|
+
fetchUser(id),
|
|
84
|
+
TaskResult.chain((user) =>
|
|
85
|
+
pipe(
|
|
86
|
+
fetchPosts(user.id),
|
|
87
|
+
TaskResult.map((posts) => ({ ...user, posts })),
|
|
88
|
+
)
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`userWithPosts` is a lazy function — nothing runs until called. The `AbortSignal` threads through
|
|
94
|
+
both requests: abort at any point and whichever request is in flight is cancelled immediately.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
const fetchUserWithPosts = userWithPosts("42"); // build the lazy task
|
|
99
|
+
const result = await fetchUserWithPosts(controller.signal); // run it — signal controls cancellation
|
|
100
|
+
|
|
101
|
+
if (Result.isOk(result)) {
|
|
102
|
+
render(result.value); // { ...User, posts: Post[] }
|
|
103
|
+
} else {
|
|
104
|
+
showError(result.error); // ApiError — typed, not unknown
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Example: transforming data
|
|
109
|
+
|
|
110
|
+
The utils modules wrap JavaScript's built-in types with data-last, curried operations that return
|
|
111
|
+
`Maybe` wherever a value might be absent. They compose naturally with the core types:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { pipe } from "@nlozgachev/pipelined/composition";
|
|
115
|
+
import { Maybe } from "@nlozgachev/pipelined/core";
|
|
116
|
+
import { Arr, Num, Rec, Str } from "@nlozgachev/pipelined/utils";
|
|
117
|
+
|
|
118
|
+
type RawItem = { name: string; price: string; category: string; };
|
|
119
|
+
type Item = { name: string; price: number; category: string; };
|
|
120
|
+
|
|
121
|
+
const normalise = (raw: RawItem): Maybe<Item> =>
|
|
122
|
+
pipe(
|
|
123
|
+
Num.parse(raw.price), // "9.99" → Some(9.99), "n/a" → None
|
|
124
|
+
Maybe.map((price) => ({ name: Str.trim(raw.name), price, category: raw.category })),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const cheapestByCategory = (items: RawItem[]) =>
|
|
128
|
+
pipe(
|
|
129
|
+
items,
|
|
130
|
+
Arr.filterMap(normalise), // parse + drop unparseable prices in one pass
|
|
131
|
+
Arr.sortBy((a, b) => a.price - b.price), // ascending price
|
|
132
|
+
Arr.groupBy((item) => item.category), // Record<string, NonEmptyList<Item>>
|
|
133
|
+
Rec.map((group) => Arr.head(group)), // cheapest per category — Maybe<Item>
|
|
134
|
+
);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`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 `Item | undefined`,
|
|
139
|
+
so the absence is explicit in the type and the rest of the pipeline handles it the same way.
|
|
140
|
+
|
|
141
|
+
## Example: retry, timeout, and cancellation
|
|
26
142
|
|
|
27
143
|
A careful, production-minded attempt at "fetch with retry, timeout, and cancellation":
|
|
28
144
|
|
|
@@ -67,38 +183,44 @@ to thread the attempt count.
|
|
|
67
183
|
With **pipelined**:
|
|
68
184
|
|
|
69
185
|
```ts
|
|
70
|
-
import {
|
|
71
|
-
import { Result, TaskResult } from "@nlozgachev/pipelined/core";
|
|
186
|
+
import { Op } from "@nlozgachev/pipelined/core";
|
|
72
187
|
|
|
73
|
-
const fetchUser = (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
188
|
+
const fetchUser = Op.interpret(
|
|
189
|
+
Op.create(
|
|
190
|
+
(signal) => (id: string) =>
|
|
191
|
+
fetch(`/users/${id}`, { signal }).then((r) => r.json() as Promise<User>),
|
|
192
|
+
(e) => new ApiError(e),
|
|
193
|
+
),
|
|
194
|
+
{
|
|
195
|
+
strategy: "restartable",
|
|
196
|
+
retry: { attempts: 3, backoff: (n) => n * 1000 },
|
|
197
|
+
timeout: { ms: 5000, onTimeout: () => new ApiError("request timed out") },
|
|
198
|
+
},
|
|
199
|
+
);
|
|
82
200
|
```
|
|
83
201
|
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
`ApiError` on the left, `User` on the right, nothing escapes as an exception.
|
|
202
|
+
`fetchUser` is a managed operator — nothing runs until you call `run`. Retry logic, signal
|
|
203
|
+
propagation, and timeout wiring are handled automatically. The outcome type is the contract:
|
|
204
|
+
`ApiError` on the left, `User` on the right, nothing escapes as an unhandled exception.
|
|
87
205
|
|
|
88
206
|
```ts
|
|
89
|
-
const
|
|
90
|
-
const result = await fetchUser("42")(controller.signal);
|
|
207
|
+
const outcome = await fetchUser.run("42");
|
|
91
208
|
|
|
92
|
-
if (
|
|
93
|
-
render(
|
|
94
|
-
} else {
|
|
95
|
-
showError(
|
|
209
|
+
if (Op.isOk(outcome)) {
|
|
210
|
+
render(outcome.value); // User
|
|
211
|
+
} else if (Op.isErr(outcome)) {
|
|
212
|
+
showError(outcome.error); // ApiError, not unknown
|
|
96
213
|
}
|
|
214
|
+
|
|
215
|
+
// explicit cancellation — in-flight request is aborted immediately
|
|
216
|
+
fetchUser.abort();
|
|
97
217
|
```
|
|
98
218
|
|
|
99
219
|
## Example: repeated interactions
|
|
100
220
|
|
|
101
|
-
|
|
221
|
+
Real UIs make the same call many times — a search input fires on every keystroke, a submit button
|
|
222
|
+
gets clicked twice, a polling loop needs to stop when something newer starts. Each scenario has a
|
|
223
|
+
different answer to the same question: _what happens to the previous call when a new one arrives?_
|
|
102
224
|
|
|
103
225
|
`Op` makes that question a one-word configuration choice.
|
|
104
226
|
|
|
@@ -119,10 +241,10 @@ const search = Op.interpret(searchOp, {
|
|
|
119
241
|
});
|
|
120
242
|
|
|
121
243
|
search.subscribe((state) => {
|
|
122
|
-
if (state
|
|
123
|
-
if (state
|
|
124
|
-
if (state
|
|
125
|
-
if (state
|
|
244
|
+
if (Op.isPending(state)) showSpinner();
|
|
245
|
+
if (Op.isRetrying(state)) showSpinner(`retrying… attempt ${state.attempt}`);
|
|
246
|
+
if (Op.isOk(state)) showResults(state.value);
|
|
247
|
+
if (Op.isError(state)) showError(state.error);
|
|
126
248
|
});
|
|
127
249
|
|
|
128
250
|
input.addEventListener("input", (e) => search.run(e.currentTarget.value));
|
|
@@ -142,9 +264,9 @@ const submit = Op.interpret(submitOp, {
|
|
|
142
264
|
});
|
|
143
265
|
|
|
144
266
|
submit.subscribe((state) => {
|
|
145
|
-
submitButton.disabled = state
|
|
146
|
-
if (state
|
|
147
|
-
if (state
|
|
267
|
+
submitButton.disabled = Op.isPending(state);
|
|
268
|
+
if (Op.isOk(state)) showConfirmation(state.value);
|
|
269
|
+
if (Op.isError(state)) showError(state.error);
|
|
148
270
|
});
|
|
149
271
|
|
|
150
272
|
form.addEventListener("submit", (e) => {
|
|
@@ -153,7 +275,7 @@ form.addEventListener("submit", (e) => {
|
|
|
153
275
|
});
|
|
154
276
|
```
|
|
155
277
|
|
|
156
|
-
`restartable`, `exclusive`, `debounced`, `throttled`, `queue`, `concurrent`, `keyed` — each strategy is a complete, tested answer to one concurrency scenario. Swap the word, keep the rest of the code.
|
|
278
|
+
`restartable`, `exclusive`, `debounced`, `throttled`, `queue`, `buffered`, `concurrent`, `keyed`, `once` — each strategy is a complete, tested answer to one concurrency scenario. Swap the word, keep the rest of the code.
|
|
157
279
|
|
|
158
280
|
## What's included?
|
|
159
281
|
|
|
@@ -168,7 +290,7 @@ The library covers the states you encounter in real applications: values that ma
|
|
|
168
290
|
- **`TaskResult<E, A>`** — a lazy async operation that can fail with a typed error.
|
|
169
291
|
- **`TaskMaybe<A>`** — a lazy async operation that may produce nothing.
|
|
170
292
|
- **`TaskValidation<E, A>`** — a lazy async operation that accumulates validation errors.
|
|
171
|
-
- **`Op<I, E, A>`** — a managed async operation with a named concurrency strategy: `restartable`, `exclusive`, `debounced`, `throttled`, `queue`, `concurrent`, `keyed`, or `once`. Handles retry, timeout, cancellation, and state in one place.
|
|
293
|
+
- **`Op<I, E, A>`** — a managed async operation with a named concurrency strategy: `restartable`, `exclusive`, `debounced`, `throttled`, `queue`, `buffered`, `concurrent`, `keyed`, or `once`. Handles retry, timeout, cancellation, and state in one place.
|
|
172
294
|
- **`RemoteData<E, A>`** — the four states of a data fetch: `NotAsked`, `Loading`, `Failure`, `Success`.
|
|
173
295
|
- **`These<A, B>`** — an inclusive OR: holds a first value, a second, or both at once.
|
|
174
296
|
- **`Lens<S, A>`** — focus on a required field in a nested structure. Read, set, and modify immutably.
|
|
@@ -107,15 +107,15 @@ type WithMinInterval = {
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
type Ok<A> = WithKind<"Ok"> & WithValue<A>;
|
|
110
|
-
type
|
|
110
|
+
type Error<E> = WithKind<"Error"> & WithError<E>;
|
|
111
111
|
/**
|
|
112
|
-
* Result represents a value that can be one of two types: a success (Ok) or a failure (
|
|
112
|
+
* Result represents a value that can be one of two types: a success (Ok) or a failure (Error).
|
|
113
113
|
* Use Result when an operation can fail with a meaningful error value.
|
|
114
114
|
*
|
|
115
115
|
* @example
|
|
116
116
|
* ```ts
|
|
117
117
|
* const divide = (a: number, b: number): Result<string, number> =>
|
|
118
|
-
* b === 0 ? Result.
|
|
118
|
+
* b === 0 ? Result.error("Division by zero") : Result.ok(a / b);
|
|
119
119
|
*
|
|
120
120
|
* pipe(
|
|
121
121
|
* divide(10, 2),
|
|
@@ -124,7 +124,7 @@ type Err<E> = WithKind<"Error"> & WithError<E>;
|
|
|
124
124
|
* ); // 10
|
|
125
125
|
* ```
|
|
126
126
|
*/
|
|
127
|
-
type Result<E, A> = Ok<A> |
|
|
127
|
+
type Result<E, A> = Ok<A> | Error<E>;
|
|
128
128
|
declare namespace Result {
|
|
129
129
|
/**
|
|
130
130
|
* Creates a successful Result with the given value.
|
|
@@ -133,15 +133,15 @@ declare namespace Result {
|
|
|
133
133
|
/**
|
|
134
134
|
* Creates a failed Result with the given error.
|
|
135
135
|
*/
|
|
136
|
-
const
|
|
136
|
+
const error: <E>(e: E) => Error<E>;
|
|
137
137
|
/**
|
|
138
138
|
* Type guard that checks if an Result is Ok.
|
|
139
139
|
*/
|
|
140
140
|
const isOk: <E, A>(data: Result<E, A>) => data is Ok<A>;
|
|
141
141
|
/**
|
|
142
|
-
* Type guard that checks if an Result is
|
|
142
|
+
* Type guard that checks if an Result is Error.
|
|
143
143
|
*/
|
|
144
|
-
const
|
|
144
|
+
const isError: <E, A>(data: Result<E, A>) => data is Error<E>;
|
|
145
145
|
/**
|
|
146
146
|
* Creates an Result from a function that may throw.
|
|
147
147
|
* Catches any errors and transforms them using the onError function.
|
|
@@ -162,7 +162,7 @@ declare namespace Result {
|
|
|
162
162
|
* @example
|
|
163
163
|
* ```ts
|
|
164
164
|
* pipe(Result.ok(5), Result.map(n => n * 2)); // Ok(10)
|
|
165
|
-
* pipe(Result.
|
|
165
|
+
* pipe(Result.error("error"), Result.map(n => n * 2)); // Error("error")
|
|
166
166
|
* ```
|
|
167
167
|
*/
|
|
168
168
|
const map: <E, A, B>(f: (a: A) => B) => (data: Result<E, A>) => Result<E, B>;
|
|
@@ -171,7 +171,7 @@ declare namespace Result {
|
|
|
171
171
|
*
|
|
172
172
|
* @example
|
|
173
173
|
* ```ts
|
|
174
|
-
* pipe(Result.
|
|
174
|
+
* pipe(Result.error("oops"), Result.mapError(e => e.toUpperCase())); // Error("OOPS")
|
|
175
175
|
* ```
|
|
176
176
|
*/
|
|
177
177
|
const mapError: <E, F, A>(f: (e: E) => F) => (data: Result<E, A>) => Result<F, A>;
|
|
@@ -182,10 +182,10 @@ declare namespace Result {
|
|
|
182
182
|
* @example
|
|
183
183
|
* ```ts
|
|
184
184
|
* const validatePositive = (n: number): Result<string, number> =>
|
|
185
|
-
* n > 0 ? Result.ok(n) : Result.
|
|
185
|
+
* n > 0 ? Result.ok(n) : Result.error("Must be positive");
|
|
186
186
|
*
|
|
187
187
|
* pipe(Result.ok(5), Result.chain(validatePositive)); // Ok(5)
|
|
188
|
-
* pipe(Result.ok(-1), Result.chain(validatePositive)); //
|
|
188
|
+
* pipe(Result.ok(-1), Result.chain(validatePositive)); // Error("Must be positive")
|
|
189
189
|
* ```
|
|
190
190
|
*/
|
|
191
191
|
const chain: <E, A, B>(f: (a: A) => Result<E, B>) => (data: Result<E, A>) => Result<E, B>;
|
|
@@ -230,8 +230,8 @@ declare namespace Result {
|
|
|
230
230
|
* @example
|
|
231
231
|
* ```ts
|
|
232
232
|
* pipe(Result.ok(5), Result.getOrElse(() => 0)); // 5
|
|
233
|
-
* pipe(Result.
|
|
234
|
-
* pipe(Result.
|
|
233
|
+
* pipe(Result.error("error"), Result.getOrElse(() => 0)); // 0
|
|
234
|
+
* pipe(Result.error("error"), Result.getOrElse(() => null)); // null — typed as number | null
|
|
235
235
|
* ```
|
|
236
236
|
*/
|
|
237
237
|
const getOrElse: <E, A, B>(defaultValue: () => B) => (data: Result<E, A>) => A | B;
|
|
@@ -256,7 +256,7 @@ declare namespace Result {
|
|
|
256
256
|
* @example
|
|
257
257
|
* ```ts
|
|
258
258
|
* pipe(
|
|
259
|
-
* Result.
|
|
259
|
+
* Result.error("not found"),
|
|
260
260
|
* Result.tapError(e => console.error("validation failed:", e)),
|
|
261
261
|
* Result.chain(save),
|
|
262
262
|
* )
|
|
@@ -270,8 +270,8 @@ declare namespace Result {
|
|
|
270
270
|
* @example
|
|
271
271
|
* ```ts
|
|
272
272
|
* pipe(5, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); // Ok(5)
|
|
273
|
-
* pipe(-1, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); //
|
|
274
|
-
* pipe("", Result.fromPredicate(s => s.length > 0, () => "empty string")); //
|
|
273
|
+
* pipe(-1, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); // Error("-1 is not positive")
|
|
274
|
+
* pipe("", Result.fromPredicate(s => s.length > 0, () => "empty string")); // Error("empty string")
|
|
275
275
|
* ```
|
|
276
276
|
*/
|
|
277
277
|
const fromPredicate: <E, A>(pred: (a: A) => boolean, onFalse: (a: A) => E) => (a: A) => Result<E, A>;
|
|
@@ -281,10 +281,18 @@ declare namespace Result {
|
|
|
281
281
|
*/
|
|
282
282
|
const recover: <E, A, B>(fallback: (e: E) => Result<E, B>) => (data: Result<E, A>) => Result<E, A | B>;
|
|
283
283
|
/**
|
|
284
|
-
* Recovers from an error unless
|
|
284
|
+
* Recovers from an error unless the predicate `isBlocked` returns true for that error.
|
|
285
285
|
* The fallback can produce a different success type, widening the result to `Result<E, A | B>`.
|
|
286
|
+
*
|
|
287
|
+
* @example
|
|
288
|
+
* ```ts
|
|
289
|
+
* pipe(
|
|
290
|
+
* Result.error(new Error("not found")),
|
|
291
|
+
* Result.recoverUnless(e => e.message === "fatal", () => Result.ok(0))
|
|
292
|
+
* ); // Ok(0)
|
|
293
|
+
* ```
|
|
286
294
|
*/
|
|
287
|
-
const recoverUnless: <E, A, B>(
|
|
295
|
+
const recoverUnless: <E, A, B>(isBlocked: (e: E) => boolean, fallback: () => Result<E, B>) => (data: Result<E, A>) => Result<E, A | B>;
|
|
288
296
|
/**
|
|
289
297
|
* Converts a Result to an Maybe.
|
|
290
298
|
* Ok becomes Some, Err becomes None (the error is discarded).
|
|
@@ -292,7 +300,7 @@ declare namespace Result {
|
|
|
292
300
|
* @example
|
|
293
301
|
* ```ts
|
|
294
302
|
* Result.toMaybe(Result.ok(42)); // Some(42)
|
|
295
|
-
* Result.toMaybe(Result.
|
|
303
|
+
* Result.toMaybe(Result.error("oops")); // None
|
|
296
304
|
* ```
|
|
297
305
|
*/
|
|
298
306
|
const toMaybe: <E, A>(data: Result<E, A>) => Maybe<A>;
|
|
@@ -367,11 +375,6 @@ declare namespace Maybe {
|
|
|
367
375
|
* Extracts the value from a Maybe, returning undefined if None.
|
|
368
376
|
*/
|
|
369
377
|
const toUndefined: <A>(data: Maybe<A>) => A | undefined;
|
|
370
|
-
/**
|
|
371
|
-
* Creates a Maybe from a possibly undefined value.
|
|
372
|
-
* Returns None if undefined, Some otherwise.
|
|
373
|
-
*/
|
|
374
|
-
const fromUndefined: <A>(value: A | undefined) => Maybe<A>;
|
|
375
378
|
/**
|
|
376
379
|
* Creates a Maybe from a predicate applied to a value.
|
|
377
380
|
* Returns Some if the predicate passes, None otherwise.
|
|
@@ -411,7 +414,7 @@ declare namespace Maybe {
|
|
|
411
414
|
* @example
|
|
412
415
|
* ```ts
|
|
413
416
|
* Maybe.fromResult(Result.ok(42)); // Some(42)
|
|
414
|
-
* Maybe.fromResult(Result.
|
|
417
|
+
* Maybe.fromResult(Result.error("oops")); // None
|
|
415
418
|
* ```
|
|
416
419
|
*/
|
|
417
420
|
const fromResult: <E, A>(data: Result<E, A>) => Maybe<A>;
|
|
@@ -703,10 +706,12 @@ declare namespace Task {
|
|
|
703
706
|
const repeat: (options: {
|
|
704
707
|
times: number;
|
|
705
708
|
delay?: number;
|
|
706
|
-
}) => <A>(task: Task<A>) => Task<A[]>;
|
|
709
|
+
}) => <A>(task: Task<A>) => Task<readonly A[]>;
|
|
707
710
|
/**
|
|
708
711
|
* Runs a Task repeatedly until the result satisfies a predicate, returning that result.
|
|
709
712
|
* An optional delay (ms) can be inserted between runs.
|
|
713
|
+
* An optional `maxAttempts` cap stops the loop after N calls — the last value is returned
|
|
714
|
+
* regardless of whether the predicate was satisfied.
|
|
710
715
|
*
|
|
711
716
|
* @example
|
|
712
717
|
* ```ts
|
|
@@ -719,6 +724,7 @@ declare namespace Task {
|
|
|
719
724
|
const repeatUntil: <A>(options: {
|
|
720
725
|
when: (a: A) => boolean;
|
|
721
726
|
delay?: number;
|
|
727
|
+
maxAttempts?: number;
|
|
722
728
|
}) => (task: Task<A>) => Task<A>;
|
|
723
729
|
/**
|
|
724
730
|
* Resolves with the value of the first Task to complete. All Tasks start
|
|
@@ -767,9 +773,12 @@ declare namespace Task {
|
|
|
767
773
|
*/
|
|
768
774
|
const timeout: <E>(ms: number, onTimeout: () => E) => <A>(task: Task<A>) => Task<Result<E, A>>;
|
|
769
775
|
/**
|
|
770
|
-
* Creates a Task paired with an `abort` handle.
|
|
771
|
-
*
|
|
772
|
-
*
|
|
776
|
+
* Creates a Task paired with an `abort` handle. Calling `abort()` cancels the
|
|
777
|
+
* current in-flight call immediately. Unlike a one-shot abort, calling `task()`
|
|
778
|
+
* again after `abort()` starts a fresh call with a new signal.
|
|
779
|
+
*
|
|
780
|
+
* Each invocation of `task()` automatically cancels the previous in-flight call,
|
|
781
|
+
* making it safe to call repeatedly (e.g. on user input) without leaking promises.
|
|
773
782
|
*
|
|
774
783
|
* If an outer signal is also present (passed at the call site), aborting it
|
|
775
784
|
* propagates into the internal controller.
|
|
@@ -788,6 +797,19 @@ declare namespace Task {
|
|
|
788
797
|
task: Task<A>;
|
|
789
798
|
abort: () => void;
|
|
790
799
|
};
|
|
800
|
+
/**
|
|
801
|
+
* Executes a task with an optional signal. Use as a terminal step in a `pipe` chain.
|
|
802
|
+
*
|
|
803
|
+
* @example
|
|
804
|
+
* ```ts
|
|
805
|
+
* const name = await pipe(
|
|
806
|
+
* fetchConfig,
|
|
807
|
+
* Task.map(config => config.name),
|
|
808
|
+
* Task.run(),
|
|
809
|
+
* );
|
|
810
|
+
* ```
|
|
811
|
+
*/
|
|
812
|
+
const run: (signal?: AbortSignal) => <A>(task: Task<A>) => Promise<A>;
|
|
791
813
|
}
|
|
792
814
|
|
|
793
|
-
export { Deferred as D, type
|
|
815
|
+
export { Deferred as D, type Error 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 WithLog as a, type WithKind as b, type WithError as c, type RetryOptions as d, type TimeoutOptions as e, type WithTimeout as f, type WithMinInterval as g, type WithCooldown as h, type WithConcurrency as i, type WithSize as j, type WithMs as k, type WithN as l, type WithErrors as m, type WithFirst as n, type WithSecond as o };
|