unthrown 0.1.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/LICENSE +21 -0
- package/README.md +40 -0
- package/dist/index.cjs +668 -0
- package/dist/index.d.cts +663 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +663 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +655 -0
- package/dist/index.mjs.map +1 -0
- package/docs/index.md +1163 -0
- package/package.json +69 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
//#region src/core.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by a {@link Result}'s `unwrap` / `unwrapErr` when the assertion is
|
|
4
|
+
* wrong on a *modeled* result — `unwrap()` on an `Err`, or `unwrapErr()` on an
|
|
5
|
+
* `Ok`.
|
|
6
|
+
*
|
|
7
|
+
* @remarks
|
|
8
|
+
* A `Defect` is never wrapped in an `UnwrapError`: its original cause is
|
|
9
|
+
* re-thrown (with its original stack) instead.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam E - the type of the {@link UnwrapError.error} it carries.
|
|
12
|
+
*/
|
|
13
|
+
var UnwrapError = class extends Error {
|
|
14
|
+
/**
|
|
15
|
+
* The offending value: the `Err` error for `unwrap()`, or the `Ok` value for
|
|
16
|
+
* `unwrapErr()`.
|
|
17
|
+
*/
|
|
18
|
+
error;
|
|
19
|
+
constructor(error) {
|
|
20
|
+
super("unthrown: called unwrap on a non-matching Result");
|
|
21
|
+
this.name = "UnwrapError";
|
|
22
|
+
this.error = error;
|
|
23
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Method holder for {@link Result}. Never instantiated with `new` and never
|
|
28
|
+
* exported; the builders below attach its prototype to plain objects. Every
|
|
29
|
+
* method types `this` as the public `Result` union, so it narrows on `tag`.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
var Res = class {
|
|
34
|
+
map(f) {
|
|
35
|
+
if (this.tag !== "Ok") return this;
|
|
36
|
+
try {
|
|
37
|
+
return okRes(f(this.value));
|
|
38
|
+
} catch (cause) {
|
|
39
|
+
return defectRes(cause);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
flatMap(f) {
|
|
43
|
+
if (this.tag !== "Ok") return this;
|
|
44
|
+
try {
|
|
45
|
+
return f(this.value);
|
|
46
|
+
} catch (cause) {
|
|
47
|
+
return defectRes(cause);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
tap(f) {
|
|
51
|
+
if (this.tag !== "Ok") return this;
|
|
52
|
+
try {
|
|
53
|
+
f(this.value);
|
|
54
|
+
return this;
|
|
55
|
+
} catch (cause) {
|
|
56
|
+
return defectRes(cause);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
as(value) {
|
|
60
|
+
if (this.tag !== "Ok") return this;
|
|
61
|
+
return okRes(value);
|
|
62
|
+
}
|
|
63
|
+
mapErr(f) {
|
|
64
|
+
if (this.tag !== "Err") return this;
|
|
65
|
+
try {
|
|
66
|
+
return errRes(f(this.error));
|
|
67
|
+
} catch (cause) {
|
|
68
|
+
return defectRes(cause);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
orElse(f) {
|
|
72
|
+
if (this.tag !== "Err") return this;
|
|
73
|
+
try {
|
|
74
|
+
return f(this.error);
|
|
75
|
+
} catch (cause) {
|
|
76
|
+
return defectRes(cause);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
recover(f) {
|
|
80
|
+
if (this.tag !== "Err") return this;
|
|
81
|
+
try {
|
|
82
|
+
return okRes(f(this.error));
|
|
83
|
+
} catch (cause) {
|
|
84
|
+
return defectRes(cause);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
tapErr(f) {
|
|
88
|
+
if (this.tag !== "Err") return this;
|
|
89
|
+
try {
|
|
90
|
+
f(this.error);
|
|
91
|
+
return this;
|
|
92
|
+
} catch (cause) {
|
|
93
|
+
return defectRes(cause);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
recoverDefect(f) {
|
|
97
|
+
if (this.tag !== "Defect") return this;
|
|
98
|
+
try {
|
|
99
|
+
return f(this.cause);
|
|
100
|
+
} catch (cause) {
|
|
101
|
+
return defectRes(cause);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
tapDefect(f) {
|
|
105
|
+
if (this.tag !== "Defect") return this;
|
|
106
|
+
try {
|
|
107
|
+
f(this.cause);
|
|
108
|
+
return this;
|
|
109
|
+
} catch (cause) {
|
|
110
|
+
return defectRes(cause);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
match(cases) {
|
|
114
|
+
switch (this.tag) {
|
|
115
|
+
case "Ok": return cases.ok(this.value);
|
|
116
|
+
case "Err": return cases.err(this.error);
|
|
117
|
+
case "Defect": return cases.defect(this.cause);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
unwrap() {
|
|
121
|
+
switch (this.tag) {
|
|
122
|
+
case "Ok": return this.value;
|
|
123
|
+
case "Err": throw new UnwrapError(this.error);
|
|
124
|
+
case "Defect": throw this.cause;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
unwrapErr() {
|
|
128
|
+
switch (this.tag) {
|
|
129
|
+
case "Err": return this.error;
|
|
130
|
+
case "Ok": throw new UnwrapError(this.value);
|
|
131
|
+
case "Defect": throw this.cause;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
unwrapOr(fallback) {
|
|
135
|
+
if (this.tag === "Ok") return this.value;
|
|
136
|
+
if (this.tag === "Defect") throw this.cause;
|
|
137
|
+
return fallback;
|
|
138
|
+
}
|
|
139
|
+
unwrapOrElse(f) {
|
|
140
|
+
if (this.tag === "Ok") return this.value;
|
|
141
|
+
if (this.tag === "Defect") throw this.cause;
|
|
142
|
+
return f(this.error);
|
|
143
|
+
}
|
|
144
|
+
getOrNull() {
|
|
145
|
+
if (this.tag === "Ok") return this.value;
|
|
146
|
+
if (this.tag === "Defect") throw this.cause;
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
getOrUndefined() {
|
|
150
|
+
if (this.tag === "Ok") return this.value;
|
|
151
|
+
if (this.tag === "Defect") throw this.cause;
|
|
152
|
+
}
|
|
153
|
+
isOk() {
|
|
154
|
+
return this.tag === "Ok";
|
|
155
|
+
}
|
|
156
|
+
isErr() {
|
|
157
|
+
return this.tag === "Err";
|
|
158
|
+
}
|
|
159
|
+
isDefect() {
|
|
160
|
+
return this.tag === "Defect";
|
|
161
|
+
}
|
|
162
|
+
toAsync() {
|
|
163
|
+
return new AsyncRes(Promise.resolve(this));
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
const RESULT_PROTO = Res.prototype;
|
|
167
|
+
/**
|
|
168
|
+
* Construct an `Ok` result — a plain object on the {@link Res} prototype.
|
|
169
|
+
*
|
|
170
|
+
* @internal
|
|
171
|
+
*/
|
|
172
|
+
function okRes(value) {
|
|
173
|
+
return Object.assign(Object.create(RESULT_PROTO), {
|
|
174
|
+
tag: "Ok",
|
|
175
|
+
value
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Construct an `Err` result.
|
|
180
|
+
*
|
|
181
|
+
* @internal
|
|
182
|
+
*/
|
|
183
|
+
function errRes(error) {
|
|
184
|
+
return Object.assign(Object.create(RESULT_PROTO), {
|
|
185
|
+
tag: "Err",
|
|
186
|
+
error
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Construct a `Defect` result.
|
|
191
|
+
*
|
|
192
|
+
* @internal
|
|
193
|
+
*/
|
|
194
|
+
function defectRes(cause) {
|
|
195
|
+
return Object.assign(Object.create(RESULT_PROTO), {
|
|
196
|
+
tag: "Defect",
|
|
197
|
+
cause
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* The sole runtime implementation of {@link AsyncResult}: wraps a
|
|
202
|
+
* `Promise<Result>` constructed never to reject. Operates on the public `Result`
|
|
203
|
+
* union (via `tag`), never on `Res` internals. Never re-exported from `index.ts`.
|
|
204
|
+
*
|
|
205
|
+
* @internal
|
|
206
|
+
*/
|
|
207
|
+
var AsyncRes = class AsyncRes {
|
|
208
|
+
promise;
|
|
209
|
+
constructor(promise) {
|
|
210
|
+
this.promise = promise;
|
|
211
|
+
}
|
|
212
|
+
then(onfulfilled, onrejected) {
|
|
213
|
+
return this.promise.then(onfulfilled, onrejected);
|
|
214
|
+
}
|
|
215
|
+
map(f) {
|
|
216
|
+
return new AsyncRes(this.promise.then((r) => {
|
|
217
|
+
if (r.tag !== "Ok") return r;
|
|
218
|
+
try {
|
|
219
|
+
return okRes(f(r.value));
|
|
220
|
+
} catch (cause) {
|
|
221
|
+
return defectRes(cause);
|
|
222
|
+
}
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
flatMap(f) {
|
|
226
|
+
return new AsyncRes(this.promise.then(async (r) => {
|
|
227
|
+
if (r.tag !== "Ok") return r;
|
|
228
|
+
try {
|
|
229
|
+
return await f(r.value);
|
|
230
|
+
} catch (cause) {
|
|
231
|
+
return defectRes(cause);
|
|
232
|
+
}
|
|
233
|
+
}));
|
|
234
|
+
}
|
|
235
|
+
tap(f) {
|
|
236
|
+
return new AsyncRes(this.promise.then((r) => {
|
|
237
|
+
if (r.tag !== "Ok") return r;
|
|
238
|
+
try {
|
|
239
|
+
f(r.value);
|
|
240
|
+
return r;
|
|
241
|
+
} catch (cause) {
|
|
242
|
+
return defectRes(cause);
|
|
243
|
+
}
|
|
244
|
+
}));
|
|
245
|
+
}
|
|
246
|
+
as(value) {
|
|
247
|
+
return new AsyncRes(this.promise.then((r) => r.tag === "Ok" ? okRes(value) : r));
|
|
248
|
+
}
|
|
249
|
+
mapErr(f) {
|
|
250
|
+
return new AsyncRes(this.promise.then((r) => {
|
|
251
|
+
if (r.tag !== "Err") return r;
|
|
252
|
+
try {
|
|
253
|
+
return errRes(f(r.error));
|
|
254
|
+
} catch (cause) {
|
|
255
|
+
return defectRes(cause);
|
|
256
|
+
}
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
orElse(f) {
|
|
260
|
+
return new AsyncRes(this.promise.then(async (r) => {
|
|
261
|
+
if (r.tag !== "Err") return r;
|
|
262
|
+
try {
|
|
263
|
+
return await f(r.error);
|
|
264
|
+
} catch (cause) {
|
|
265
|
+
return defectRes(cause);
|
|
266
|
+
}
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
recover(f) {
|
|
270
|
+
return new AsyncRes(this.promise.then((r) => {
|
|
271
|
+
if (r.tag !== "Err") return r;
|
|
272
|
+
try {
|
|
273
|
+
return okRes(f(r.error));
|
|
274
|
+
} catch (cause) {
|
|
275
|
+
return defectRes(cause);
|
|
276
|
+
}
|
|
277
|
+
}));
|
|
278
|
+
}
|
|
279
|
+
tapErr(f) {
|
|
280
|
+
return new AsyncRes(this.promise.then((r) => {
|
|
281
|
+
if (r.tag !== "Err") return r;
|
|
282
|
+
try {
|
|
283
|
+
f(r.error);
|
|
284
|
+
return r;
|
|
285
|
+
} catch (cause) {
|
|
286
|
+
return defectRes(cause);
|
|
287
|
+
}
|
|
288
|
+
}));
|
|
289
|
+
}
|
|
290
|
+
recoverDefect(f) {
|
|
291
|
+
return new AsyncRes(this.promise.then(async (r) => {
|
|
292
|
+
if (r.tag !== "Defect") return r;
|
|
293
|
+
try {
|
|
294
|
+
return await f(r.cause);
|
|
295
|
+
} catch (cause) {
|
|
296
|
+
return defectRes(cause);
|
|
297
|
+
}
|
|
298
|
+
}));
|
|
299
|
+
}
|
|
300
|
+
tapDefect(f) {
|
|
301
|
+
return new AsyncRes(this.promise.then((r) => {
|
|
302
|
+
if (r.tag !== "Defect") return r;
|
|
303
|
+
try {
|
|
304
|
+
f(r.cause);
|
|
305
|
+
return r;
|
|
306
|
+
} catch (cause) {
|
|
307
|
+
return defectRes(cause);
|
|
308
|
+
}
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
match(cases) {
|
|
312
|
+
return this.promise.then((r) => r.match(cases));
|
|
313
|
+
}
|
|
314
|
+
unwrap() {
|
|
315
|
+
return this.promise.then((r) => r.unwrap());
|
|
316
|
+
}
|
|
317
|
+
unwrapErr() {
|
|
318
|
+
return this.promise.then((r) => r.unwrapErr());
|
|
319
|
+
}
|
|
320
|
+
unwrapOr(fallback) {
|
|
321
|
+
return this.promise.then((r) => r.unwrapOr(fallback));
|
|
322
|
+
}
|
|
323
|
+
unwrapOrElse(f) {
|
|
324
|
+
return this.promise.then((r) => {
|
|
325
|
+
if (r.tag === "Ok") return r.value;
|
|
326
|
+
if (r.tag === "Defect") throw r.cause;
|
|
327
|
+
return f(r.error);
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
getOrNull() {
|
|
331
|
+
return this.promise.then((r) => r.getOrNull());
|
|
332
|
+
}
|
|
333
|
+
getOrUndefined() {
|
|
334
|
+
return this.promise.then((r) => r.getOrUndefined());
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
//#endregion
|
|
338
|
+
//#region src/constructors.ts
|
|
339
|
+
/**
|
|
340
|
+
* Construct a successful {@link Result}.
|
|
341
|
+
*
|
|
342
|
+
* @typeParam T - the success value type.
|
|
343
|
+
* @param value - the success value to wrap.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* import { ok } from "unthrown";
|
|
348
|
+
* ok(42).unwrap(); // 42
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
351
|
+
function ok(value) {
|
|
352
|
+
return okRes(value);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Construct a failed {@link Result} carrying a **modeled** error.
|
|
356
|
+
*
|
|
357
|
+
* @typeParam E - the modeled error type.
|
|
358
|
+
* @param error - the domain error to wrap.
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```ts
|
|
362
|
+
* import { err } from "unthrown";
|
|
363
|
+
* err("not_found").unwrapErr(); // "not_found"
|
|
364
|
+
* ```
|
|
365
|
+
*/
|
|
366
|
+
function err(error) {
|
|
367
|
+
return errRes(error);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Type guard: narrow a {@link Result} to its `Ok` variant, exposing `.value`.
|
|
371
|
+
*
|
|
372
|
+
* @returns `true` when `r` is `Ok`.
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```ts
|
|
376
|
+
* import { isOk, type Result } from "unthrown";
|
|
377
|
+
* declare const r: Result<number, string>;
|
|
378
|
+
* if (isOk(r)) r.value; // number, narrowed
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
function isOk(r) {
|
|
382
|
+
return r.tag === "Ok";
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Type guard: narrow a {@link Result} to its `Err` variant, exposing `.error`.
|
|
386
|
+
*
|
|
387
|
+
* @returns `true` when `r` is `Err`.
|
|
388
|
+
*/
|
|
389
|
+
function isErr(r) {
|
|
390
|
+
return r.tag === "Err";
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Type guard: narrow a {@link Result} to its `Defect` variant, exposing `.cause`.
|
|
394
|
+
*
|
|
395
|
+
* @returns `true` when `r` is a `Defect`.
|
|
396
|
+
*/
|
|
397
|
+
function isDefect(r) {
|
|
398
|
+
return r.tag === "Defect";
|
|
399
|
+
}
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/defect.ts
|
|
402
|
+
const DEFECT = Symbol("unthrown/defect");
|
|
403
|
+
/**
|
|
404
|
+
* Wrap a cause as a {@link Defect} — the value you return from a `qualify`
|
|
405
|
+
* function when a failure is **not** a modeled domain error.
|
|
406
|
+
*
|
|
407
|
+
* @param cause - the original thrown/rejected value.
|
|
408
|
+
* @returns an opaque defect marker carrying `cause`.
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* ```ts
|
|
412
|
+
* import { fromPromise, defect } from "unthrown";
|
|
413
|
+
*
|
|
414
|
+
* const user = fromPromise(fetchUser(id), (cause) =>
|
|
415
|
+
* cause instanceof NotFoundError ? cause : defect(cause),
|
|
416
|
+
* );
|
|
417
|
+
* ```
|
|
418
|
+
*/
|
|
419
|
+
function defect(cause) {
|
|
420
|
+
return {
|
|
421
|
+
[DEFECT]: true,
|
|
422
|
+
cause
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Internal guard for the qualify-time marker. Distinct from the public
|
|
427
|
+
* {@link isDefect} state guard — this one narrows the `E | Defect` union a
|
|
428
|
+
* `qualify` function returns, not a `Result`.
|
|
429
|
+
*
|
|
430
|
+
* @internal
|
|
431
|
+
*/
|
|
432
|
+
function isDefectMarker(x) {
|
|
433
|
+
return typeof x === "object" && x !== null && x[DEFECT] === true;
|
|
434
|
+
}
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region src/interop.ts
|
|
437
|
+
/**
|
|
438
|
+
* Bridge a nullable value into a {@link Result}: absence becomes a **modeled**
|
|
439
|
+
* `Err`. The sanctioned alternative to an `Option` type.
|
|
440
|
+
*
|
|
441
|
+
* @remarks
|
|
442
|
+
* `null` and `undefined` map to `err(onAbsent())`; any other value (including
|
|
443
|
+
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
444
|
+
*
|
|
445
|
+
* @typeParam T - the (nullable) value type.
|
|
446
|
+
* @typeParam E - the error produced when the value is absent.
|
|
447
|
+
* @param value - the possibly-absent value.
|
|
448
|
+
* @param onAbsent - lazily produces the error for the absent case.
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* ```ts
|
|
452
|
+
* import { fromNullable } from "unthrown";
|
|
453
|
+
* fromNullable(map.get(key), () => "missing").unwrap();
|
|
454
|
+
* ```
|
|
455
|
+
*/
|
|
456
|
+
function fromNullable(value, onAbsent) {
|
|
457
|
+
return value === null || value === void 0 ? err(onAbsent()) : ok(value);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Wrap a throwing synchronous function so it returns a {@link Result} instead of
|
|
461
|
+
* throwing.
|
|
462
|
+
*
|
|
463
|
+
* @remarks
|
|
464
|
+
* `qualify` **must** triage every thrown cause into a modeled error `E` or a
|
|
465
|
+
* {@link Defect} (via {@link defect}) — there is no path that leaves `unknown`
|
|
466
|
+
* in `E`. A throw inside `qualify` itself is treated as a `Defect`.
|
|
467
|
+
*
|
|
468
|
+
* @typeParam A - the wrapped function's argument tuple.
|
|
469
|
+
* @typeParam T - the wrapped function's return type.
|
|
470
|
+
* @typeParam E - the modeled error type.
|
|
471
|
+
* @param fn - the throwing function to wrap.
|
|
472
|
+
* @param qualify - triages a thrown cause into `E` or a `Defect`.
|
|
473
|
+
* @returns a function with the same arguments returning `Result<T, E>`.
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```ts
|
|
477
|
+
* import { fromThrowable, defect } from "unthrown";
|
|
478
|
+
* const parse = fromThrowable(JSON.parse, (cause) => defect(cause));
|
|
479
|
+
* parse("{}").unwrap();
|
|
480
|
+
* ```
|
|
481
|
+
*/
|
|
482
|
+
function fromThrowable(fn, qualify) {
|
|
483
|
+
return (...args) => {
|
|
484
|
+
try {
|
|
485
|
+
return ok(fn(...args));
|
|
486
|
+
} catch (cause) {
|
|
487
|
+
return qualifyToResult(cause, qualify);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Wrap a `Promise` (or a thunk producing one) as an {@link AsyncResult}, forcing
|
|
493
|
+
* every rejection to be triaged.
|
|
494
|
+
*
|
|
495
|
+
* @remarks
|
|
496
|
+
* `qualify` **must** map each rejection cause into a modeled error `E` or a
|
|
497
|
+
* {@link Defect}. The returned `AsyncResult`'s internal promise never rejects;
|
|
498
|
+
* `await`-ing it always yields a `Result`. A throw inside `qualify` is itself a
|
|
499
|
+
* `Defect`.
|
|
500
|
+
*
|
|
501
|
+
* @typeParam T - the resolved value type.
|
|
502
|
+
* @typeParam E - the modeled error type.
|
|
503
|
+
* @param promise - the promise, or a thunk returning one.
|
|
504
|
+
* @param qualify - triages a rejection cause into `E` or a `Defect`.
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* ```ts
|
|
508
|
+
* import { fromPromise, defect } from "unthrown";
|
|
509
|
+
* const user = await fromPromise(fetchUser(id), (cause) =>
|
|
510
|
+
* cause instanceof NotFoundError ? ("not_found" as const) : defect(cause),
|
|
511
|
+
* );
|
|
512
|
+
* ```
|
|
513
|
+
*/
|
|
514
|
+
function fromPromise(promise, qualify) {
|
|
515
|
+
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : promise).then((value) => okRes(value), (cause) => qualifyToResult(cause, qualify)));
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Wrap a `Promise` asserted **not** to fail in any modeled way: any rejection
|
|
519
|
+
* becomes a `Defect`.
|
|
520
|
+
*
|
|
521
|
+
* @remarks
|
|
522
|
+
* Use this only when a rejection genuinely indicates a bug rather than an
|
|
523
|
+
* anticipated outcome — the error channel is `never`, so there is nothing to
|
|
524
|
+
* triage. (`await`-ing still yields a `Result`; it never throws.)
|
|
525
|
+
*
|
|
526
|
+
* @typeParam T - the resolved value type.
|
|
527
|
+
* @param promise - the promise, or a thunk returning one.
|
|
528
|
+
*/
|
|
529
|
+
function fromSafePromise(promise) {
|
|
530
|
+
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : promise).then((value) => okRes(value), (cause) => defectRes(cause)));
|
|
531
|
+
}
|
|
532
|
+
function qualifyToResult(cause, qualify) {
|
|
533
|
+
try {
|
|
534
|
+
const q = qualify(cause);
|
|
535
|
+
return isDefectMarker(q) ? defectRes(q.cause) : errRes(q);
|
|
536
|
+
} catch (qErr) {
|
|
537
|
+
return defectRes(qErr);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Collect a tuple of {@link Result}s into a single `Result` of the tuple of
|
|
542
|
+
* success values.
|
|
543
|
+
*
|
|
544
|
+
* @remarks
|
|
545
|
+
* Short-circuits on the **first** `Err` (later entries are not inspected for
|
|
546
|
+
* their error); any `Defect` present **dominates**, winning even over an earlier
|
|
547
|
+
* `Err`. Positional types are preserved, so `all([ok(1), ok("a")])` is
|
|
548
|
+
* `Result<[number, string], …>`.
|
|
549
|
+
*
|
|
550
|
+
* @typeParam Rs - the tuple of input `Result` types.
|
|
551
|
+
* @param results - the results to combine.
|
|
552
|
+
*
|
|
553
|
+
* @example
|
|
554
|
+
* ```ts
|
|
555
|
+
* import { all, ok } from "unthrown";
|
|
556
|
+
* all([ok(1), ok("a"), ok(true)]).unwrap(); // [1, "a", true]
|
|
557
|
+
* ```
|
|
558
|
+
*/
|
|
559
|
+
function all(results) {
|
|
560
|
+
const values = [];
|
|
561
|
+
let firstErr;
|
|
562
|
+
let firstDefect;
|
|
563
|
+
for (const r of results) if (r.tag === "Defect") firstDefect ??= r;
|
|
564
|
+
else if (r.tag === "Err") firstErr ??= r;
|
|
565
|
+
else values.push(r.value);
|
|
566
|
+
if (firstDefect) return firstDefect;
|
|
567
|
+
if (firstErr) return firstErr;
|
|
568
|
+
return ok(values);
|
|
569
|
+
}
|
|
570
|
+
//#endregion
|
|
571
|
+
//#region src/facade.ts
|
|
572
|
+
/**
|
|
573
|
+
* Companion object grouping the standalone entry points under a single,
|
|
574
|
+
* discoverable namespace: {@link Result.ok}, {@link Result.err},
|
|
575
|
+
* {@link Result.defect}, {@link Result.fromNullable}, {@link Result.fromThrowable},
|
|
576
|
+
* {@link Result.fromPromise}, {@link Result.fromSafePromise}, {@link Result.all},
|
|
577
|
+
* {@link Result.isOk}, {@link Result.isErr}, {@link Result.isDefect}.
|
|
578
|
+
*
|
|
579
|
+
* @remarks
|
|
580
|
+
* Purely additive sugar — each member **is** the corresponding free function.
|
|
581
|
+
* The free functions remain the primary, tree-shakeable API; importing only
|
|
582
|
+
* `{ ok }` never pulls this object in. The value `Result` and the type
|
|
583
|
+
* {@link Result} share one name (the companion-object pattern).
|
|
584
|
+
*
|
|
585
|
+
* @example
|
|
586
|
+
* ```ts
|
|
587
|
+
* import { Result } from "unthrown";
|
|
588
|
+
* Result.ok(1).flatMap((n) => Result.ok(n + 1)).unwrap(); // 2
|
|
589
|
+
* ```
|
|
590
|
+
*/
|
|
591
|
+
const Result = {
|
|
592
|
+
ok,
|
|
593
|
+
err,
|
|
594
|
+
defect,
|
|
595
|
+
fromNullable,
|
|
596
|
+
fromThrowable,
|
|
597
|
+
fromPromise,
|
|
598
|
+
fromSafePromise,
|
|
599
|
+
all,
|
|
600
|
+
isOk,
|
|
601
|
+
isErr,
|
|
602
|
+
isDefect
|
|
603
|
+
};
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/tagged.ts
|
|
606
|
+
/**
|
|
607
|
+
* Build a base class for a tagged error — a class extending `Error` with a
|
|
608
|
+
* `_tag` string discriminant, in the style of Effect's `Data.TaggedError`.
|
|
609
|
+
*
|
|
610
|
+
* @remarks
|
|
611
|
+
* Extend the returned class to declare a concrete error. Supply the payload with
|
|
612
|
+
* an instantiation expression; omit it for a payload-less error. A `message`
|
|
613
|
+
* field in the payload is forwarded to `Error`. The `_tag` always reflects
|
|
614
|
+
* `tag` and cannot be overridden by the payload.
|
|
615
|
+
*
|
|
616
|
+
* @typeParam Tag - the string literal discriminant.
|
|
617
|
+
* @param tag - the discriminant value, also used as the error `name`.
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* ```ts
|
|
621
|
+
* class NotFound extends TaggedError("NotFound") {}
|
|
622
|
+
* class HttpError extends TaggedError("HttpError")<{ status: number }> {}
|
|
623
|
+
*
|
|
624
|
+
* new NotFound()._tag; // "NotFound"
|
|
625
|
+
* new HttpError({ status: 500 }).status; // 500
|
|
626
|
+
* ```
|
|
627
|
+
*/
|
|
628
|
+
function TaggedError(tag) {
|
|
629
|
+
class TaggedErrorBase extends Error {
|
|
630
|
+
_tag;
|
|
631
|
+
constructor(props) {
|
|
632
|
+
super(typeof props?.["message"] === "string" ? props["message"] : void 0);
|
|
633
|
+
if (props) Object.assign(this, props);
|
|
634
|
+
this._tag = tag;
|
|
635
|
+
this.name = tag;
|
|
636
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return TaggedErrorBase;
|
|
640
|
+
}
|
|
641
|
+
function matchTags(result, handlers) {
|
|
642
|
+
const onErr = (error) => {
|
|
643
|
+
const handler = handlers[error._tag];
|
|
644
|
+
return handler(error);
|
|
645
|
+
};
|
|
646
|
+
return result.match({
|
|
647
|
+
ok: handlers.Ok,
|
|
648
|
+
err: onErr,
|
|
649
|
+
defect: handlers.Defect
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
//#endregion
|
|
653
|
+
export { Result, TaggedError, UnwrapError, all, defect, err, fromNullable, fromPromise, fromSafePromise, fromThrowable, isDefect, isErr, isOk, matchTags, ok };
|
|
654
|
+
|
|
655
|
+
//# sourceMappingURL=index.mjs.map
|