nalloc 0.0.1 → 0.0.3

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 (64) hide show
  1. package/README.md +124 -38
  2. package/build/index.cjs +12 -68
  3. package/build/index.cjs.map +1 -1
  4. package/build/index.d.ts +1 -4
  5. package/build/index.js +1 -3
  6. package/build/index.js.map +1 -1
  7. package/build/iter.cjs +105 -0
  8. package/build/iter.cjs.map +1 -0
  9. package/build/iter.d.ts +61 -0
  10. package/build/iter.js +78 -0
  11. package/build/iter.js.map +1 -0
  12. package/build/option.cjs +22 -7
  13. package/build/option.cjs.map +1 -1
  14. package/build/option.d.ts +22 -1
  15. package/build/option.js +17 -8
  16. package/build/option.js.map +1 -1
  17. package/build/result.cjs +137 -48
  18. package/build/result.cjs.map +1 -1
  19. package/build/result.d.ts +91 -50
  20. package/build/result.js +112 -35
  21. package/build/result.js.map +1 -1
  22. package/build/safe.cjs +34 -15
  23. package/build/safe.cjs.map +1 -1
  24. package/build/safe.d.ts +4 -27
  25. package/build/safe.js +3 -14
  26. package/build/safe.js.map +1 -1
  27. package/build/types.cjs +38 -7
  28. package/build/types.cjs.map +1 -1
  29. package/build/types.d.ts +26 -4
  30. package/build/types.js +23 -7
  31. package/build/types.js.map +1 -1
  32. package/build/unsafe.cjs +14 -61
  33. package/build/unsafe.cjs.map +1 -1
  34. package/build/unsafe.d.ts +2 -27
  35. package/build/unsafe.js +2 -9
  36. package/build/unsafe.js.map +1 -1
  37. package/package.json +13 -16
  38. package/src/__tests__/index.ts +42 -0
  39. package/src/__tests__/iter.ts +218 -0
  40. package/src/__tests__/option.ts +48 -19
  41. package/src/__tests__/result.ts +322 -91
  42. package/src/__tests__/result.types.ts +3 -22
  43. package/src/__tests__/safe.ts +9 -15
  44. package/src/__tests__/unsafe.ts +11 -12
  45. package/src/index.ts +1 -18
  46. package/src/iter.ts +129 -0
  47. package/src/option.ts +39 -9
  48. package/src/result.ts +236 -106
  49. package/src/safe.ts +5 -42
  50. package/src/types.ts +52 -14
  51. package/src/unsafe.ts +2 -47
  52. package/build/devtools.cjs +0 -79
  53. package/build/devtools.cjs.map +0 -1
  54. package/build/devtools.d.ts +0 -82
  55. package/build/devtools.js +0 -43
  56. package/build/devtools.js.map +0 -1
  57. package/build/testing.cjs +0 -111
  58. package/build/testing.cjs.map +0 -1
  59. package/build/testing.d.ts +0 -85
  60. package/build/testing.js +0 -81
  61. package/build/testing.js.map +0 -1
  62. package/src/__tests__/tooling.ts +0 -86
  63. package/src/devtools.ts +0 -97
  64. package/src/testing.ts +0 -159
package/src/result.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { err as ERR, isOk, isErr, isSome, isNone, NONE, optionOf } from './types.js';
2
- import type { Ok, Err, Result, Option, Widen, WidenNever } from './types.js';
1
+ import { err as ERR, isOk, isErr, isSome, isNone, NONE, optionOf, isThenable } from './types.js';
2
+ import type { Ok, Err, Result, Option, Widen, WidenNever, MaybePromise } from './types.js';
3
3
 
4
4
  export type { Ok, Err, Result };
5
5
  export { isOk, isErr };
@@ -14,6 +14,8 @@ export { isOk, isErr };
14
14
  * tryCatch(() => JSON.parse('invalid')) // Err(SyntaxError)
15
15
  * tryCatch(() => { throw 'oops' }, e => e) // Err('oops')
16
16
  */
17
+ export function tryCatch<T>(fn: () => T): Result<T, unknown>;
18
+ export function tryCatch<T, E>(fn: () => T, onError: (error: unknown) => E): Result<T, E>;
17
19
  export function tryCatch<T, E = unknown>(fn: () => T, onError?: (error: unknown) => E): Result<T, E> {
18
20
  try {
19
21
  return fn() as Ok<T>;
@@ -27,49 +29,53 @@ export function tryCatch<T, E = unknown>(fn: () => T, onError?: (error: unknown)
27
29
  * @param fn - Function to execute
28
30
  * @returns Ok(result) if successful, Err(error) if thrown
29
31
  */
30
- export function of<T, E = unknown>(fn: () => T): Result<T, E> {
32
+ export function of<T>(fn: () => T): Result<T, unknown> {
31
33
  return tryCatch(fn);
32
34
  }
33
35
 
34
36
  /**
35
- * Executes an async function and captures the result or error.
36
- * @param fn - Async function to execute
37
+ * Converts a Promise to a Result. Resolves to Ok if successful, Err on rejection.
38
+ * @param promise - The promise to convert
37
39
  * @param onError - Optional error transformer
38
- * @returns Promise of Ok(result) if successful, Err(error) if rejected
40
+ * @returns Promise resolving to Ok(value) or Err(error)
39
41
  * @example
40
- * await tryAsync(() => fetch('/api').then(r => r.json())) // Ok(data) or Err(error)
42
+ * await fromPromise(fetch('/api')) // Ok(Response) or Err(unknown)
43
+ * await fromPromise(fetch('/api'), e => String(e)) // Ok(Response) or Err(string)
41
44
  */
42
- export async function tryAsync<T, E = unknown>(fn: () => Promise<T>, onError?: (error: unknown) => E): Promise<Result<T, E>> {
45
+ export async function fromPromise<T>(promise: Promise<T>): Promise<Result<T, unknown>>;
46
+ export async function fromPromise<T, E>(promise: Promise<T>, onError: (error: unknown) => E): Promise<Result<T, E>>;
47
+ export async function fromPromise<T, E = unknown>(promise: Promise<T>, onError?: (error: unknown) => E): Promise<Result<T, E>> {
43
48
  try {
44
- return (await fn()) as Ok<T>;
49
+ return (await promise) as Ok<T>;
45
50
  } catch (error) {
46
51
  return ERR(onError ? onError(error) : (error as E));
47
52
  }
48
53
  }
49
54
 
50
55
  /**
51
- * Alias for tryAsync. Executes an async function and captures the result or error.
52
- * @param fn - Async function to execute
53
- * @returns Promise of Ok(result) if successful, Err(error) if rejected
54
- */
55
- export function ofAsync<T, E = unknown>(fn: () => Promise<T>): Promise<Result<T, E>> {
56
- return tryAsync(fn);
57
- }
58
-
59
- /**
60
- * Converts a Promise to a Result.
61
- * @param promise - The promise to convert
62
- * @param onRejected - Optional rejection handler
63
- * @returns Promise of Ok(value) if resolved, Err(error) if rejected
56
+ * Executes a function that may return sync or async, preserving sync execution when possible.
57
+ * @param fn - Function that may return T or Promise<T>
58
+ * @param onError - Optional error transformer
59
+ * @returns Result<T, E> if sync, Promise<Result<T, E>> if async
64
60
  * @example
65
- * await fromPromise(Promise.resolve(42)) // Ok(42)
66
- * await fromPromise(Promise.reject('error')) // Err('error')
61
+ * tryCatchMaybePromise(() => 42) // Ok(42) - sync
62
+ * tryCatchMaybePromise(() => Promise.resolve(42)) // Promise<Ok(42)> - async
63
+ * tryCatchMaybePromise(() => { throw 'err' }) // Err('err') - sync
67
64
  */
68
- export async function fromPromise<T, E = unknown>(promise: Promise<T>, onRejected?: (reason: unknown) => E): Promise<Result<T, E>> {
65
+ export function tryCatchMaybePromise<T>(fn: () => MaybePromise<T>): Result<T, unknown> | Promise<Result<T, unknown>>;
66
+ export function tryCatchMaybePromise<T, E>(fn: () => MaybePromise<T>, onError: (error: unknown) => E): Result<T, E> | Promise<Result<T, E>>;
67
+ export function tryCatchMaybePromise<T, E = unknown>(fn: () => MaybePromise<T>, onError?: (error: unknown) => E): Result<T, E> | Promise<Result<T, E>> {
69
68
  try {
70
- return (await promise) as Ok<T>;
69
+ const result = fn();
70
+ if (isThenable(result)) {
71
+ return Promise.resolve(result).then(
72
+ (value) => value as Ok<T>,
73
+ (error) => ERR(onError ? onError(error) : (error as E)),
74
+ );
75
+ }
76
+ return result as Ok<T>;
71
77
  } catch (error) {
72
- return ERR(onRejected ? onRejected(error) : (error as E));
78
+ return ERR(onError ? onError(error) : (error as E));
73
79
  }
74
80
  }
75
81
 
@@ -97,7 +103,7 @@ export function unwrapOrReturn<T, E, const R>(result: Result<T, E>, onErr: (erro
97
103
  */
98
104
  export function assertOk<T, E>(result: Result<T, E>, message?: string): asserts result is Ok<T> {
99
105
  if (isErr(result)) {
100
- throw new Error(message ?? `Expected Ok result. Received error: ${String((result as Err<E>).error)}`);
106
+ throw new Error(message ?? `Expected Ok result. Received error: ${String(result.error)}`);
101
107
  }
102
108
  }
103
109
 
@@ -122,7 +128,7 @@ export function assertErr<T, E>(result: Result<T, E>, message?: string): asserts
122
128
  * @returns true if Err with Some error value
123
129
  */
124
130
  export function isSomeErr<T, E>(result: Result<T, E>): boolean {
125
- return isErr(result) && isSome((result as Err<E>).error);
131
+ return isErr(result) && isSome(result.error);
126
132
  }
127
133
 
128
134
  /**
@@ -138,8 +144,8 @@ export function map<T, U, E>(result: Err<E>, fn: (value: T) => U): Err<E>;
138
144
  export function map<T, U>(result: Ok<T>, fn: (value: T) => U): Ok<U>;
139
145
  export function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
140
146
  export function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
141
- if (isErr(result)) return result as Err<E>;
142
- return fn(result as Ok<T>) as Ok<U>;
147
+ if (isErr(result)) return result;
148
+ return fn(result) as Ok<U>;
143
149
  }
144
150
 
145
151
  /**
@@ -172,8 +178,8 @@ export function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Resu
172
178
  export function flatMap<T, U, E>(result: Err<E>, fn: (value: T) => Result<U, E>): Err<E>;
173
179
  export function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
174
180
  export function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E> {
175
- if (isErr(result)) return result as Err<E>;
176
- return fn(result as Ok<T>);
181
+ if (isErr(result)) return result;
182
+ return fn(result);
177
183
  }
178
184
 
179
185
  /**
@@ -185,7 +191,7 @@ export function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<
185
191
  export function andThen<T, U, E>(result: Err<E>, fn: (value: T) => Result<U, E>): Err<E>;
186
192
  export function andThen<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
187
193
  export function andThen<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E> {
188
- return flatMap(result, fn);
194
+ return isErr(result) ? result : fn(result);
189
195
  }
190
196
 
191
197
  /**
@@ -219,7 +225,7 @@ export function tapErr<E>(result: Err<E>, fn: (error: E) => void): Err<E>;
219
225
  export function tapErr<T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E>;
220
226
  export function tapErr<T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E> {
221
227
  if (isErr(result)) {
222
- fn((result as Err<E>).error);
228
+ fn(result.error);
223
229
  }
224
230
  return result;
225
231
  }
@@ -242,19 +248,22 @@ export function bimap<T, U, E, F>(result: Result<T, E>, okFn: (value: T) => U, e
242
248
  }
243
249
 
244
250
  /**
245
- * Extracts the Ok value, throws if Err.
251
+ * Extracts the Ok value, throws Err if not Ok.
252
+ * Use with safeTry for Rust-like ? operator ergonomics.
246
253
  * @param result - The Result to unwrap
247
254
  * @returns The contained Ok value
248
- * @throws Error if result is Err
255
+ * @throws The Err object itself
249
256
  * @example
250
257
  * unwrap(ok(42)) // 42
251
- * unwrap(err('failed')) // throws Error
258
+ * unwrap(err('failed')) // throws Err
259
+ * safeTry(() => {
260
+ * const a = unwrap(getValue());
261
+ * return a + 1;
262
+ * });
252
263
  */
253
264
  export function unwrap<T, E>(result: Result<T, E>): T {
254
- if (isErr(result)) {
255
- throw new Error(`Called unwrap on Err: ${String((result as Err<E>).error)}`);
256
- }
257
- return result as T;
265
+ if (isErr(result)) throw result.error;
266
+ return result;
258
267
  }
259
268
 
260
269
  /**
@@ -339,9 +348,9 @@ export function mapOrElse<T, E, U>(result: Result<T, E>, defaultFn: () => U, fn:
339
348
  */
340
349
  export function expect<T, E>(result: Result<T, E>, message: string): T {
341
350
  if (isErr(result)) {
342
- throw new Error(`${message}: ${String((result as Err<E>).error)}`);
351
+ throw new Error(`${message}: ${String(result.error)}`);
343
352
  }
344
- return result as Ok<T>;
353
+ return result;
345
354
  }
346
355
 
347
356
  /**
@@ -421,7 +430,7 @@ export function toOption<T, E>(result: Result<T, E>): Option<T> {
421
430
  * toErrorOption(ok(42)) // None
422
431
  */
423
432
  export function toErrorOption<T, E>(result: Result<T, E>): Option<E> {
424
- return isErr(result) ? optionOf((result as Err<E>).error) : NONE;
433
+ return isErr(result) ? optionOf(result.error) : NONE;
425
434
  }
426
435
 
427
436
  /**
@@ -434,9 +443,9 @@ export function toErrorOption<T, E>(result: Result<T, E>): Option<E> {
434
443
  * zip(ok(1), err('e')) // Err('e')
435
444
  */
436
445
  export function zip<T, U, E>(left: Result<T, E>, right: Result<U, E>): Result<[T, U], E> {
437
- if (isErr(left)) return left as Err<E>;
438
- if (isErr(right)) return right as Err<E>;
439
- return [left as Ok<T>, right as Ok<U>] as Ok<[T, U]>;
446
+ if (isErr(left)) return left;
447
+ if (isErr(right)) return right;
448
+ return [left, right] as Ok<[T, U]>;
440
449
  }
441
450
 
442
451
  /**
@@ -449,9 +458,9 @@ export function zip<T, U, E>(left: Result<T, E>, right: Result<U, E>): Result<[T
449
458
  * zipWith(ok(2), ok(3), (a, b) => a + b) // Ok(5)
450
459
  */
451
460
  export function zipWith<T, U, V, E>(left: Result<T, E>, right: Result<U, E>, fn: (left: T, right: U) => V): Result<V, E> {
452
- if (isErr(left)) return left as Err<E>;
453
- if (isErr(right)) return right as Err<E>;
454
- return fn(left as Ok<T>, right as Ok<U>) as Ok<V>;
461
+ if (isErr(left)) return left;
462
+ if (isErr(right)) return right;
463
+ return fn(left, right) as Ok<V>;
455
464
  }
456
465
 
457
466
  /**
@@ -503,6 +512,36 @@ export function partition<T, E>(results: Result<T, E>[]): [T[], E[]] {
503
512
  return [oks, errs];
504
513
  }
505
514
 
515
+ /**
516
+ * Extracts all Ok values from an iterable of Results.
517
+ * @param results - Iterable of Results
518
+ * @returns Array of Ok values
519
+ * @example
520
+ * filterOk([ok(1), err('a'), ok(2)]) // [1, 2]
521
+ */
522
+ export function filterOk<T, E>(results: Iterable<Result<T, E>>): T[] {
523
+ const oks: T[] = [];
524
+ for (const result of results) {
525
+ if (isOk(result)) oks.push(result);
526
+ }
527
+ return oks;
528
+ }
529
+
530
+ /**
531
+ * Extracts all Err values from an iterable of Results.
532
+ * @param results - Iterable of Results
533
+ * @returns Array of error values
534
+ * @example
535
+ * filterErr([ok(1), err('a'), ok(2)]) // ['a']
536
+ */
537
+ export function filterErr<T, E>(results: Iterable<Result<T, E>>): E[] {
538
+ const errs: E[] = [];
539
+ for (const result of results) {
540
+ if (isErr(result)) errs.push(result.error);
541
+ }
542
+ return errs;
543
+ }
544
+
506
545
  /**
507
546
  * Collects an array of Results into a Result of an array. Fails on first Err.
508
547
  * @param results - Array of Results
@@ -515,10 +554,8 @@ export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
515
554
  const values: T[] = [];
516
555
 
517
556
  for (const result of results) {
518
- if (isErr(result)) {
519
- return result as Err<E>;
520
- }
521
- values.push(result as Ok<T>);
557
+ if (isErr(result)) return result;
558
+ values.push(result);
522
559
  }
523
560
 
524
561
  return values as Ok<T[]>;
@@ -533,7 +570,15 @@ export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
533
570
  * collectAll([ok(1), err('a'), err('b')]) // Err(['a', 'b'])
534
571
  */
535
572
  export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
536
- const [oks, errs] = partition(results);
573
+ const oks: T[] = [];
574
+ const errs: E[] = [];
575
+ for (const result of results) {
576
+ if (isOk(result)) {
577
+ oks.push(result);
578
+ } else {
579
+ errs.push(result.error);
580
+ }
581
+ }
537
582
  return errs.length > 0 ? ERR(errs) : (oks as Ok<T[]>);
538
583
  }
539
584
 
@@ -542,8 +587,8 @@ export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
542
587
  * @param results - Array of Results
543
588
  * @returns Ok(values) if all Ok, first Err otherwise
544
589
  */
545
- export function all<T, E>(results: Result<T, E>[]): Result<Widen<T>[], WidenNever<E>> {
546
- return collect(results) as Result<Widen<T>[], WidenNever<E>>;
590
+ export function all<T, E>(results: Result<T, E>[]): Result<readonly Widen<T>[], WidenNever<E>> {
591
+ return collect(results) as Result<readonly Widen<T>[], WidenNever<E>>;
547
592
  }
548
593
 
549
594
  /**
@@ -555,12 +600,11 @@ export function all<T, E>(results: Result<T, E>[]): Result<Widen<T>[], WidenNeve
555
600
  * any([err('a'), err('b')]) // Err(['a', 'b'])
556
601
  */
557
602
  export function any<T, E>(results: Result<T, E>[]): Result<Widen<T>, WidenNever<E>[]> {
558
- const len = results.length;
559
- const errors = new Array<WidenNever<E>>(len);
560
- for (let i = 0; i < len; i++) {
603
+ const errors: WidenNever<E>[] = [];
604
+ for (let i = 0; i < results.length; i++) {
561
605
  const result = results[i];
562
606
  if (isOk(result)) return result as Ok<Widen<T>>;
563
- errors[i] = (result as Err<WidenNever<E>>).error;
607
+ errors.push((result as Err<WidenNever<E>>).error);
564
608
  }
565
609
  return ERR(errors);
566
610
  }
@@ -576,7 +620,7 @@ export function any<T, E>(results: Result<T, E>[]): Result<Widen<T>, WidenNever<
576
620
  */
577
621
  export function transpose<T, E>(result: Result<Option<T>, E>): Option<Result<T, E>> {
578
622
  if (isErr(result)) {
579
- return ERR((result as Err<E>).error) as Option<Result<T, E>>;
623
+ return ERR(result.error) as Option<Result<T, E>>;
580
624
  }
581
625
  const opt = result as Option<T>;
582
626
  return isNone(opt) ? NONE : (opt as unknown as Ok<T> as Option<Result<T, E>>);
@@ -606,71 +650,157 @@ export function isOkAnd<T, E>(result: Result<T, E>, predicate: (value: T) => boo
606
650
  * isErrAnd(ok(42), e => true) // false
607
651
  */
608
652
  export function isErrAnd<T, E>(result: Result<T, E>, predicate: (error: E) => boolean): boolean {
609
- return isErr(result) && predicate((result as Err<E>).error);
653
+ return isErr(result) && predicate(result.error);
610
654
  }
611
655
 
612
- /**
613
- * Maps an async function over an Ok value.
614
- * @param result - The Result to map
615
- * @param fn - Async transform function
616
- * @param onRejected - Optional rejection handler
617
- * @returns Promise of mapped Result
618
- * @example
619
- * await mapAsync(ok(2), async x => x * 2) // Ok(4)
620
- */
621
- export async function mapAsync<T, U, E = unknown>(
622
- result: Result<T, E>,
623
- fn: (value: T) => Promise<U>,
624
- onRejected?: (error: unknown) => E,
625
- ): Promise<Result<U, E>> {
626
- if (isErr(result)) return result as Err<E>;
627
- return fromPromise(fn(result as Ok<T>), onRejected);
656
+ export function settledToResult<T, E>(result: PromiseSettledResult<Result<T, E>>): Result<T, E> {
657
+ if (result.status === 'fulfilled') return result.value;
658
+ return ERR(result.reason);
628
659
  }
629
660
 
630
661
  /**
631
- * Chains an async Result-returning function.
632
- * @param result - The Result to chain
633
- * @param fn - Async function returning a Result
634
- * @returns Promise of the chained Result
662
+ * Partitions an async iterable of Results.
663
+ * @param results - Iterable of Promise Results
664
+ * @returns Promise of [Ok values, Err values]
635
665
  * @example
636
- * await andThenAsync(ok(2), async x => ok(x * 2)) // Ok(4)
666
+ * await partitionAsync([Promise.resolve(ok(1)), Promise.resolve(err('a'))])
667
+ * // [[1], ['a']]
637
668
  */
638
- export async function andThenAsync<T, U, E>(result: Result<T, E>, fn: (value: T) => Promise<Result<U, E>>): Promise<Result<U, E>> {
639
- if (isErr(result)) return result as Err<E>;
640
- return fn(result as Ok<T>);
669
+ export async function partitionAsync<T, E>(promises: Iterable<Promise<Result<T, E>>>): Promise<[Widen<T>[], WidenNever<E>[]]> {
670
+ const settled = await Promise.allSettled(promises);
671
+ return partition(settled.map(settledToResult)) as [Widen<T>[], WidenNever<E>[]];
641
672
  }
642
673
 
643
674
  /**
644
- * Pattern matches with async handlers.
645
- * @param result - The Result to match
646
- * @param onOk - Async handler for Ok
647
- * @param onErr - Async handler for Err
648
- * @returns Promise of the handler result
675
+ * Settles an array of MaybePromise values into Results.
676
+ * Returns synchronously if all inputs are sync, avoiding Promise overhead.
677
+ * @param values - Array of values that may or may not be Promises
678
+ * @returns Array of Results (sync) or Promise of Results (if any async)
679
+ * @example
680
+ * settleMaybePromise([1, 2, 3]) // [Ok(1), Ok(2), Ok(3)] - sync
681
+ * settleMaybePromise([1, Promise.resolve(2)]) // Promise<[Ok(1), Ok(2)]>
682
+ * settleMaybePromise([Promise.reject('e')]) // Promise<[Err('e')]>
649
683
  */
650
- export async function matchAsync<T, E, U>(result: Result<T, E>, onOk: (value: T) => Promise<U>, onErr: (error: E) => Promise<U>): Promise<U> {
651
- return isOk(result) ? onOk(result) : onErr(result.error);
684
+ export function settleMaybePromise<T, E = unknown>(values: MaybePromise<T>[]): Result<T, E>[] | Promise<Result<T, E>[]> {
685
+ const len = values.length;
686
+ const results = new Array<Result<T, E>>(len);
687
+ let pendingIndices: number[] | undefined;
688
+ let pendingPromises: Promise<T>[] | undefined;
689
+
690
+ for (let i = 0; i < len; i++) {
691
+ const v = values[i];
692
+ if (isThenable(v)) {
693
+ (pendingIndices ??= []).push(i);
694
+ (pendingPromises ??= []).push(Promise.resolve(v));
695
+ } else {
696
+ results[i] = v as Ok<T>;
697
+ }
698
+ }
699
+
700
+ if (!pendingPromises) return results;
701
+
702
+ return Promise.allSettled(pendingPromises).then((settled) => {
703
+ for (let i = 0; i < settled.length; i++) {
704
+ const s = settled[i];
705
+ results[pendingIndices![i]] = s.status === 'fulfilled' ? (s.value as Ok<T>) : ERR(s.reason as E);
706
+ }
707
+ return results;
708
+ });
709
+ }
710
+
711
+ export async function partitionMaybePromiseAsync<T, E>(
712
+ values: MaybePromise<Result<T, E>>[],
713
+ oks: Widen<T>[],
714
+ errs: WidenNever<E>[],
715
+ startIndex: number = 0,
716
+ ): Promise<[Widen<T>[], WidenNever<E>[]]> {
717
+ const suffixLength = values.length - startIndex;
718
+ const pending = new Array<Promise<Result<T, E>>>(suffixLength);
719
+
720
+ for (let i = 0; i < suffixLength; i++) {
721
+ const value = values[startIndex + i];
722
+ pending[i] = Promise.resolve(value).then(
723
+ (result) => result as Result<T, E>,
724
+ (error) => ERR(error as E),
725
+ );
726
+ }
727
+
728
+ const resolved = await Promise.all(pending);
729
+ for (let i = 0; i < resolved.length; i++) {
730
+ const result = resolved[i];
731
+ if (isOk(result)) {
732
+ oks.push(result as Widen<T>);
733
+ } else {
734
+ errs.push((result as Err<WidenNever<E>>).error);
735
+ }
736
+ }
737
+ return [oks, errs] as [Widen<T>[], WidenNever<E>[]];
652
738
  }
653
739
 
654
740
  /**
655
- * Partitions an async iterable of Results.
656
- * @param results - Iterable of Promise Results
657
- * @returns Promise of [Ok values, Err values]
741
+ * Partitions MaybePromise Results into Ok and Err values.
742
+ * Returns synchronously if all inputs are sync, avoiding Promise overhead.
743
+ * @param values - Array of MaybePromise Results
744
+ * @returns [Ok values, Err values] (sync) or Promise of same (if any async)
658
745
  * @example
659
- * await partitionAsync([Promise.resolve(ok(1)), Promise.resolve(err('a'))])
660
- * // [[1], ['a']]
746
+ * partitionMaybePromise([ok(1), err('a')]) // [[1], ['a']] - sync
747
+ * partitionMaybePromise([ok(1), Promise.resolve(err('a'))]) // Promise<[[1], ['a']]>
661
748
  */
662
- export async function partitionAsync<T, E>(results: Iterable<Promise<Result<T, E>>>): Promise<[Widen<T>[], WidenNever<E>[]]> {
749
+ export function partitionMaybePromise<T, E>(values: MaybePromise<Result<T, E>>[]): [Widen<T>[], WidenNever<E>[]] | Promise<[Widen<T>[], WidenNever<E>[]]> {
750
+ const len = values.length;
663
751
  const oks: Widen<T>[] = [];
664
752
  const errs: WidenNever<E>[] = [];
665
753
 
666
- for (const promise of results) {
667
- const result = await promise;
668
- if (isOk(result)) {
669
- oks.push(result as Ok<Widen<T>>);
754
+ for (let i = 0; i < len; i++) {
755
+ const value = values[i];
756
+ if (isThenable(value)) {
757
+ return partitionMaybePromiseAsync(values, oks, errs, i);
758
+ }
759
+ if (isOk(value)) {
760
+ oks.push(value as Widen<T>);
670
761
  } else {
671
- errs.push((result as Err<WidenNever<E>>).error);
762
+ errs.push((value as Err<WidenNever<E>>).error);
672
763
  }
673
764
  }
674
765
 
675
766
  return [oks, errs];
676
767
  }
768
+
769
+ /**
770
+ * Executes a function, catching thrown Err values.
771
+ * Use with unwrap for Rust-like ? operator ergonomics.
772
+ * @param fn - Function that may throw Err via unwrap
773
+ * @returns Ok(return value) or the caught Err
774
+ * @example
775
+ * const result = safeTry(() => {
776
+ * const a = unwrap(parseNumber('10'));
777
+ * const b = unwrap(parseNumber('5'));
778
+ * return a + b;
779
+ * }); // Ok(15) or Err(...)
780
+ */
781
+ export function safeTry<T>(fn: () => T): Result<T, unknown> {
782
+ try {
783
+ return fn() as Ok<T>;
784
+ } catch (e) {
785
+ return ERR(e);
786
+ }
787
+ }
788
+
789
+ /**
790
+ * Async version of safeTry.
791
+ * @param fn - Async function that may throw Err via unwrap
792
+ * @returns Promise of Ok(return value) or the caught Err
793
+ * @example
794
+ * const result = await safeTryAsync(async () => {
795
+ * const user = unwrap(await fetchUser(id));
796
+ * const posts = unwrap(await fetchPosts(user.id));
797
+ * return { user, posts };
798
+ * });
799
+ */
800
+ export async function safeTryAsync<T>(fn: () => Promise<T>): Promise<Result<T, unknown>> {
801
+ try {
802
+ return (await fn()) as Ok<T>;
803
+ } catch (e) {
804
+ return ERR(e);
805
+ }
806
+ }
package/src/safe.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { some as someUnsafe, ok as okUnsafe, err, none, isErr } from './types.js';
2
- import type {
1
+ export { some, none, ok, err, isOk, isErr, isSome, isNone, isThenable, isSync } from './types.js';
2
+ export type {
3
3
  Some,
4
4
  None,
5
5
  Ok,
@@ -13,46 +13,9 @@ import type {
13
13
  ResultErrorType,
14
14
  IsResult,
15
15
  InferErr,
16
- ValueType,
16
+ MaybePromise,
17
17
  } from './types.js';
18
-
19
- export type { Some, None, Ok, Err, OptionType, OptionValue, IsOption, InferSome, ResultType, ResultValue, ResultErrorType, IsResult, InferErr };
20
-
21
- export { none, err };
18
+ export { safeTry, safeTryAsync, unwrap } from './result.js';
22
19
  export * as Option from './option.js';
23
20
  export * as Result from './result.js';
24
-
25
- /**
26
- * Creates a Some value with runtime validation.
27
- * Throws if the value is null or undefined.
28
- * @param value - The value to wrap (must be non-nullable)
29
- * @returns The value typed as Some
30
- * @throws {TypeError} If value is null or undefined
31
- * @example
32
- * some(42) // Some(42)
33
- * some(null) // throws TypeError
34
- * some(undefined) // throws TypeError
35
- */
36
- export function some<T>(value: T): Some<ValueType<T>> {
37
- if (value === null || value === undefined) {
38
- throw new TypeError('some() requires a non-nullable value');
39
- }
40
- return someUnsafe(value as ValueType<T>);
41
- }
42
-
43
- /**
44
- * Creates an Ok value with runtime validation.
45
- * Throws if the value is an Err (prevents accidental double-wrapping).
46
- * @param value - The success value
47
- * @returns The value typed as Ok
48
- * @throws {TypeError} If value is an Err
49
- * @example
50
- * ok(42) // Ok(42)
51
- * ok(err('fail')) // throws TypeError
52
- */
53
- export function ok<T>(value: T): Ok<T> {
54
- if (isErr(value)) {
55
- throw new TypeError('ok() cannot wrap an Err value');
56
- }
57
- return okUnsafe(value);
58
- }
21
+ export * as Iter from './iter.js';