@nlozgachev/pipelined 0.37.0 → 0.38.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.
@@ -1,333 +1,198 @@
1
1
  import { NonEmptyList, Duration } from './types.js';
2
2
 
3
- type WithKind<K extends string> = {
4
- readonly kind: K;
5
- };
6
- type WithValue<T> = {
7
- readonly value: T;
8
- };
9
- type WithError<T> = {
10
- readonly error: T;
11
- };
12
- type WithErrors<T> = {
13
- readonly errors: NonEmptyList<T>;
14
- };
15
- type WithFirst<T> = {
16
- readonly first: T;
17
- };
18
- type WithSecond<T> = {
19
- readonly second: T;
20
- };
21
- type WithLog<T> = {
22
- readonly log: ReadonlyArray<T>;
23
- };
24
- /** Retry policy for `Op.interpret`. */
25
- type RetryOptions<E> = {
26
- readonly attempts: number;
27
- readonly backoff?: Duration | ((attempt: number) => Duration);
28
- readonly when?: (error: E) => boolean;
29
- };
30
- /** Timeout policy for `Op.interpret`. Wraps the entire retry sequence. */
31
- type TimeoutOptions<E> = {
32
- readonly duration: Duration;
33
- readonly onTimeout: () => E;
34
- };
35
- type WithTimeout<E> = {
36
- readonly timeout?: TimeoutOptions<E>;
37
- };
38
- type WithDuration = {
39
- readonly duration: Duration;
40
- };
41
- type WithN = {
42
- readonly n: number;
43
- };
44
- type WithConcurrency = {
45
- readonly concurrency?: number;
46
- };
47
- type WithSize = {
48
- readonly size?: number;
49
- };
50
- type WithCooldown = {
51
- readonly cooldown?: Duration;
52
- };
53
- type WithMinInterval = {
54
- readonly minInterval?: Duration;
55
- };
56
-
57
- type Ok<A> = WithKind<"Ok"> & WithValue<A>;
58
- type Err<E> = WithKind<"Err"> & WithError<E>;
3
+ declare const _deferred: unique symbol;
59
4
  /**
60
- * Result represents a value that can be one of two types: a success (Ok) or a failure (Err).
61
- * Use Result when an operation can fail with a meaningful error value.
5
+ * A nominally typed, one-shot async value that supports `await` but enforces infallibility.
6
+ *
7
+ * Two design choices work together to make the guarantee structural rather than documentary:
8
+ *
9
+ * - The phantom `[_deferred]` symbol makes the type **nominal**: only values produced by
10
+ * `Deferred.fromPromise` satisfy it. A plain object `{ then: ... }` does not.
11
+ * - The single-parameter `.then()` **excludes rejection handlers** by construction. There is
12
+ * no second argument to pass, so chaining and `.catch()` are impossible.
13
+ *
14
+ * This makes `Deferred<A>` the natural return type for `Task<A>`, which is guaranteed to
15
+ * never reject.
62
16
  *
63
17
  * @example
64
18
  * ```ts
65
- * const divide = (a: number, b: number): Result<string, number> =>
66
- * b === 0 ? Result.err("Division by zero") : Result.ok(a / b);
67
- *
68
- * pipe(
69
- * divide(10, 2),
70
- * Result.map(n => n * 2),
71
- * Result.getOrElse(() => 0)
72
- * ); // 10
19
+ * const value = await Deferred.fromPromise(Promise.resolve(42));
20
+ * // value === 42
73
21
  * ```
74
22
  */
75
- type Result<E, A> = Ok<A> | Err<E>;
76
- declare namespace Result {
77
- /**
78
- * Creates a successful Result with the given value.
79
- */
80
- const ok: <A>(value: A) => Ok<A>;
81
- /**
82
- * Creates a failed Result with the given error.
83
- */
84
- const err: <E>(e: E) => Err<E>;
85
- /**
86
- * Type guard that checks if a Result is Ok.
87
- */
88
- const isOk: <E, A>(data: Result<E, A>) => data is Ok<A>;
89
- /**
90
- * Type guard that checks if a Result is Err.
91
- */
92
- const isErr: <E, A>(data: Result<E, A>) => data is Err<E>;
93
- /**
94
- * Creates a Result from a function that may throw.
95
- * Catches any errors and transforms them using the onError function.
96
- *
97
- * @example
98
- * ```ts
99
- * const parseJson = (s: string): Result<string, unknown> =>
100
- * Result.tryCatch(
101
- * () => JSON.parse(s),
102
- * (e) => `Parse error: ${e}`
103
- * );
104
- * ```
105
- */
106
- const tryCatch: <E, A>(f: () => A, onError: (e: unknown) => E) => Result<E, A>;
107
- /**
108
- * Transforms the success value inside a Result.
109
- *
110
- * @example
111
- * ```ts
112
- * pipe(Result.ok(5), Result.map(n => n * 2)); // Ok(10)
113
- * pipe(Result.err("error"), Result.map(n => n * 2)); // Err("error")
114
- * ```
115
- */
116
- const map: <E, A, B>(f: (a: A) => B) => (data: Result<E, A>) => Result<E, B>;
117
- /**
118
- * Transforms the error value inside a Result.
119
- *
120
- * @example
121
- * ```ts
122
- * pipe(Result.err("oops"), Result.mapError(e => e.toUpperCase())); // Err("OOPS")
123
- * ```
124
- */
125
- const mapError: <E, F, A>(f: (e: E) => F) => (data: Result<E, A>) => Result<F, A>;
126
- /**
127
- * Chains Result computations. If the first is Ok, passes the value to f.
128
- * If the first is Err, propagates the error.
129
- *
130
- * @example
131
- * ```ts
132
- * const validatePositive = (n: number): Result<string, number> =>
133
- * n > 0 ? Result.ok(n) : Result.err("Must be positive");
134
- *
135
- * pipe(Result.ok(5), Result.chain(validatePositive)); // Ok(5)
136
- * pipe(Result.ok(-1), Result.chain(validatePositive)); // Err("Must be positive")
137
- * ```
138
- */
139
- const chain: <E, A, B>(f: (a: A) => Result<E, B>) => (data: Result<E, A>) => Result<E, B>;
140
- /**
141
- * Extracts the value from a Result by providing handlers for both cases.
142
- *
143
- * @example
144
- * ```ts
145
- * pipe(
146
- * Result.ok(5),
147
- * Result.fold(
148
- * e => `Error: ${e}`,
149
- * n => `Value: ${n}`
150
- * )
151
- * ); // "Value: 5"
152
- * ```
153
- */
154
- const fold: <E, A, B>(onErr: (e: E) => B, onOk: (a: A) => B) => (data: Result<E, A>) => B;
155
- /**
156
- * Pattern matches on a Result, returning the result of the matching case.
157
- *
158
- * @example
159
- * ```ts
160
- * pipe(
161
- * result,
162
- * Result.match({
163
- * ok: value => `Got ${value}`,
164
- * err: error => `Failed: ${error}`
165
- * })
166
- * );
167
- * ```
168
- */
169
- const match: <E, A, B>(cases: {
170
- ok: (a: A) => B;
171
- err: (e: E) => B;
172
- }) => (data: Result<E, A>) => B;
173
- /**
174
- * Returns the success value or a default value if the Result is an error.
175
- * The default is a thunk `() => B` — evaluated only when the Result is Err.
176
- * The default can be a different type, widening the result to `A | B`.
177
- *
178
- * @example
179
- * ```ts
180
- * pipe(Result.ok(5), Result.getOrElse(() => 0)); // 5
181
- * pipe(Result.err("error"), Result.getOrElse(() => 0)); // 0
182
- * pipe(Result.err("error"), Result.getOrElse(() => null)); // null — typed as number | null
183
- * ```
184
- */
185
- const getOrElse: <E, A, B>(defaultValue: () => B) => (data: Result<E, A>) => A | B;
186
- /**
187
- * Executes a side effect on the success value without changing the Result.
188
- * Useful for logging or debugging.
189
- *
190
- * @example
191
- * ```ts
192
- * pipe(
193
- * Result.ok(5),
194
- * Result.tap(n => console.log("Value:", n)),
195
- * Result.map(n => n * 2)
196
- * );
197
- * ```
198
- */
199
- const tap: <E, A>(f: (a: A) => void) => (data: Result<E, A>) => Result<E, A>;
23
+ type Deferred<A> = {
24
+ readonly [_deferred]: A;
25
+ readonly then: (onfulfilled: (value: A) => unknown) => void;
26
+ };
27
+ declare namespace Deferred {
200
28
  /**
201
- * Executes a side effect on the error value without changing the Result.
202
- * Useful for logging or reporting errors.
29
+ * Wraps a `Promise` into a `Deferred`, structurally excluding rejection handlers,
30
+ * `.catch()`, `.finally()`, and chainable `.then()`.
203
31
  *
204
- * @example
205
- * ```ts
206
- * pipe(
207
- * Result.err("not found"),
208
- * Result.tapError(e => console.error("validation failed:", e)),
209
- * Result.chain(save),
210
- * )
211
- * ```
212
- */
213
- const tapError: <E, A>(f: (e: E) => void) => (data: Result<E, A>) => Result<E, A>;
214
- /**
215
- * Creates a Result from a predicate applied to a value.
216
- * Returns Ok if the predicate passes, Err from onFalse otherwise.
32
+ * **Precondition**: `p` must never reject. If `p` rejects, the returned `Deferred` will
33
+ * never resolve — `await`-ing it will hang indefinitely. Use `TaskResult.tryCatch` to
34
+ * handle operations that may fail before converting to a `Deferred`.
217
35
  *
218
36
  * @example
219
37
  * ```ts
220
- * pipe(5, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); // Ok(5)
221
- * pipe(-1, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); // Err("-1 is not positive")
222
- * pipe("", Result.fromPredicate(s => s.length > 0, () => "empty string")); // Err("empty string")
38
+ * const d = Deferred.fromPromise(Promise.resolve("hello"));
39
+ * const value = await d; // "hello"
223
40
  * ```
224
41
  */
225
- const fromPredicate: <E, A>(pred: (a: A) => boolean, onFalse: (a: A) => E) => (a: A) => Result<E, A>;
42
+ const fromPromise: <A>(p: Promise<A>) => Deferred<A>;
226
43
  /**
227
- * Creates a Result from a nullable value.
228
- * Returns Ok if the value is not null or undefined, error from onNull otherwise.
44
+ * Converts a `Deferred` back into a `Promise`.
229
45
  *
230
46
  * @example
231
47
  * ```ts
232
- * pipe(null, Result.fromNullable(() => "is null")); // Err("is null")
233
- * pipe(42, Result.fromNullable(() => "is null")); // Ok(42)
48
+ * const p = Deferred.toPromise(Deferred.fromPromise(Promise.resolve(42)));
49
+ * // p is Promise<42>
234
50
  * ```
235
51
  */
236
- const fromNullable: <E>(onNull: () => E) => <A>(value: A | null | undefined) => Result<E, A>;
52
+ const toPromise: <A>(d: Deferred<A>) => Promise<A>;
53
+ }
54
+
55
+ /**
56
+ * A function that checks whether two values of type `A` are equal.
57
+ * Use built-in instances (`Equality.string`, `Equality.number`, etc.) as starting points,
58
+ * then adapt them with `Equality.by` and combine them with `Equality.and`.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * type User = { id: string; name: string };
63
+ * const byId = pipe(Equality.string, Equality.by((u: User) => u.id));
64
+ *
65
+ * pipe(users, Arr.uniqWith(byId));
66
+ * ```
67
+ */
68
+ type Equality<A> = (a: A, b: A) => boolean;
69
+ declare namespace Equality {
237
70
  /**
238
- * Creates a Result from a Maybe.
239
- * Some becomes Ok, None becomes error from onNone.
71
+ * Equality for strings. Case-sensitive.
240
72
  *
241
73
  * @example
242
74
  * ```ts
243
- * pipe(Maybe.none(), Result.fromMaybe(() => "is none")); // Err("is none")
244
- * pipe(Maybe.some(42), Result.fromMaybe(() => "is none")); // Ok(42)
75
+ * Equality.string("hello", "hello"); // true
76
+ * Equality.string("hello", "Hello"); // false
245
77
  * ```
246
78
  */
247
- const fromMaybe: <E>(onNone: () => E) => <A>(maybe: Maybe<A>) => Result<E, A>;
79
+ const string: Equality<string>;
248
80
  /**
249
- * Wraps a throwing function of any arguments, returning a new function
250
- * that catches errors and returns a Result.
81
+ * Equality for numbers. Uses strict equality.
251
82
  *
252
83
  * @example
253
84
  * ```ts
254
- * const safeParse = Result.fromThrowable(
255
- * (s: string) => JSON.parse(s),
256
- * (e) => new Error(`Parse error: ${e}`)
257
- * );
258
- *
259
- * safeParse('{"a":1}'); // Ok({ a: 1 })
260
- * safeParse('invalid'); // Err(Error)
85
+ * Equality.number(42, 42); // true
261
86
  * ```
262
87
  */
263
- const fromThrowable: <Args extends readonly unknown[], A, E>(f: (...args: Args) => A, onError: (e: unknown) => E) => (...args: Args) => Result<E, A>;
264
- /**
265
- * Recovers from an error by providing a fallback Result.
266
- * The fallback can produce a different success type, widening the result to `Result<E, A | B>`.
267
- */
268
- const recover: <E, A, B>(fallback: (e: E) => Result<E, B>) => (data: Result<E, A>) => Result<E, A | B>;
88
+ const number: Equality<number>;
269
89
  /**
270
- * Recovers from an error unless the predicate `isBlocked` returns true for that error.
271
- * The fallback can produce a different success type, widening the result to `Result<E, A | B>`.
90
+ * Equality for booleans.
272
91
  *
273
92
  * @example
274
93
  * ```ts
275
- * pipe(
276
- * Result.err(new Error("not found")),
277
- * Result.recoverUnless(e => e.message === "fatal", () => Result.ok(0))
278
- * ); // Ok(0)
94
+ * Equality.boolean(true, true); // true
279
95
  * ```
280
96
  */
281
- const recoverUnless: <E, A, B>(isBlocked: (e: E) => boolean, fallback: () => Result<E, B>) => (data: Result<E, A>) => Result<E, A | B>;
97
+ const boolean: Equality<boolean>;
282
98
  /**
283
- * Converts a Result to a Maybe.
284
- * Ok becomes Some, Err becomes None (the error is discarded).
99
+ * Equality for `Date` values. Compares by numeric time value.
285
100
  *
286
101
  * @example
287
102
  * ```ts
288
- * Result.toMaybe(Result.ok(42)); // Some(42)
289
- * Result.toMaybe(Result.err("oops")); // None
103
+ * Equality.date(new Date("2024-01-01"), new Date("2024-01-01")); // true
290
104
  * ```
291
105
  */
292
- const toMaybe: <E, A>(data: Result<E, A>) => Maybe<A>;
106
+ const date: Equality<Date>;
293
107
  /**
294
- * Applies a function wrapped in a Result to a value wrapped in a Result.
108
+ * Lifts an element equality into an array equality. Two arrays are equal if they have the
109
+ * same length and every element pair is equal under `eq`.
295
110
  *
296
111
  * @example
297
112
  * ```ts
298
- * const add = (a: number) => (b: number) => a + b;
299
- * pipe(
300
- * Result.ok(add),
301
- * Result.ap(Result.ok(5)),
302
- * Result.ap(Result.ok(3))
303
- * ); // Ok(8)
113
+ * Equality.array(Equality.number)([1, 2, 3], [1, 2, 3]); // true
304
114
  * ```
305
115
  */
306
- const ap: <E, A>(arg: Result<E, A>) => <B>(data: Result<E, (a: A) => B>) => Result<E, B>;
116
+ const array: <A>(eq: Equality<A>) => Equality<readonly A[]>;
307
117
  /**
308
- * Converts a Result value into an object containing a single property.
309
- * Initiates the pipeline accumulator record.
118
+ * Adapts an equality for type `A` into an equality for type `B` by extracting a field.
119
+ * Read as "equality by this field": `pipe(Equality.string, Equality.by(u => u.name))`.
310
120
  *
311
121
  * @example
312
122
  * ```ts
313
- * pipe(Result.ok(42), Result.bindTo("value")); // Ok({ value: 42 })
123
+ * type Product = { id: string; price: number };
124
+ * const byId = pipe(Equality.string, Equality.by((p: Product) => p.id));
125
+ * byId({ id: "p1", price: 9 }, { id: "p1", price: 12 }); // true
314
126
  * ```
315
127
  */
316
- const bindTo: <K extends string>(key: K) => <E, A>(data: Result<E, A>) => Result<E, { [P in K]: A; }>;
128
+ const by: <A, B>(f: (b: B) => A) => (eq: Equality<A>) => Equality<B>;
317
129
  /**
318
- * Evaluates a new Result using the current accumulator and attaches the output to a new key.
130
+ * Combines two equalities with logical AND. Both must pass for two values to be considered equal.
131
+ * Data-last: the first equality is the data being piped.
319
132
  *
320
133
  * @example
321
134
  * ```ts
322
- * pipe(
323
- * Result.ok({ a: 1 }),
324
- * Result.bind("b", ({ a }) => Result.ok(a + 1))
325
- * ); // Ok({ a: 1, b: 2 })
135
+ * const exact = pipe(byName, Equality.and(byRole));
136
+ * exact(userA, userB); // true only if name AND role match
326
137
  * ```
327
138
  */
328
- const bind: <K extends string, E, A, B>(key: K, f: (a: A) => Result<E, B>) => (data: Result<E, A>) => Result<E, A & { [P in K]: B; }>;
139
+ const and: <A>(eq2: Equality<A>) => (eq1: Equality<A>) => Equality<A>;
329
140
  }
330
141
 
142
+ type WithKind<K extends string> = {
143
+ readonly kind: K;
144
+ };
145
+ type WithValue<T> = {
146
+ readonly value: T;
147
+ };
148
+ type WithError<T> = {
149
+ readonly error: T;
150
+ };
151
+ type WithErrors<T> = {
152
+ readonly errors: NonEmptyList<T>;
153
+ };
154
+ type WithFirst<T> = {
155
+ readonly first: T;
156
+ };
157
+ type WithSecond<T> = {
158
+ readonly second: T;
159
+ };
160
+ type WithLog<T> = {
161
+ readonly log: ReadonlyArray<T>;
162
+ };
163
+ /** Retry policy for `Op.interpret`. */
164
+ type RetryOptions<E> = {
165
+ readonly attempts: number;
166
+ readonly backoff?: Duration | ((attempt: number) => Duration);
167
+ readonly when?: (error: E) => boolean;
168
+ };
169
+ /** Timeout policy for `Op.interpret`. Wraps the entire retry sequence. */
170
+ type TimeoutOptions<E> = {
171
+ readonly duration: Duration;
172
+ readonly onTimeout: () => E;
173
+ };
174
+ type WithTimeout<E> = {
175
+ readonly timeout?: TimeoutOptions<E>;
176
+ };
177
+ type WithDuration = {
178
+ readonly duration: Duration;
179
+ };
180
+ type WithN = {
181
+ readonly n: number;
182
+ };
183
+ type WithConcurrency = {
184
+ readonly concurrency?: number;
185
+ };
186
+ type WithSize = {
187
+ readonly size?: number;
188
+ };
189
+ type WithCooldown = {
190
+ readonly cooldown?: Duration;
191
+ };
192
+ type WithMinInterval = {
193
+ readonly minInterval?: Duration;
194
+ };
195
+
331
196
  type Some<A> = WithKind<"Some"> & WithValue<A>;
332
197
  type None = WithKind<"None">;
333
198
  /**
@@ -540,246 +405,407 @@ declare namespace Maybe {
540
405
  * ); // Some(8)
541
406
  * ```
542
407
  */
543
- const ap: <A>(arg: Maybe<A>) => <B>(data: Maybe<(a: A) => B>) => Maybe<B>;
408
+ const ap: <A>(arg: Maybe<A>) => <B>(data: Maybe<(a: A) => B>) => Maybe<B>;
409
+ /**
410
+ * Converts a Maybe value into an object containing a single property.
411
+ * Initiates the pipeline accumulator record.
412
+ *
413
+ * @example
414
+ * ```ts
415
+ * pipe(Maybe.some(42), Maybe.bindTo("value")); // Some({ value: 42 })
416
+ * ```
417
+ */
418
+ const bindTo: <K extends string>(key: K) => <A>(data: Maybe<A>) => Maybe<{ [P in K]: A; }>;
419
+ /**
420
+ * Evaluates a new Maybe using the current accumulator and attaches the output to a new key.
421
+ *
422
+ * @example
423
+ * ```ts
424
+ * pipe(
425
+ * Maybe.some({ a: 1 }),
426
+ * Maybe.bind("b", ({ a }) => Maybe.some(a + 1))
427
+ * ); // Some({ a: 1, b: 2 })
428
+ * ```
429
+ */
430
+ const bind: <K extends string, A, B>(key: K, f: (a: A) => Maybe<B>) => (data: Maybe<A>) => Maybe<A & { [P in K]: B; }>;
431
+ /**
432
+ * Combines a record of Maybes into a single Maybe of a record.
433
+ * Evaluates fields in key order and short-circuits on the first None.
434
+ *
435
+ * @example
436
+ * ```ts
437
+ * Maybe.struct({
438
+ * name: Maybe.some("Alice"),
439
+ * age: Maybe.some(30)
440
+ * }); // Some({ name: "Alice", age: 30 })
441
+ * ```
442
+ */
443
+ const struct: <R extends Record<string, any>>(fields: { [K in keyof R]: Maybe<R[K]>; }) => Maybe<R>;
444
+ }
445
+
446
+ /**
447
+ * A function that orders two values of type `A`. Returns a negative number when `a` comes before
448
+ * `b`, a positive number when `a` comes after `b`, and `0` when they are equal.
449
+ *
450
+ * Compatible with `Array.prototype.sort` and `Arr.sortWith`.
451
+ *
452
+ * @example
453
+ * ```ts
454
+ * type Employee = { name: string; salary: number };
455
+ *
456
+ * const byName = pipe(Ordering.string, Ordering.by((e: Employee) => e.name));
457
+ * const bySalary = pipe(Ordering.number, Ordering.by((e: Employee) => e.salary));
458
+ *
459
+ * pipe(employees, Arr.sortWith(pipe(byName, Ordering.thenBy(bySalary))));
460
+ * ```
461
+ */
462
+ type Ordering<A> = (a: A, b: A) => number;
463
+ declare namespace Ordering {
464
+ /**
465
+ * Alphabetical ordering for strings.
466
+ *
467
+ * @example
468
+ * ```ts
469
+ * Ordering.string("apple", "banana"); // negative
470
+ * ```
471
+ */
472
+ const string: Ordering<string>;
473
+ /**
474
+ * Numeric ordering. Equivalent to `(a, b) => a - b`.
475
+ *
476
+ * @example
477
+ * ```ts
478
+ * pipe([3, 1, 2], Arr.sortWith(Ordering.number)); // [1, 2, 3]
479
+ * ```
480
+ */
481
+ const number: Ordering<number>;
482
+ /**
483
+ * Ordering for `Date` values by numeric time value.
484
+ *
485
+ * @example
486
+ * ```ts
487
+ * pipe(dates, Arr.sortWith(Ordering.date)); // earliest first
488
+ * ```
489
+ */
490
+ const date: Ordering<Date>;
491
+ /**
492
+ * Flips the direction of an ordering.
493
+ *
494
+ * @example
495
+ * ```ts
496
+ * pipe([3, 1, 2], Arr.sortWith(Ordering.reverse(Ordering.number))); // [3, 2, 1]
497
+ * ```
498
+ */
499
+ const reverse: <A>(ord: Ordering<A>) => Ordering<A>;
500
+ /**
501
+ * Chains two orderings: the second is used only when the first returns `0`.
502
+ * Data-last: the first ordering is the data being piped.
503
+ *
504
+ * @example
505
+ * ```ts
506
+ * const byDeptThenSalary = pipe(byDept, Ordering.thenBy(bySalary));
507
+ * ```
508
+ */
509
+ const thenBy: <A>(ord2: Ordering<A>) => (ord1: Ordering<A>) => Ordering<A>;
510
+ /**
511
+ * Adapts an ordering for type `A` into an ordering for type `B` by extracting a field.
512
+ * Read as "ordering by this field": `pipe(Ordering.number, Ordering.by(p => p.price))`.
513
+ *
514
+ * @example
515
+ * ```ts
516
+ * type Product = { name: string; price: number };
517
+ * const byPrice = pipe(Ordering.number, Ordering.by((p: Product) => p.price));
518
+ * pipe(products, Arr.sortWith(byPrice));
519
+ * ```
520
+ */
521
+ const by: <A, B>(f: (b: B) => A) => (ord: Ordering<A>) => Ordering<B>;
522
+ }
523
+
524
+ type Ok<A> = WithKind<"Ok"> & WithValue<A>;
525
+ type Err<E> = WithKind<"Err"> & WithError<E>;
526
+ /**
527
+ * Result represents a value that can be one of two types: a success (Ok) or a failure (Err).
528
+ * Use Result when an operation can fail with a meaningful error value.
529
+ *
530
+ * @example
531
+ * ```ts
532
+ * const divide = (a: number, b: number): Result<string, number> =>
533
+ * b === 0 ? Result.err("Division by zero") : Result.ok(a / b);
534
+ *
535
+ * pipe(
536
+ * divide(10, 2),
537
+ * Result.map(n => n * 2),
538
+ * Result.getOrElse(() => 0)
539
+ * ); // 10
540
+ * ```
541
+ */
542
+ type Result<E, A> = Ok<A> | Err<E>;
543
+ declare namespace Result {
544
+ /**
545
+ * Creates a successful Result with the given value.
546
+ */
547
+ const ok: <A>(value: A) => Ok<A>;
548
+ /**
549
+ * Creates a failed Result with the given error.
550
+ */
551
+ const err: <E>(e: E) => Err<E>;
552
+ /**
553
+ * Type guard that checks if a Result is Ok.
554
+ */
555
+ const isOk: <E, A>(data: Result<E, A>) => data is Ok<A>;
556
+ /**
557
+ * Type guard that checks if a Result is Err.
558
+ */
559
+ const isErr: <E, A>(data: Result<E, A>) => data is Err<E>;
560
+ /**
561
+ * Creates a Result from a function that may throw.
562
+ * Catches any errors and transforms them using the onError function.
563
+ *
564
+ * @example
565
+ * ```ts
566
+ * const parseJson = (s: string): Result<string, unknown> =>
567
+ * Result.tryCatch(
568
+ * () => JSON.parse(s),
569
+ * (e) => `Parse error: ${e}`
570
+ * );
571
+ * ```
572
+ */
573
+ const tryCatch: <E, A>(f: () => A, onError: (e: unknown) => E) => Result<E, A>;
574
+ /**
575
+ * Transforms the success value inside a Result.
576
+ *
577
+ * @example
578
+ * ```ts
579
+ * pipe(Result.ok(5), Result.map(n => n * 2)); // Ok(10)
580
+ * pipe(Result.err("error"), Result.map(n => n * 2)); // Err("error")
581
+ * ```
582
+ */
583
+ const map: <E, A, B>(f: (a: A) => B) => (data: Result<E, A>) => Result<E, B>;
544
584
  /**
545
- * Converts a Maybe value into an object containing a single property.
546
- * Initiates the pipeline accumulator record.
585
+ * Transforms the error value inside a Result.
547
586
  *
548
587
  * @example
549
588
  * ```ts
550
- * pipe(Maybe.some(42), Maybe.bindTo("value")); // Some({ value: 42 })
589
+ * pipe(Result.err("oops"), Result.mapError(e => e.toUpperCase())); // Err("OOPS")
551
590
  * ```
552
591
  */
553
- const bindTo: <K extends string>(key: K) => <A>(data: Maybe<A>) => Maybe<{ [P in K]: A; }>;
592
+ const mapError: <E, F, A>(f: (e: E) => F) => (data: Result<E, A>) => Result<F, A>;
554
593
  /**
555
- * Evaluates a new Maybe using the current accumulator and attaches the output to a new key.
594
+ * Chains Result computations. If the first is Ok, passes the value to f.
595
+ * If the first is Err, propagates the error.
556
596
  *
557
597
  * @example
558
598
  * ```ts
559
- * pipe(
560
- * Maybe.some({ a: 1 }),
561
- * Maybe.bind("b", ({ a }) => Maybe.some(a + 1))
562
- * ); // Some({ a: 1, b: 2 })
599
+ * const validatePositive = (n: number): Result<string, number> =>
600
+ * n > 0 ? Result.ok(n) : Result.err("Must be positive");
601
+ *
602
+ * pipe(Result.ok(5), Result.chain(validatePositive)); // Ok(5)
603
+ * pipe(Result.ok(-1), Result.chain(validatePositive)); // Err("Must be positive")
563
604
  * ```
564
605
  */
565
- const bind: <K extends string, A, B>(key: K, f: (a: A) => Maybe<B>) => (data: Maybe<A>) => Maybe<A & { [P in K]: B; }>;
566
- }
567
-
568
- declare const _deferred: unique symbol;
569
- /**
570
- * A nominally typed, one-shot async value that supports `await` but enforces infallibility.
571
- *
572
- * Two design choices work together to make the guarantee structural rather than documentary:
573
- *
574
- * - The phantom `[_deferred]` symbol makes the type **nominal**: only values produced by
575
- * `Deferred.fromPromise` satisfy it. A plain object `{ then: ... }` does not.
576
- * - The single-parameter `.then()` **excludes rejection handlers** by construction. There is
577
- * no second argument to pass, so chaining and `.catch()` are impossible.
578
- *
579
- * This makes `Deferred<A>` the natural return type for `Task<A>`, which is guaranteed to
580
- * never reject.
581
- *
582
- * @example
583
- * ```ts
584
- * const value = await Deferred.fromPromise(Promise.resolve(42));
585
- * // value === 42
586
- * ```
587
- */
588
- type Deferred<A> = {
589
- readonly [_deferred]: A;
590
- readonly then: (onfulfilled: (value: A) => unknown) => void;
591
- };
592
- declare namespace Deferred {
606
+ const chain: <E, A, B>(f: (a: A) => Result<E, B>) => (data: Result<E, A>) => Result<E, B>;
593
607
  /**
594
- * Wraps a `Promise` into a `Deferred`, structurally excluding rejection handlers,
595
- * `.catch()`, `.finally()`, and chainable `.then()`.
596
- *
597
- * **Precondition**: `p` must never reject. If `p` rejects, the returned `Deferred` will
598
- * never resolve — `await`-ing it will hang indefinitely. Use `TaskResult.tryCatch` to
599
- * handle operations that may fail before converting to a `Deferred`.
608
+ * Extracts the value from a Result by providing handlers for both cases.
600
609
  *
601
610
  * @example
602
611
  * ```ts
603
- * const d = Deferred.fromPromise(Promise.resolve("hello"));
604
- * const value = await d; // "hello"
612
+ * pipe(
613
+ * Result.ok(5),
614
+ * Result.fold(
615
+ * e => `Error: ${e}`,
616
+ * n => `Value: ${n}`
617
+ * )
618
+ * ); // "Value: 5"
605
619
  * ```
606
620
  */
607
- const fromPromise: <A>(p: Promise<A>) => Deferred<A>;
621
+ const fold: <E, A, B>(onErr: (e: E) => B, onOk: (a: A) => B) => (data: Result<E, A>) => B;
608
622
  /**
609
- * Converts a `Deferred` back into a `Promise`.
623
+ * Pattern matches on a Result, returning the result of the matching case.
610
624
  *
611
625
  * @example
612
626
  * ```ts
613
- * const p = Deferred.toPromise(Deferred.fromPromise(Promise.resolve(42)));
614
- * // p is Promise<42>
627
+ * pipe(
628
+ * result,
629
+ * Result.match({
630
+ * ok: value => `Got ${value}`,
631
+ * err: error => `Failed: ${error}`
632
+ * })
633
+ * );
615
634
  * ```
616
635
  */
617
- const toPromise: <A>(d: Deferred<A>) => Promise<A>;
618
- }
619
-
620
- /**
621
- * A function that checks whether two values of type `A` are equal.
622
- * Use built-in instances (`Equality.string`, `Equality.number`, etc.) as starting points,
623
- * then adapt them with `Equality.by` and combine them with `Equality.and`.
624
- *
625
- * @example
626
- * ```ts
627
- * type User = { id: string; name: string };
628
- * const byId = pipe(Equality.string, Equality.by((u: User) => u.id));
629
- *
630
- * pipe(users, Arr.uniqWith(byId));
631
- * ```
632
- */
633
- type Equality<A> = (a: A, b: A) => boolean;
634
- declare namespace Equality {
636
+ const match: <E, A, B>(cases: {
637
+ ok: (a: A) => B;
638
+ err: (e: E) => B;
639
+ }) => (data: Result<E, A>) => B;
635
640
  /**
636
- * Equality for strings. Case-sensitive.
641
+ * Returns the success value or a default value if the Result is an error.
642
+ * The default is a thunk `() => B` — evaluated only when the Result is Err.
643
+ * The default can be a different type, widening the result to `A | B`.
637
644
  *
638
645
  * @example
639
646
  * ```ts
640
- * Equality.string("hello", "hello"); // true
641
- * Equality.string("hello", "Hello"); // false
647
+ * pipe(Result.ok(5), Result.getOrElse(() => 0)); // 5
648
+ * pipe(Result.err("error"), Result.getOrElse(() => 0)); // 0
649
+ * pipe(Result.err("error"), Result.getOrElse(() => null)); // null — typed as number | null
642
650
  * ```
643
651
  */
644
- const string: Equality<string>;
652
+ const getOrElse: <E, A, B>(defaultValue: () => B) => (data: Result<E, A>) => A | B;
645
653
  /**
646
- * Equality for numbers. Uses strict equality.
654
+ * Executes a side effect on the success value without changing the Result.
655
+ * Useful for logging or debugging.
647
656
  *
648
657
  * @example
649
658
  * ```ts
650
- * Equality.number(42, 42); // true
659
+ * pipe(
660
+ * Result.ok(5),
661
+ * Result.tap(n => console.log("Value:", n)),
662
+ * Result.map(n => n * 2)
663
+ * );
651
664
  * ```
652
665
  */
653
- const number: Equality<number>;
666
+ const tap: <E, A>(f: (a: A) => void) => (data: Result<E, A>) => Result<E, A>;
654
667
  /**
655
- * Equality for booleans.
668
+ * Executes a side effect on the error value without changing the Result.
669
+ * Useful for logging or reporting errors.
656
670
  *
657
671
  * @example
658
672
  * ```ts
659
- * Equality.boolean(true, true); // true
673
+ * pipe(
674
+ * Result.err("not found"),
675
+ * Result.tapError(e => console.error("validation failed:", e)),
676
+ * Result.chain(save),
677
+ * )
660
678
  * ```
661
679
  */
662
- const boolean: Equality<boolean>;
680
+ const tapError: <E, A>(f: (e: E) => void) => (data: Result<E, A>) => Result<E, A>;
663
681
  /**
664
- * Equality for `Date` values. Compares by numeric time value.
682
+ * Creates a Result from a predicate applied to a value.
683
+ * Returns Ok if the predicate passes, Err from onFalse otherwise.
665
684
  *
666
685
  * @example
667
686
  * ```ts
668
- * Equality.date(new Date("2024-01-01"), new Date("2024-01-01")); // true
687
+ * pipe(5, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); // Ok(5)
688
+ * pipe(-1, Result.fromPredicate(n => n > 0, n => `${n} is not positive`)); // Err("-1 is not positive")
689
+ * pipe("", Result.fromPredicate(s => s.length > 0, () => "empty string")); // Err("empty string")
669
690
  * ```
670
691
  */
671
- const date: Equality<Date>;
692
+ const fromPredicate: <E, A>(pred: (a: A) => boolean, onFalse: (a: A) => E) => (a: A) => Result<E, A>;
672
693
  /**
673
- * Lifts an element equality into an array equality. Two arrays are equal if they have the
674
- * same length and every element pair is equal under `eq`.
694
+ * Creates a Result from a nullable value.
695
+ * Returns Ok if the value is not null or undefined, error from onNull otherwise.
675
696
  *
676
697
  * @example
677
698
  * ```ts
678
- * Equality.array(Equality.number)([1, 2, 3], [1, 2, 3]); // true
699
+ * pipe(null, Result.fromNullable(() => "is null")); // Err("is null")
700
+ * pipe(42, Result.fromNullable(() => "is null")); // Ok(42)
679
701
  * ```
680
702
  */
681
- const array: <A>(eq: Equality<A>) => Equality<readonly A[]>;
703
+ const fromNullable: <E>(onNull: () => E) => <A>(value: A | null | undefined) => Result<E, A>;
682
704
  /**
683
- * Adapts an equality for type `A` into an equality for type `B` by extracting a field.
684
- * Read as "equality by this field": `pipe(Equality.string, Equality.by(u => u.name))`.
705
+ * Creates a Result from a Maybe.
706
+ * Some becomes Ok, None becomes error from onNone.
685
707
  *
686
708
  * @example
687
709
  * ```ts
688
- * type Product = { id: string; price: number };
689
- * const byId = pipe(Equality.string, Equality.by((p: Product) => p.id));
690
- * byId({ id: "p1", price: 9 }, { id: "p1", price: 12 }); // true
710
+ * pipe(Maybe.none(), Result.fromMaybe(() => "is none")); // Err("is none")
711
+ * pipe(Maybe.some(42), Result.fromMaybe(() => "is none")); // Ok(42)
691
712
  * ```
692
713
  */
693
- const by: <A, B>(f: (b: B) => A) => (eq: Equality<A>) => Equality<B>;
714
+ const fromMaybe: <E>(onNone: () => E) => <A>(maybe: Maybe<A>) => Result<E, A>;
694
715
  /**
695
- * Combines two equalities with logical AND. Both must pass for two values to be considered equal.
696
- * Data-last: the first equality is the data being piped.
716
+ * Wraps a throwing function of any arguments, returning a new function
717
+ * that catches errors and returns a Result.
697
718
  *
698
719
  * @example
699
720
  * ```ts
700
- * const exact = pipe(byName, Equality.and(byRole));
701
- * exact(userA, userB); // true only if name AND role match
721
+ * const safeParse = Result.fromThrowable(
722
+ * (s: string) => JSON.parse(s),
723
+ * (e) => new Error(`Parse error: ${e}`)
724
+ * );
725
+ *
726
+ * safeParse('{"a":1}'); // Ok({ a: 1 })
727
+ * safeParse('invalid'); // Err(Error)
702
728
  * ```
703
729
  */
704
- const and: <A>(eq2: Equality<A>) => (eq1: Equality<A>) => Equality<A>;
705
- }
706
-
707
- /**
708
- * A function that orders two values of type `A`. Returns a negative number when `a` comes before
709
- * `b`, a positive number when `a` comes after `b`, and `0` when they are equal.
710
- *
711
- * Compatible with `Array.prototype.sort` and `Arr.sortWith`.
712
- *
713
- * @example
714
- * ```ts
715
- * type Employee = { name: string; salary: number };
716
- *
717
- * const byName = pipe(Ordering.string, Ordering.by((e: Employee) => e.name));
718
- * const bySalary = pipe(Ordering.number, Ordering.by((e: Employee) => e.salary));
719
- *
720
- * pipe(employees, Arr.sortWith(pipe(byName, Ordering.thenBy(bySalary))));
721
- * ```
722
- */
723
- type Ordering<A> = (a: A, b: A) => number;
724
- declare namespace Ordering {
730
+ const fromThrowable: <Args extends readonly unknown[], A, E>(f: (...args: Args) => A, onError: (e: unknown) => E) => (...args: Args) => Result<E, A>;
725
731
  /**
726
- * Alphabetical ordering for strings.
732
+ * Recovers from an error by providing a fallback Result.
733
+ * The fallback can produce a different success type, widening the result to `Result<E, A | B>`.
734
+ */
735
+ const recover: <E, A, B>(fallback: (e: E) => Result<E, B>) => (data: Result<E, A>) => Result<E, A | B>;
736
+ /**
737
+ * Recovers from an error unless the predicate `isBlocked` returns true for that error.
738
+ * The fallback can produce a different success type, widening the result to `Result<E, A | B>`.
727
739
  *
728
740
  * @example
729
741
  * ```ts
730
- * Ordering.string("apple", "banana"); // negative
742
+ * pipe(
743
+ * Result.err(new Error("not found")),
744
+ * Result.recoverUnless(e => e.message === "fatal", () => Result.ok(0))
745
+ * ); // Ok(0)
731
746
  * ```
732
747
  */
733
- const string: Ordering<string>;
748
+ const recoverUnless: <E, A, B>(isBlocked: (e: E) => boolean, fallback: () => Result<E, B>) => (data: Result<E, A>) => Result<E, A | B>;
734
749
  /**
735
- * Numeric ordering. Equivalent to `(a, b) => a - b`.
750
+ * Converts a Result to a Maybe.
751
+ * Ok becomes Some, Err becomes None (the error is discarded).
736
752
  *
737
753
  * @example
738
754
  * ```ts
739
- * pipe([3, 1, 2], Arr.sortWith(Ordering.number)); // [1, 2, 3]
755
+ * Result.toMaybe(Result.ok(42)); // Some(42)
756
+ * Result.toMaybe(Result.err("oops")); // None
740
757
  * ```
741
758
  */
742
- const number: Ordering<number>;
759
+ const toMaybe: <E, A>(data: Result<E, A>) => Maybe<A>;
743
760
  /**
744
- * Ordering for `Date` values by numeric time value.
761
+ * Applies a function wrapped in a Result to a value wrapped in a Result.
745
762
  *
746
763
  * @example
747
764
  * ```ts
748
- * pipe(dates, Arr.sortWith(Ordering.date)); // earliest first
765
+ * const add = (a: number) => (b: number) => a + b;
766
+ * pipe(
767
+ * Result.ok(add),
768
+ * Result.ap(Result.ok(5)),
769
+ * Result.ap(Result.ok(3))
770
+ * ); // Ok(8)
749
771
  * ```
750
772
  */
751
- const date: Ordering<Date>;
773
+ const ap: <E, A>(arg: Result<E, A>) => <B>(data: Result<E, (a: A) => B>) => Result<E, B>;
752
774
  /**
753
- * Flips the direction of an ordering.
775
+ * Converts a Result value into an object containing a single property.
776
+ * Initiates the pipeline accumulator record.
754
777
  *
755
778
  * @example
756
779
  * ```ts
757
- * pipe([3, 1, 2], Arr.sortWith(Ordering.reverse(Ordering.number))); // [3, 2, 1]
780
+ * pipe(Result.ok(42), Result.bindTo("value")); // Ok({ value: 42 })
758
781
  * ```
759
782
  */
760
- const reverse: <A>(ord: Ordering<A>) => Ordering<A>;
783
+ const bindTo: <K extends string>(key: K) => <E, A>(data: Result<E, A>) => Result<E, { [P in K]: A; }>;
761
784
  /**
762
- * Chains two orderings: the second is used only when the first returns `0`.
763
- * Data-last: the first ordering is the data being piped.
785
+ * Evaluates a new Result using the current accumulator and attaches the output to a new key.
764
786
  *
765
787
  * @example
766
788
  * ```ts
767
- * const byDeptThenSalary = pipe(byDept, Ordering.thenBy(bySalary));
789
+ * pipe(
790
+ * Result.ok({ a: 1 }),
791
+ * Result.bind("b", ({ a }) => Result.ok(a + 1))
792
+ * ); // Ok({ a: 1, b: 2 })
768
793
  * ```
769
794
  */
770
- const thenBy: <A>(ord2: Ordering<A>) => (ord1: Ordering<A>) => Ordering<A>;
795
+ const bind: <K extends string, E, A, B>(key: K, f: (a: A) => Result<E, B>) => (data: Result<E, A>) => Result<E, A & { [P in K]: B; }>;
771
796
  /**
772
- * Adapts an ordering for type `A` into an ordering for type `B` by extracting a field.
773
- * Read as "ordering by this field": `pipe(Ordering.number, Ordering.by(p => p.price))`.
797
+ * Combines a record of Results into a single Result of a record.
798
+ * Evaluates fields in key order and short-circuits on the first failure.
774
799
  *
775
800
  * @example
776
801
  * ```ts
777
- * type Product = { name: string; price: number };
778
- * const byPrice = pipe(Ordering.number, Ordering.by((p: Product) => p.price));
779
- * pipe(products, Arr.sortWith(byPrice));
802
+ * Result.struct({
803
+ * name: Result.ok("Alice"),
804
+ * age: Result.ok(30)
805
+ * }); // Ok({ name: "Alice", age: 30 })
780
806
  * ```
781
807
  */
782
- const by: <A, B>(f: (b: B) => A) => (ord: Ordering<A>) => Ordering<B>;
808
+ const struct: <E, R extends Record<string, any>>(fields: { [K in keyof R]: Result<E, R[K]>; }) => Result<E, R>;
783
809
  }
784
810
 
785
811
  /**