@pencroff-lab/kore 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.
Files changed (51) hide show
  1. package/LICENSE +201 -0
  2. package/dist/cjs/index.d.ts +3 -0
  3. package/dist/cjs/index.d.ts.map +1 -0
  4. package/dist/cjs/index.js +19 -0
  5. package/dist/cjs/index.js.map +1 -0
  6. package/dist/cjs/package.json +3 -0
  7. package/dist/cjs/src/types/err.d.ts +1116 -0
  8. package/dist/cjs/src/types/err.d.ts.map +1 -0
  9. package/dist/cjs/src/types/err.js +1324 -0
  10. package/dist/cjs/src/types/err.js.map +1 -0
  11. package/dist/cjs/src/types/index.d.ts +3 -0
  12. package/dist/cjs/src/types/index.d.ts.map +1 -0
  13. package/dist/cjs/src/types/index.js +19 -0
  14. package/dist/cjs/src/types/index.js.map +1 -0
  15. package/dist/cjs/src/types/outcome.d.ts +1002 -0
  16. package/dist/cjs/src/types/outcome.d.ts.map +1 -0
  17. package/dist/cjs/src/types/outcome.js +958 -0
  18. package/dist/cjs/src/types/outcome.js.map +1 -0
  19. package/dist/cjs/src/utils/format_dt.d.ts +9 -0
  20. package/dist/cjs/src/utils/format_dt.d.ts.map +1 -0
  21. package/dist/cjs/src/utils/format_dt.js +29 -0
  22. package/dist/cjs/src/utils/format_dt.js.map +1 -0
  23. package/dist/cjs/src/utils/index.d.ts +2 -0
  24. package/dist/cjs/src/utils/index.d.ts.map +1 -0
  25. package/dist/cjs/src/utils/index.js +18 -0
  26. package/dist/cjs/src/utils/index.js.map +1 -0
  27. package/dist/esm/index.d.ts +3 -0
  28. package/dist/esm/index.d.ts.map +1 -0
  29. package/dist/esm/index.js +3 -0
  30. package/dist/esm/index.js.map +1 -0
  31. package/dist/esm/src/types/err.d.ts +1116 -0
  32. package/dist/esm/src/types/err.d.ts.map +1 -0
  33. package/dist/esm/src/types/err.js +1320 -0
  34. package/dist/esm/src/types/err.js.map +1 -0
  35. package/dist/esm/src/types/index.d.ts +3 -0
  36. package/dist/esm/src/types/index.d.ts.map +1 -0
  37. package/dist/esm/src/types/index.js +3 -0
  38. package/dist/esm/src/types/index.js.map +1 -0
  39. package/dist/esm/src/types/outcome.d.ts +1002 -0
  40. package/dist/esm/src/types/outcome.d.ts.map +1 -0
  41. package/dist/esm/src/types/outcome.js +954 -0
  42. package/dist/esm/src/types/outcome.js.map +1 -0
  43. package/dist/esm/src/utils/format_dt.d.ts +9 -0
  44. package/dist/esm/src/utils/format_dt.d.ts.map +1 -0
  45. package/dist/esm/src/utils/format_dt.js +26 -0
  46. package/dist/esm/src/utils/format_dt.js.map +1 -0
  47. package/dist/esm/src/utils/index.d.ts +2 -0
  48. package/dist/esm/src/utils/index.d.ts.map +1 -0
  49. package/dist/esm/src/utils/index.js +2 -0
  50. package/dist/esm/src/utils/index.js.map +1 -0
  51. package/package.json +56 -0
@@ -0,0 +1,958 @@
1
+ "use strict";
2
+ /**
3
+ * @fileoverview
4
+ * Monadic container for handling success and error states using tuple-first API design.
5
+ *
6
+ * This module provides the `Outcome<T>` class and related types for implementing
7
+ * type-safe error handling without exceptions. All operations favor immutability.
8
+ *
9
+ * @example Basic usage
10
+ * ```typescript
11
+ * import { Outcome } from './outcome';
12
+ *
13
+ * const [val, err] = Outcome.from(() => [42, null]).toTuple();
14
+ * ```
15
+ *
16
+ * @example Migration from throwing functions
17
+ * ```typescript
18
+ * // Before (throwing):
19
+ * function getUser(id: string): User {
20
+ * const user = db.find(id);
21
+ * if (!user) throw new Error("Not found");
22
+ * return user;
23
+ * }
24
+ * try {
25
+ * const user = getUser("123");
26
+ * console.log(user.name);
27
+ * } catch (e) {
28
+ * console.error(e.message);
29
+ * }
30
+ *
31
+ * // After (Outcome):
32
+ * function getUser(id: string): Outcome<User> {
33
+ * return Outcome.from(() => {
34
+ * const user = db.find(id);
35
+ * if (!user) return Err.from("Not found", "NOT_FOUND");
36
+ * return [user, null];
37
+ * });
38
+ * }
39
+ * const [user, err] = getUser("123").toTuple();
40
+ * if (err) {
41
+ * console.error(err.message);
42
+ * return;
43
+ * }
44
+ * console.log(user.name);
45
+ * ```
46
+ */
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.Outcome = void 0;
49
+ const err_1 = require("./err");
50
+ /**
51
+ * A monadic container for handling success and error states.
52
+ *
53
+ * `Outcome<T>` provides a type-safe way to handle operations that can fail,
54
+ * using tuples as the primary interface. All instances are immutable.
55
+ *
56
+ * ## Core Patterns
57
+ *
58
+ * - **Construction**: Use static methods `ok()`, `err()`, `from()`, `fromAsync()`
59
+ * - **Inspection**: Use `isOk`, `isErr`, `value`, `error` properties
60
+ * - **Transformation**: Use `map()`, `mapErr()` for chained operations
61
+ * - **Extraction**: Use `toTuple()` for final value extraction
62
+ *
63
+ * @example Basic usage
64
+ * ```typescript
65
+ * const outcome = Outcome.from(() => {
66
+ * if (Math.random() > 0.5) return [42, null];
67
+ * return Err.from('Bad luck');
68
+ * });
69
+ *
70
+ * const [value, err] = outcome.toTuple();
71
+ * if (err) {
72
+ * console.error('Failed:', err.message);
73
+ * } else {
74
+ * console.log('Success:', value);
75
+ * }
76
+ * ```
77
+ *
78
+ * @example Chaining transformations
79
+ * ```typescript
80
+ * const result = Outcome.ok(5)
81
+ * .map(n => [n * 2, null])
82
+ * .map(n => [n.toString(), null])
83
+ * .toTuple();
84
+ * // result: ['10', null]
85
+ * ```
86
+ *
87
+ * @typeParam T - The type of the success value
88
+ */
89
+ class Outcome {
90
+ /**
91
+ * Discriminator property for type narrowing.
92
+ * `true` for success outcomes, `false` for error outcomes.
93
+ */
94
+ isOk;
95
+ /** Internal tuple storage */
96
+ _tuple;
97
+ /**
98
+ * Private constructor - use static factory methods.
99
+ * @internal
100
+ */
101
+ constructor(tuple) {
102
+ this._tuple = tuple;
103
+ this.isOk = tuple[1] === null;
104
+ }
105
+ /**
106
+ * Process a CallbackReturn value into an Outcome.
107
+ * Handles discrimination: Err → null (void) → tuple destructure.
108
+ * @internal
109
+ */
110
+ static _processCallbackReturn(result) {
111
+ // Case 1: Direct Err return (shorthand)
112
+ if (err_1.Err.isErr(result)) {
113
+ return new Outcome([null, result]);
114
+ }
115
+ // Case 2: null = void success
116
+ if (result === null) {
117
+ return new Outcome([null, null]);
118
+ }
119
+ // Case 3: Tuple [T, null] | [null, Err]
120
+ const [value, error] = result;
121
+ if (err_1.Err.isErr(error)) {
122
+ return new Outcome([null, error]);
123
+ }
124
+ return new Outcome([value, null]);
125
+ }
126
+ // ══════════════════════════════════════════════════════════════════════════
127
+ // Static Constructors
128
+ // ══════════════════════════════════════════════════════════════════════════
129
+ /**
130
+ * Create a success Outcome with the given value.
131
+ *
132
+ * @param value - The success value
133
+ * @returns Outcome containing the success value
134
+ *
135
+ * @example
136
+ * ```typescript
137
+ * const outcome = Outcome.ok(42);
138
+ * console.log(outcome.isOk); // true
139
+ * console.log(outcome.value); // 42
140
+ *
141
+ * const [val, err] = outcome.toTuple();
142
+ * // val: 42, err: null
143
+ * ```
144
+ */
145
+ static ok(value) {
146
+ return new Outcome([value, null]);
147
+ }
148
+ /**
149
+ * Implementation signature for err().
150
+ * @internal
151
+ */
152
+ static err(messageOrErr, codeOrOptionsOrErr, options) {
153
+ // If first arg is already an Err, use it directly
154
+ if (err_1.Err.isErr(messageOrErr)) {
155
+ return new Outcome([null, messageOrErr]);
156
+ }
157
+ const message = messageOrErr;
158
+ // If second arg is Err or Error, wrap it
159
+ if (err_1.Err.isErr(codeOrOptionsOrErr) || codeOrOptionsOrErr instanceof Error) {
160
+ const wrapped = err_1.Err.wrap(message, codeOrOptionsOrErr, options);
161
+ return new Outcome([null, wrapped]);
162
+ }
163
+ // Otherwise, create new Err with message and options/code
164
+ // biome-ignore lint/suspicious/noExplicitAny: overloaded argument handling
165
+ const err = err_1.Err.from(message, codeOrOptionsOrErr);
166
+ return new Outcome([null, err]);
167
+ }
168
+ /**
169
+ * Create a success Outcome with null value (void success).
170
+ *
171
+ * Use for operations that succeed but have no meaningful return value.
172
+ *
173
+ * @returns Outcome<null> representing void success
174
+ *
175
+ * @remarks
176
+ * Returns `Outcome<null>` (not `Outcome<undefined>` or `Outcome<void>`).
177
+ * This is intentional for consistency with the tuple pattern where `null`
178
+ * indicates absence of error in `[value, null]`.
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * function logMessage(msg: string): Outcome<null> {
183
+ * console.log(msg);
184
+ * return Outcome.unit();
185
+ * }
186
+ *
187
+ * const outcome = logMessage('Hello');
188
+ * console.log(outcome.isOk); // true
189
+ * console.log(outcome.value); // null
190
+ * ```
191
+ */
192
+ static unit() {
193
+ return new Outcome([null, null]);
194
+ }
195
+ /**
196
+ * Create an Outcome from a callback that returns `CallbackReturn<T>`.
197
+ *
198
+ * The callback can return:
199
+ * - `[value, null]` - success with value
200
+ * - `[null, Err]` - error as tuple
201
+ * - `null` - void success
202
+ * - `Err` - error directly
203
+ *
204
+ * If the callback throws, the exception is caught and wrapped in an error Outcome.
205
+ *
206
+ * @param fn - Callback returning CallbackReturn<T>
207
+ * @returns Outcome<T>
208
+ *
209
+ * @see {@link fromAsync} for the async version
210
+ *
211
+ * @example Success with value
212
+ * ```typescript
213
+ * const outcome = Outcome.from(() => {
214
+ * return [42, null];
215
+ * });
216
+ * console.log(outcome.value); // 42
217
+ * ```
218
+ *
219
+ * @example Error shorthand
220
+ * ```typescript
221
+ * const outcome = Outcome.from(() => {
222
+ * if (invalid) return Err.from('Invalid input');
223
+ * return [result, null];
224
+ * });
225
+ * ```
226
+ *
227
+ * @example Catching throws from external libraries
228
+ * ```typescript
229
+ * const outcome = Outcome.from(() => {
230
+ * const data = JSON.parse(untrustedInput); // may throw
231
+ * return [data, null];
232
+ * });
233
+ * // If JSON.parse throws, outcome.isErr === true
234
+ * ```
235
+ */
236
+ static from(fn) {
237
+ try {
238
+ const result = fn();
239
+ return Outcome._processCallbackReturn(result);
240
+ }
241
+ catch (e) {
242
+ return new Outcome([null, err_1.Err.from(e)]);
243
+ }
244
+ }
245
+ /**
246
+ * Create an Outcome from an async callback that returns `Promise<CallbackReturn<T>>`.
247
+ *
248
+ * Async version of `from()` with identical semantics.
249
+ *
250
+ * @param fn - Async callback returning Promise<CallbackReturn<T>>
251
+ * @returns Promise<Outcome<T>>
252
+ *
253
+ * @see {@link from} for the synchronous version
254
+ *
255
+ * @example Async operation
256
+ * ```typescript
257
+ * const outcome = await Outcome.fromAsync(async () => {
258
+ * const response = await fetch('/api/data');
259
+ * if (!response.ok) {
260
+ * return Err.from('Request failed', { code: 'HTTP_ERROR' });
261
+ * }
262
+ * const data = await response.json();
263
+ * return [data, null];
264
+ * });
265
+ * ```
266
+ *
267
+ * @example With error aggregation
268
+ * ```typescript
269
+ * const outcome = await Outcome.fromAsync(async () => {
270
+ * let errors = Err.aggregate('Batch failed');
271
+ *
272
+ * const [a, errA] = await taskA().toTuple();
273
+ * if (errA) errors = errors.add(errA);
274
+ *
275
+ * const [b, errB] = await taskB().toTuple();
276
+ * if (errB) errors = errors.add(errB);
277
+ *
278
+ * if (errors.count > 0) return errors;
279
+ * return [{ a, b }, null];
280
+ * });
281
+ * ```
282
+ */
283
+ static async fromAsync(fn) {
284
+ try {
285
+ const result = await fn();
286
+ return Outcome._processCallbackReturn(result);
287
+ }
288
+ catch (e) {
289
+ return new Outcome([null, err_1.Err.from(e)]);
290
+ }
291
+ }
292
+ /**
293
+ * Create an Outcome from an existing ResultTuple.
294
+ *
295
+ * Useful for deserializing Outcomes or converting from external tuple sources.
296
+ *
297
+ * @param tuple - A ResultTuple<T>
298
+ * @returns Outcome<T>
299
+ *
300
+ * @see {@link toTuple} for extracting the tuple from an Outcome
301
+ *
302
+ * @example Deserializing from JSON
303
+ * ```typescript
304
+ * const json = '["hello", null]';
305
+ * const tuple = JSON.parse(json) as ResultTuple<string>;
306
+ * const outcome = Outcome.fromTuple(tuple);
307
+ * console.log(outcome.value); // 'hello'
308
+ * ```
309
+ *
310
+ * @example Round-trip serialization
311
+ * ```typescript
312
+ * const original = Outcome.ok(42);
313
+ * const json = JSON.stringify(original.toJSON());
314
+ * const restored = Outcome.fromTuple(JSON.parse(json));
315
+ * console.log(restored.value); // 42
316
+ * ```
317
+ */
318
+ static fromTuple(tuple) {
319
+ return new Outcome(tuple);
320
+ }
321
+ static fromJSON(payload) {
322
+ return Outcome.from(() => {
323
+ if (!Array.isArray(payload) || payload.length !== 2) {
324
+ return err_1.Err.from("Invalid Outcome JSON");
325
+ }
326
+ const [value, error] = payload;
327
+ if (error === null) {
328
+ return [value, null];
329
+ }
330
+ return [null, err_1.Err.fromJSON(error)];
331
+ });
332
+ }
333
+ // ══════════════════════════════════════════════════════════════════════════
334
+ // Combinators
335
+ // ══════════════════════════════════════════════════════════════════════════
336
+ /**
337
+ * Combines multiple Outcomes, succeeding if all succeed with an array of values.
338
+ * If any Outcome fails, returns an Err containing all failures aggregated via Err.aggregate().
339
+ *
340
+ * This is useful for validation scenarios where you need to collect all errors.
341
+ *
342
+ * For empty arrays, returns `Outcome.ok([])` (vacuous truth).
343
+ *
344
+ * @param outcomes - Array of Outcomes to combine
345
+ * @returns Outcome containing array of all success values, or aggregate error
346
+ *
347
+ * @remarks
348
+ * Time complexity: O(n) where n is the number of outcomes.
349
+ * All outcomes are evaluated (non-short-circuiting) to collect all errors.
350
+ *
351
+ * @example All succeed
352
+ * ```typescript
353
+ * const outcomes = [Outcome.ok(1), Outcome.ok(2), Outcome.ok(3)];
354
+ * const combined = Outcome.all(outcomes);
355
+ * console.log(combined.value); // [1, 2, 3]
356
+ * ```
357
+ *
358
+ * @example One fails
359
+ * ```typescript
360
+ * const outcomes = [
361
+ * Outcome.ok(1),
362
+ * Outcome.err('Failed'),
363
+ * Outcome.ok(3)
364
+ * ];
365
+ * const combined = Outcome.all(outcomes);
366
+ * console.log(combined.isErr); // true
367
+ * console.log(combined.error?.message); // 'Failed'
368
+ * ```
369
+ *
370
+ * @example Many fails
371
+ * ```typescript
372
+ * const mixed = [
373
+ * Outcome.ok(1),
374
+ * Outcome.err("Error A"),
375
+ * Outcome.err("Error B")
376
+ * ];
377
+ * const failed = Outcome.all(mixed);
378
+ * console.log(failed.isErr); // true
379
+ * // Error contains both "Error A" and "Error B"
380
+ * ```
381
+ *
382
+ * @example Empty array
383
+ * ```typescript
384
+ * const combined = Outcome.all([]);
385
+ * console.log(combined.value); // []
386
+ * ```
387
+ */
388
+ static all(outcomes) {
389
+ const values = [];
390
+ const errors = [];
391
+ for (const outcome of outcomes) {
392
+ if (outcome.isErr) {
393
+ //
394
+ errors.push(outcome._tuple[1]);
395
+ continue;
396
+ }
397
+ values.push(outcome._tuple[0]);
398
+ }
399
+ if (errors.length === 1) {
400
+ return new Outcome([null, errors[0]]);
401
+ }
402
+ if (errors.length > 0) {
403
+ return Outcome.err(err_1.Err.aggregate("Multiple failed", errors));
404
+ }
405
+ return new Outcome([values, null]);
406
+ }
407
+ /**
408
+ * Return the first successful Outcome from an array.
409
+ *
410
+ * Returns the first success encountered.
411
+ * Returns an aggregate error if ALL outcomes fail.
412
+ *
413
+ * For empty arrays, returns an error (no value to return).
414
+ *
415
+ * @param outcomes - Array of Outcomes to check
416
+ * @returns First successful Outcome, or aggregate of all errors
417
+ *
418
+ * @remarks
419
+ * Time complexity: O(n) worst case, but short-circuits on first success.
420
+ * Best case: O(1) if first outcome is successful.
421
+ *
422
+ * @example First success wins
423
+ * ```typescript
424
+ * const outcomes = [
425
+ * Outcome.err('First failed'),
426
+ * Outcome.ok(42),
427
+ * Outcome.ok(100)
428
+ * ];
429
+ * const result = Outcome.any(outcomes);
430
+ * console.log(result.value); // 42
431
+ * ```
432
+ *
433
+ * @example All fail
434
+ * ```typescript
435
+ * const outcomes = [
436
+ * Outcome.err('Error 1'),
437
+ * Outcome.err('Error 2')
438
+ * ];
439
+ * const result = Outcome.any(outcomes);
440
+ * console.log(result.isErr); // true
441
+ * console.log(result.error?.isAggregate); // true
442
+ * ```
443
+ *
444
+ * @example Empty array
445
+ * ```typescript
446
+ * const result = Outcome.any([]);
447
+ * console.log(result.isErr); // true
448
+ * console.log(result.error?.message); // 'No outcomes provided'
449
+ * ```
450
+ */
451
+ static any(outcomes) {
452
+ if (outcomes.length === 0) {
453
+ return Outcome.err("No outcomes provided", "EMPTY_INPUT");
454
+ }
455
+ const errors = [];
456
+ for (const outcome of outcomes) {
457
+ if (outcome.isOk) {
458
+ return outcome;
459
+ }
460
+ errors.push(outcome._tuple[1]);
461
+ }
462
+ const aggregate = err_1.Err.aggregate("All failed", errors);
463
+ return new Outcome([null, aggregate]);
464
+ }
465
+ // ══════════════════════════════════════════════════════════════════════════
466
+ // Instance Accessors
467
+ // ══════════════════════════════════════════════════════════════════════════
468
+ /**
469
+ * Whether this Outcome is in error state.
470
+ *
471
+ * @example
472
+ * ```typescript
473
+ * const success = Outcome.ok(42);
474
+ * const failure = Outcome.err('Failed');
475
+ *
476
+ * console.log(success.isErr); // false
477
+ * console.log(failure.isErr); // true
478
+ * ```
479
+ */
480
+ get isErr() {
481
+ return !this.isOk;
482
+ }
483
+ /**
484
+ * The success value, or null if in error state.
485
+ *
486
+ * @example
487
+ * ```typescript
488
+ * const success = Outcome.ok(42);
489
+ * const failure = Outcome.err('Failed');
490
+ *
491
+ * console.log(success.value); // 42
492
+ * console.log(failure.value); // null
493
+ * ```
494
+ */
495
+ get value() {
496
+ return this._tuple[0];
497
+ }
498
+ /**
499
+ * The error, or null if in success state.
500
+ *
501
+ * @example
502
+ * ```typescript
503
+ * const success = Outcome.ok(42);
504
+ * const failure = Outcome.err('Failed');
505
+ *
506
+ * console.log(success.error); // null
507
+ * console.log(failure.error?.message); // 'Failed'
508
+ * ```
509
+ */
510
+ get error() {
511
+ return this._tuple[1];
512
+ }
513
+ // ══════════════════════════════════════════════════════════════════════════
514
+ // Transformation
515
+ // ══════════════════════════════════════════════════════════════════════════
516
+ /**
517
+ * Transform the success value using a callback.
518
+ *
519
+ * Only called if this Outcome is successful. Errors pass through unchanged.
520
+ * The callback can return any `CallbackReturn<U>` pattern.
521
+ * If the callback throws, the exception is caught and wrapped.
522
+ *
523
+ * @param fn - Transformation function receiving the success value
524
+ * @returns New Outcome with transformed value or original/new error
525
+ *
526
+ * @see {@link mapAsync} for the async version
527
+ * @see {@link mapErr} for transforming or recovering from errors
528
+ *
529
+ * @example Simple transformation
530
+ * ```typescript
531
+ * const outcome = Outcome.ok(5)
532
+ * .map(n => [n * 2, null])
533
+ * .map(n => [n.toString(), null]);
534
+ *
535
+ * console.log(outcome.value); // '10'
536
+ * ```
537
+ *
538
+ * @example Transformation that can fail
539
+ * ```typescript
540
+ * const outcome = Outcome.ok('{"name":"John"}')
541
+ * .map(json => {
542
+ * try {
543
+ * return [JSON.parse(json), null];
544
+ * } catch {
545
+ * return Err.from('Invalid JSON');
546
+ * }
547
+ * });
548
+ * ```
549
+ *
550
+ * @example Error passes through
551
+ * ```typescript
552
+ * const outcome = Outcome.err('Original error')
553
+ * .map(v => [v * 2, null]); // Never called
554
+ *
555
+ * console.log(outcome.error?.message); // 'Original error'
556
+ * ```
557
+ */
558
+ map(fn) {
559
+ if (this.isErr) {
560
+ return new Outcome([null, this._tuple[1]]);
561
+ }
562
+ try {
563
+ const result = fn(this._tuple[0]);
564
+ return Outcome._processCallbackReturn(result);
565
+ }
566
+ catch (e) {
567
+ return new Outcome([null, err_1.Err.from(e)]);
568
+ }
569
+ }
570
+ /**
571
+ * Async version of `map()`.
572
+ *
573
+ * @param fn - Async transformation function
574
+ * @returns Promise of new Outcome
575
+ *
576
+ * @see {@link map} for the synchronous version
577
+ *
578
+ * @example
579
+ * ```typescript
580
+ * const outcome = await Outcome.ok(userId)
581
+ * .mapAsync(async id => {
582
+ * const user = await fetchUser(id);
583
+ * return [user, null];
584
+ * });
585
+ * ```
586
+ */
587
+ async mapAsync(fn) {
588
+ if (this.isErr) {
589
+ return new Outcome([null, this._tuple[1]]);
590
+ }
591
+ try {
592
+ const result = await fn(this._tuple[0]);
593
+ return Outcome._processCallbackReturn(result);
594
+ }
595
+ catch (e) {
596
+ return new Outcome([null, err_1.Err.from(e)]);
597
+ }
598
+ }
599
+ /**
600
+ * Transform or recover from an error using a callback.
601
+ *
602
+ * Only called if this Outcome is in error state. Success passes through unchanged.
603
+ * The callback can return any `CallbackReturn<U>` pattern, allowing:
604
+ * - Recovery: return `[value, null]` to convert error to success
605
+ * - Transform: return `Err` or `[null, Err]` to change the error
606
+ *
607
+ * @param fn - Function receiving the error
608
+ * @returns New Outcome with transformed error or recovered value
609
+ *
610
+ * @see {@link mapErrAsync} for the async version
611
+ * @see {@link map} for transforming success values
612
+ *
613
+ * @example Recovery
614
+ * ```typescript
615
+ * const outcome = Outcome.err('Not found')
616
+ * .mapErr(err => {
617
+ * if (err.hasCode('NOT_FOUND')) {
618
+ * return [defaultValue, null]; // recover with default
619
+ * }
620
+ * return err; // pass through other errors
621
+ * });
622
+ * ```
623
+ *
624
+ * @example Error transformation
625
+ * ```typescript
626
+ * const outcome = Outcome.err('Low-level error')
627
+ * .mapErr(err => err.wrap('High-level context'));
628
+ * ```
629
+ *
630
+ * @example Logging and pass-through
631
+ * ```typescript
632
+ * const outcome = Outcome.err('Something failed')
633
+ * .mapErr(err => {
634
+ * console.error('Error occurred:', err.message);
635
+ * return err; // pass through unchanged
636
+ * });
637
+ * ```
638
+ */
639
+ mapErr(fn) {
640
+ if (this.isOk) {
641
+ return this;
642
+ }
643
+ try {
644
+ const result = fn(this._tuple[1]);
645
+ return Outcome._processCallbackReturn(result);
646
+ }
647
+ catch (e) {
648
+ return new Outcome([null, err_1.Err.from(e)]);
649
+ }
650
+ }
651
+ /**
652
+ * Async version of `mapErr()`.
653
+ *
654
+ * @param fn - Async function receiving the error
655
+ * @returns Promise of new Outcome
656
+ *
657
+ * @see {@link mapErr} for the synchronous version
658
+ *
659
+ * @example Async recovery with fallback fetch
660
+ * ```typescript
661
+ * const outcome = await Outcome.err('Primary failed')
662
+ * .mapErrAsync(async err => {
663
+ * const fallback = await fetchFromBackup();
664
+ * if (fallback) return [fallback, null];
665
+ * return err.wrap('Backup also failed');
666
+ * });
667
+ * ```
668
+ */
669
+ async mapErrAsync(fn) {
670
+ if (this.isOk) {
671
+ return this;
672
+ }
673
+ try {
674
+ const result = await fn(this._tuple[1]);
675
+ return Outcome._processCallbackReturn(result);
676
+ }
677
+ catch (e) {
678
+ return new Outcome([null, err_1.Err.from(e)]);
679
+ }
680
+ }
681
+ // ══════════════════════════════════════════════════════════════════════════
682
+ // Side Effects
683
+ // ══════════════════════════════════════════════════════════════════════════
684
+ /**
685
+ * Execute a side effect with access to the full tuple.
686
+ *
687
+ * The callback receives the tuple `[value, error]` regardless of state.
688
+ * Returns `this` unchanged for chaining.
689
+ * If the callback throws, the exception is caught and the Outcome becomes an error.
690
+ *
691
+ * @param fn - Side effect function receiving the tuple
692
+ * @returns This Outcome (for chaining), or error Outcome if callback throws
693
+ *
694
+ * @see {@link effectAsync} for the async version
695
+ *
696
+ * @example Logging
697
+ * ```typescript
698
+ * const outcome = Outcome.ok(42)
699
+ * .effect(([val, err]) => {
700
+ * if (err) console.error('Failed:', err.message);
701
+ * else console.log('Success:', val);
702
+ * })
703
+ * .map(v => [v * 2, null]);
704
+ * ```
705
+ *
706
+ * @example Metrics
707
+ * ```typescript
708
+ * outcome.effect(([val, err]) => {
709
+ * metrics.record({
710
+ * success: !err,
711
+ * value: val,
712
+ * errorCode: err?.code
713
+ * });
714
+ * });
715
+ * ```
716
+ */
717
+ effect(fn) {
718
+ try {
719
+ const t = this.toTuple();
720
+ fn(t);
721
+ return this;
722
+ }
723
+ catch (e) {
724
+ return new Outcome([null, err_1.Err.from(e)]);
725
+ }
726
+ }
727
+ /**
728
+ * Async version of `effect()`.
729
+ *
730
+ * @param fn - Async side effect function
731
+ * @returns Promise of this Outcome
732
+ *
733
+ * @see {@link effect} for the synchronous version
734
+ *
735
+ * @example Async logging
736
+ * ```typescript
737
+ * const outcome = await Outcome.ok(data)
738
+ * .effectAsync(async ([val, err]) => {
739
+ * await logger.log({ value: val, error: err?.toJSON() });
740
+ * });
741
+ * ```
742
+ */
743
+ async effectAsync(fn) {
744
+ try {
745
+ const t = this.toTuple();
746
+ await fn(t);
747
+ return this;
748
+ }
749
+ catch (e) {
750
+ return new Outcome([null, err_1.Err.from(e)]);
751
+ }
752
+ }
753
+ /**
754
+ * Implementation for defaultTo overloads.
755
+ * @internal
756
+ */
757
+ defaultTo(fallbackOrHandler, asValue) {
758
+ if (this.isOk) {
759
+ return this._tuple[0];
760
+ }
761
+ if (asValue === true) {
762
+ return fallbackOrHandler;
763
+ }
764
+ if (typeof fallbackOrHandler === "function") {
765
+ return fallbackOrHandler(this._tuple[1]);
766
+ }
767
+ return fallbackOrHandler;
768
+ }
769
+ /**
770
+ * Transform the Outcome into a final value by handling both cases.
771
+ *
772
+ * This is a terminal operation that exits the Outcome chain, similar to
773
+ * `toTuple()` but with transformation logic applied.
774
+ *
775
+ * Each handler receives only its relevant type with full type safety:
776
+ * - `onOk` receives `T` (guaranteed non-null value)
777
+ * - `onErr` receives `Err` (guaranteed error)
778
+ *
779
+ * @param onOk - Function to transform success value into final result
780
+ * @param onErr - Function to transform error into final result
781
+ * @returns The transformed value (not wrapped in Outcome)
782
+ * @throws If either callback throws, the exception propagates to the caller
783
+ *
784
+ * @see {@link defaultTo} for simple value extraction with fallback
785
+ * @see {@link toTuple} for raw tuple extraction
786
+ * @see {@link toJSON} for JSON serialization
787
+ *
788
+ * @example Basic transformation
789
+ * ```typescript
790
+ * const message = fetchUser(id).either(
791
+ * user => `Welcome, ${user.name}!`,
792
+ * err => `Error: ${err.message}`
793
+ * );
794
+ * // message is string, not Outcome<string>
795
+ * ```
796
+ *
797
+ * @example HTTP response building
798
+ * ```typescript
799
+ * const response = processOrder(orderId).either(
800
+ * order => ({ status: 200, body: { id: order.id, total: order.total } }),
801
+ * err => ({
802
+ * status: err.hasCode('NOT_FOUND') ? 404 : 500,
803
+ * body: { error: err.message }
804
+ * })
805
+ * );
806
+ * ```
807
+ *
808
+ * @example Default value on error
809
+ * ```typescript
810
+ * const count = parseNumber(input).either(n => n, () => 0);
811
+ * ```
812
+ *
813
+ * @example Type transformation
814
+ * ```typescript
815
+ * const status: 'success' | 'error' = outcomeEntity.either(
816
+ * () => 'success',
817
+ * () => 'error'
818
+ * );
819
+ * ```
820
+ */
821
+ either(onOk, onErr) {
822
+ if (this.isOk) {
823
+ return onOk(this._tuple[0]);
824
+ }
825
+ return onErr(this._tuple[1]);
826
+ }
827
+ /**
828
+ * Implementation for pipe overloads.
829
+ * @internal
830
+ */
831
+ // biome-ignore lint/suspicious/noExplicitAny: implementation signature needs any
832
+ pipe(...fns) {
833
+ // biome-ignore lint/suspicious/noExplicitAny: implementation signature needs any
834
+ let current = this;
835
+ for (const fn of fns) {
836
+ try {
837
+ const result = fn(current.toTuple());
838
+ current = Outcome._processCallbackReturn(result);
839
+ }
840
+ catch (e) {
841
+ // biome-ignore lint/suspicious/noExplicitAny: implementation signature needs any
842
+ current = new Outcome([null, err_1.Err.from(e)]);
843
+ }
844
+ }
845
+ return current;
846
+ }
847
+ /**
848
+ * Implementation for pipeAsync overloads.
849
+ * @internal
850
+ */
851
+ // biome-ignore lint/suspicious/noExplicitAny: implementation signature needs any
852
+ async pipeAsync(...fns) {
853
+ // biome-ignore lint/suspicious/noExplicitAny: implementation signature needs any
854
+ let current = this;
855
+ for (const fn of fns) {
856
+ try {
857
+ const result = await fn(current.toTuple());
858
+ current = Outcome._processCallbackReturn(result);
859
+ }
860
+ catch (e) {
861
+ // biome-ignore lint/suspicious/noExplicitAny: implementation signature needs any
862
+ current = new Outcome([null, err_1.Err.from(e)]);
863
+ }
864
+ }
865
+ return current;
866
+ }
867
+ // ══════════════════════════════════════════════════════════════════════════
868
+ // Conversion
869
+ // ══════════════════════════════════════════════════════════════════════════
870
+ /**
871
+ * Extract the internal tuple.
872
+ *
873
+ * Primary method for extracting values from an Outcome.
874
+ * Use destructuring for ergonomic access.
875
+ *
876
+ * @returns The internal ResultTuple<T>
877
+ *
878
+ * @see {@link fromTuple} for creating an Outcome from a tuple
879
+ *
880
+ * @example
881
+ * ```typescript
882
+ * const outcome = Outcome.ok(42);
883
+ * const [value, error] = outcome.toTuple();
884
+ *
885
+ * if (error) {
886
+ * console.error('Failed:', error.message);
887
+ * return;
888
+ * }
889
+ * console.log('Value:', value); // 42
890
+ * ```
891
+ */
892
+ toTuple() {
893
+ const [v, e] = this._tuple;
894
+ return [v, e];
895
+ }
896
+ /**
897
+ * Convert to JSON-serializable tuple.
898
+ *
899
+ * For success: returns `[value, null]`
900
+ * For error: returns `[null, errJSON]` where errJSON is from `Err.toJSON()`
901
+ *
902
+ * @returns JSON-serializable representation
903
+ *
904
+ * @see {@link fromJSON} for deserializing an Outcome from JSON
905
+ *
906
+ * @example
907
+ * ```typescript
908
+ * const outcome = Outcome.ok({ name: 'John' });
909
+ * const json = JSON.stringify(outcome.toJSON());
910
+ * // '[{"name":"John"},null]'
911
+ *
912
+ * // Deserialize
913
+ * const restored = Outcome.fromJSON(JSON.parse(json));
914
+ * ```
915
+ */
916
+ toJSON() {
917
+ if (this.isOk) {
918
+ return [this._tuple[0], null];
919
+ }
920
+ return [null, this._tuple[1].toJSON()];
921
+ }
922
+ /**
923
+ * Convert to a human-readable string.
924
+ *
925
+ * @returns String representation
926
+ *
927
+ * @example
928
+ * ```typescript
929
+ * console.log(Outcome.ok(42).toString());
930
+ * // 'Outcome.ok(42)'
931
+ *
932
+ * console.log(Outcome.err('Failed').toString());
933
+ * // 'Outcome.err([ERROR] Failed)'
934
+ * ```
935
+ */
936
+ toString() {
937
+ if (this.isOk) {
938
+ return `Outcome.ok(${fmt(this._tuple[0])})`;
939
+ }
940
+ return `Outcome.err(${this._tuple[1].toString()})`;
941
+ }
942
+ }
943
+ exports.Outcome = Outcome;
944
+ function fmt(v) {
945
+ if (v === null)
946
+ return "null";
947
+ if (v === undefined)
948
+ return "undefined";
949
+ if (typeof v === "string")
950
+ return JSON.stringify(v);
951
+ try {
952
+ return JSON.stringify(v);
953
+ }
954
+ catch {
955
+ return String(v);
956
+ }
957
+ }
958
+ //# sourceMappingURL=outcome.js.map