@nlozgachev/pipelined 0.28.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 +105 -102
- package/dist/{Task-BZT0wedE.d.mts → Task-BDcKwFAj.d.mts} +33 -20
- package/dist/{Task-BW66NsGR.d.ts → Task-CnF22Q2o.d.ts} +33 -20
- package/dist/{chunk-CA3VE4YD.mjs → chunk-FWYOEWJ2.mjs} +10 -2
- package/dist/{chunk-QJS6D6MW.mjs → chunk-PV7JOUKL.mjs} +117 -39
- package/dist/{chunk-7JF44HJH.mjs → chunk-SDGDJ7CU.mjs} +17 -16
- package/dist/core.d.mts +236 -66
- package/dist/core.d.ts +236 -66
- package/dist/core.js +133 -54
- package/dist/core.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +142 -55
- package/dist/index.mjs +3 -3
- package/dist/utils.d.mts +17 -2
- package/dist/utils.d.ts +17 -2
- package/dist/utils.js +26 -17
- package/dist/utils.mjs +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -34,18 +34,18 @@ import { Maybe } from "@nlozgachev/pipelined/core";
|
|
|
34
34
|
import { Num, Str } from "@nlozgachev/pipelined/utils";
|
|
35
35
|
|
|
36
36
|
const parseDiscount = (raw: string): string =>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
45
|
|
|
46
46
|
parseDiscount(" 15 "); // "15% off"
|
|
47
|
-
parseDiscount("150");
|
|
48
|
-
parseDiscount("abc");
|
|
47
|
+
parseDiscount("150"); // "No discount"
|
|
48
|
+
parseDiscount("abc"); // "No discount"
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Every step that sees `None` is skipped. The fallback runs once, at the end.
|
|
@@ -59,35 +59,35 @@ values — the error type is part of the signature, not a runtime surprise.
|
|
|
59
59
|
import { pipe } from "@nlozgachev/pipelined/composition";
|
|
60
60
|
import { Result, TaskResult } from "@nlozgachev/pipelined/core";
|
|
61
61
|
|
|
62
|
-
type ApiError = { status: number; message: string };
|
|
62
|
+
type ApiError = { status: number; message: string; };
|
|
63
63
|
|
|
64
64
|
const fetchUser = (id: string): TaskResult<ApiError, User> =>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
73
|
|
|
74
74
|
const fetchPosts = (userId: string): TaskResult<ApiError, Post[]> =>
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
TaskResult.tryCatch(
|
|
76
|
+
(signal) => fetch(`/users/${userId}/posts`, { signal }).then((r) => r.json()),
|
|
77
|
+
(e) => e as ApiError,
|
|
78
|
+
);
|
|
79
79
|
|
|
80
80
|
// Chain two requests — the AbortSignal propagates to both automatically
|
|
81
81
|
const userWithPosts = (id: string) =>
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
91
|
```
|
|
92
92
|
|
|
93
93
|
`userWithPosts` is a lazy function — nothing runs until called. The `AbortSignal` threads through
|
|
@@ -95,12 +95,13 @@ both requests: abort at any point and whichever request is in flight is cancelle
|
|
|
95
95
|
|
|
96
96
|
```ts
|
|
97
97
|
const controller = new AbortController();
|
|
98
|
-
const
|
|
98
|
+
const fetchUserWithPosts = userWithPosts("42"); // build the lazy task
|
|
99
|
+
const result = await fetchUserWithPosts(controller.signal); // run it — signal controls cancellation
|
|
99
100
|
|
|
100
101
|
if (Result.isOk(result)) {
|
|
101
|
-
|
|
102
|
+
render(result.value); // { ...User, posts: Post[] }
|
|
102
103
|
} else {
|
|
103
|
-
|
|
104
|
+
showError(result.error); // ApiError — typed, not unknown
|
|
104
105
|
}
|
|
105
106
|
```
|
|
106
107
|
|
|
@@ -114,23 +115,23 @@ import { pipe } from "@nlozgachev/pipelined/composition";
|
|
|
114
115
|
import { Maybe } from "@nlozgachev/pipelined/core";
|
|
115
116
|
import { Arr, Num, Rec, Str } from "@nlozgachev/pipelined/utils";
|
|
116
117
|
|
|
117
|
-
type RawItem = { name: string; price: string; category: string };
|
|
118
|
-
type Item = { name: string; price: number; category: string };
|
|
118
|
+
type RawItem = { name: string; price: string; category: string; };
|
|
119
|
+
type Item = { name: string; price: number; category: string; };
|
|
119
120
|
|
|
120
121
|
const normalise = (raw: RawItem): Maybe<Item> =>
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
+
);
|
|
125
126
|
|
|
126
127
|
const cheapestByCategory = (items: RawItem[]) =>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
+
);
|
|
134
135
|
```
|
|
135
136
|
|
|
136
137
|
`filterMap` applies a function that returns `Maybe` and collects only the `Some` results — one step
|
|
@@ -143,35 +144,35 @@ A careful, production-minded attempt at "fetch with retry, timeout, and cancella
|
|
|
143
144
|
|
|
144
145
|
```ts
|
|
145
146
|
type UserResult =
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
| { ok: true; user: User; }
|
|
148
|
+
| { ok: false; error: "Timeout" | "NetworkError"; };
|
|
148
149
|
|
|
149
150
|
async function fetchUser(
|
|
150
|
-
|
|
151
|
-
|
|
151
|
+
id: string,
|
|
152
|
+
signal?: AbortSignal,
|
|
152
153
|
): Promise<UserResult> {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
154
|
+
async function attempt(n: number): Promise<UserResult> {
|
|
155
|
+
const controller = new AbortController();
|
|
156
|
+
const timerId = setTimeout(() => controller.abort(), 5000);
|
|
157
|
+
signal?.addEventListener("abort", () => controller.abort(), { once: true });
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const res = await fetch(`/users/${id}`, { signal: controller.signal });
|
|
161
|
+
clearTimeout(timerId);
|
|
162
|
+
return { ok: true, user: await res.json() };
|
|
163
|
+
} catch (e) {
|
|
164
|
+
clearTimeout(timerId);
|
|
165
|
+
if ((e as Error).name === "AbortError" && !signal?.aborted) {
|
|
166
|
+
return { ok: false, error: "Timeout" };
|
|
167
|
+
}
|
|
168
|
+
if (n < 3) {
|
|
169
|
+
await new Promise((r) => setTimeout(r, n * 1000));
|
|
170
|
+
return attempt(n + 1);
|
|
171
|
+
}
|
|
172
|
+
return { ok: false, error: "NetworkError" };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return attempt(1);
|
|
175
176
|
}
|
|
176
177
|
```
|
|
177
178
|
|
|
@@ -185,15 +186,16 @@ With **pipelined**:
|
|
|
185
186
|
import { Op } from "@nlozgachev/pipelined/core";
|
|
186
187
|
|
|
187
188
|
const fetchUser = Op.interpret(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
+
},
|
|
197
199
|
);
|
|
198
200
|
```
|
|
199
201
|
|
|
@@ -205,9 +207,9 @@ propagation, and timeout wiring are handled automatically. The outcome type is t
|
|
|
205
207
|
const outcome = await fetchUser.run("42");
|
|
206
208
|
|
|
207
209
|
if (Op.isOk(outcome)) {
|
|
208
|
-
|
|
210
|
+
render(outcome.value); // User
|
|
209
211
|
} else if (Op.isErr(outcome)) {
|
|
210
|
-
|
|
212
|
+
showError(outcome.error); // ApiError, not unknown
|
|
211
213
|
}
|
|
212
214
|
|
|
213
215
|
// explicit cancellation — in-flight request is aborted immediately
|
|
@@ -228,21 +230,21 @@ different answer to the same question: _what happens to the previous call when a
|
|
|
228
230
|
import { Op } from "@nlozgachev/pipelined/core";
|
|
229
231
|
|
|
230
232
|
const searchOp = Op.create(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
233
|
+
(signal) => (query: string) =>
|
|
234
|
+
fetch(`/search?q=${query}`, { signal }).then((r) => r.json() as Promise<SearchResult[]>),
|
|
235
|
+
(e) => new SearchError(e),
|
|
234
236
|
);
|
|
235
237
|
|
|
236
238
|
const search = Op.interpret(searchOp, {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
+
strategy: "restartable", // new call cancels the previous one
|
|
240
|
+
retry: { attempts: 2, backoff: 300 },
|
|
239
241
|
});
|
|
240
242
|
|
|
241
243
|
search.subscribe((state) => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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);
|
|
246
248
|
});
|
|
247
249
|
|
|
248
250
|
input.addEventListener("input", (e) => search.run(e.currentTarget.value));
|
|
@@ -252,23 +254,24 @@ input.addEventListener("input", (e) => search.run(e.currentTarget.value));
|
|
|
252
254
|
|
|
253
255
|
```ts
|
|
254
256
|
const submitOp = Op.create(
|
|
255
|
-
|
|
256
|
-
|
|
257
|
+
(signal) => (data: FormData) =>
|
|
258
|
+
fetch("/orders", { method: "POST", body: data, signal }).then((r) => r.json()),
|
|
259
|
+
(e) => new ApiError(e),
|
|
257
260
|
);
|
|
258
261
|
|
|
259
262
|
const submit = Op.interpret(submitOp, {
|
|
260
|
-
|
|
263
|
+
strategy: "exclusive", // in-flight? new calls are dropped immediately
|
|
261
264
|
});
|
|
262
265
|
|
|
263
266
|
submit.subscribe((state) => {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
+
submitButton.disabled = Op.isPending(state);
|
|
268
|
+
if (Op.isOk(state)) showConfirmation(state.value);
|
|
269
|
+
if (Op.isError(state)) showError(state.error);
|
|
267
270
|
});
|
|
268
271
|
|
|
269
272
|
form.addEventListener("submit", (e) => {
|
|
270
|
-
|
|
271
|
-
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
submit.run(new FormData(form)); // double-clicks and rage-clicks are ignored
|
|
272
275
|
});
|
|
273
276
|
```
|
|
274
277
|
|
|
@@ -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>;
|
|
@@ -287,7 +287,7 @@ declare namespace Result {
|
|
|
287
287
|
* @example
|
|
288
288
|
* ```ts
|
|
289
289
|
* pipe(
|
|
290
|
-
* Result.
|
|
290
|
+
* Result.error(new Error("not found")),
|
|
291
291
|
* Result.recoverUnless(e => e.message === "fatal", () => Result.ok(0))
|
|
292
292
|
* ); // Ok(0)
|
|
293
293
|
* ```
|
|
@@ -300,7 +300,7 @@ declare namespace Result {
|
|
|
300
300
|
* @example
|
|
301
301
|
* ```ts
|
|
302
302
|
* Result.toMaybe(Result.ok(42)); // Some(42)
|
|
303
|
-
* Result.toMaybe(Result.
|
|
303
|
+
* Result.toMaybe(Result.error("oops")); // None
|
|
304
304
|
* ```
|
|
305
305
|
*/
|
|
306
306
|
const toMaybe: <E, A>(data: Result<E, A>) => Maybe<A>;
|
|
@@ -414,7 +414,7 @@ declare namespace Maybe {
|
|
|
414
414
|
* @example
|
|
415
415
|
* ```ts
|
|
416
416
|
* Maybe.fromResult(Result.ok(42)); // Some(42)
|
|
417
|
-
* Maybe.fromResult(Result.
|
|
417
|
+
* Maybe.fromResult(Result.error("oops")); // None
|
|
418
418
|
* ```
|
|
419
419
|
*/
|
|
420
420
|
const fromResult: <E, A>(data: Result<E, A>) => Maybe<A>;
|
|
@@ -797,6 +797,19 @@ declare namespace Task {
|
|
|
797
797
|
task: Task<A>;
|
|
798
798
|
abort: () => void;
|
|
799
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>;
|
|
800
813
|
}
|
|
801
814
|
|
|
802
|
-
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 };
|
|
@@ -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>;
|
|
@@ -287,7 +287,7 @@ declare namespace Result {
|
|
|
287
287
|
* @example
|
|
288
288
|
* ```ts
|
|
289
289
|
* pipe(
|
|
290
|
-
* Result.
|
|
290
|
+
* Result.error(new Error("not found")),
|
|
291
291
|
* Result.recoverUnless(e => e.message === "fatal", () => Result.ok(0))
|
|
292
292
|
* ); // Ok(0)
|
|
293
293
|
* ```
|
|
@@ -300,7 +300,7 @@ declare namespace Result {
|
|
|
300
300
|
* @example
|
|
301
301
|
* ```ts
|
|
302
302
|
* Result.toMaybe(Result.ok(42)); // Some(42)
|
|
303
|
-
* Result.toMaybe(Result.
|
|
303
|
+
* Result.toMaybe(Result.error("oops")); // None
|
|
304
304
|
* ```
|
|
305
305
|
*/
|
|
306
306
|
const toMaybe: <E, A>(data: Result<E, A>) => Maybe<A>;
|
|
@@ -414,7 +414,7 @@ declare namespace Maybe {
|
|
|
414
414
|
* @example
|
|
415
415
|
* ```ts
|
|
416
416
|
* Maybe.fromResult(Result.ok(42)); // Some(42)
|
|
417
|
-
* Maybe.fromResult(Result.
|
|
417
|
+
* Maybe.fromResult(Result.error("oops")); // None
|
|
418
418
|
* ```
|
|
419
419
|
*/
|
|
420
420
|
const fromResult: <E, A>(data: Result<E, A>) => Maybe<A>;
|
|
@@ -797,6 +797,19 @@ declare namespace Task {
|
|
|
797
797
|
task: Task<A>;
|
|
798
798
|
abort: () => void;
|
|
799
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>;
|
|
800
813
|
}
|
|
801
814
|
|
|
802
|
-
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 };
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
Maybe,
|
|
4
4
|
Result,
|
|
5
5
|
Task
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SDGDJ7CU.mjs";
|
|
7
7
|
import {
|
|
8
8
|
isNonEmptyList
|
|
9
9
|
} from "./chunk-DBIC62UV.mjs";
|
|
@@ -166,7 +166,7 @@ var Arr;
|
|
|
166
166
|
const result = [];
|
|
167
167
|
for (const a of data) {
|
|
168
168
|
const r = await Deferred.toPromise(f(a)());
|
|
169
|
-
if (Result.
|
|
169
|
+
if (Result.isError(r)) return r;
|
|
170
170
|
result.push(r.value);
|
|
171
171
|
}
|
|
172
172
|
return Result.ok(result);
|
|
@@ -307,6 +307,14 @@ var Dict;
|
|
|
307
307
|
}
|
|
308
308
|
return result;
|
|
309
309
|
};
|
|
310
|
+
Dict2.filterMap = (f) => (m) => {
|
|
311
|
+
const result = new globalThis.Map();
|
|
312
|
+
for (const [key, value] of m) {
|
|
313
|
+
const mapped = f(value);
|
|
314
|
+
if (mapped.kind === "Some") result.set(key, mapped.value);
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
};
|
|
310
318
|
Dict2.union = (other) => (m) => {
|
|
311
319
|
const result = new globalThis.Map(m);
|
|
312
320
|
for (const [k, v] of other) {
|