nalloc 0.0.1 → 0.0.2

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 +19 -5
  13. package/build/option.cjs.map +1 -1
  14. package/build/option.d.ts +22 -1
  15. package/build/option.js +14 -6
  16. package/build/option.js.map +1 -1
  17. package/build/result.cjs +125 -54
  18. package/build/result.cjs.map +1 -1
  19. package/build/result.d.ts +83 -53
  20. package/build/result.js +100 -38
  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 +286 -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 +36 -7
  48. package/src/result.ts +216 -113
  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,52 +29,37 @@ 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
+ * Executes a function that may return sync or async, preserving sync execution when possible.
38
+ * @param fn - Function that may return T or Promise<T>
37
39
  * @param onError - Optional error transformer
38
- * @returns Promise of Ok(result) if successful, Err(error) if rejected
40
+ * @returns Result<T, E> if sync, Promise<Result<T, E>> if async
39
41
  * @example
40
- * await tryAsync(() => fetch('/api').then(r => r.json())) // Ok(data) or Err(error)
42
+ * tryCatchMaybePromise(() => 42) // Ok(42) - sync
43
+ * tryCatchMaybePromise(() => Promise.resolve(42)) // Promise<Ok(42)> - async
44
+ * tryCatchMaybePromise(() => { throw 'err' }) // Err('err') - sync
41
45
  */
42
- export async function tryAsync<T, E = unknown>(fn: () => Promise<T>, onError?: (error: unknown) => E): Promise<Result<T, E>> {
46
+ export function tryCatchMaybePromise<T>(fn: () => MaybePromise<T>): Result<T, unknown> | Promise<Result<T, unknown>>;
47
+ export function tryCatchMaybePromise<T, E>(fn: () => MaybePromise<T>, onError: (error: unknown) => E): Result<T, E> | Promise<Result<T, E>>;
48
+ export function tryCatchMaybePromise<T, E = unknown>(fn: () => MaybePromise<T>, onError?: (error: unknown) => E): Result<T, E> | Promise<Result<T, E>> {
43
49
  try {
44
- return (await fn()) as Ok<T>;
50
+ const result = fn();
51
+ if (isThenable(result)) {
52
+ return Promise.resolve(result).then(
53
+ (value) => value as Ok<T>,
54
+ (error) => ERR(onError ? onError(error) : (error as E)),
55
+ );
56
+ }
57
+ return result as Ok<T>;
45
58
  } catch (error) {
46
59
  return ERR(onError ? onError(error) : (error as E));
47
60
  }
48
61
  }
49
62
 
50
- /**
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
64
- * @example
65
- * await fromPromise(Promise.resolve(42)) // Ok(42)
66
- * await fromPromise(Promise.reject('error')) // Err('error')
67
- */
68
- export async function fromPromise<T, E = unknown>(promise: Promise<T>, onRejected?: (reason: unknown) => E): Promise<Result<T, E>> {
69
- try {
70
- return (await promise) as Ok<T>;
71
- } catch (error) {
72
- return ERR(onRejected ? onRejected(error) : (error as E));
73
- }
74
- }
75
-
76
63
  /**
77
64
  * Unwraps an Ok value or returns a computed value for Err.
78
65
  * @param result - The Result to unwrap
@@ -97,7 +84,7 @@ export function unwrapOrReturn<T, E, const R>(result: Result<T, E>, onErr: (erro
97
84
  */
98
85
  export function assertOk<T, E>(result: Result<T, E>, message?: string): asserts result is Ok<T> {
99
86
  if (isErr(result)) {
100
- throw new Error(message ?? `Expected Ok result. Received error: ${String((result as Err<E>).error)}`);
87
+ throw new Error(message ?? `Expected Ok result. Received error: ${String(result.error)}`);
101
88
  }
102
89
  }
103
90
 
@@ -122,7 +109,7 @@ export function assertErr<T, E>(result: Result<T, E>, message?: string): asserts
122
109
  * @returns true if Err with Some error value
123
110
  */
124
111
  export function isSomeErr<T, E>(result: Result<T, E>): boolean {
125
- return isErr(result) && isSome((result as Err<E>).error);
112
+ return isErr(result) && isSome(result.error);
126
113
  }
127
114
 
128
115
  /**
@@ -138,8 +125,8 @@ export function map<T, U, E>(result: Err<E>, fn: (value: T) => U): Err<E>;
138
125
  export function map<T, U>(result: Ok<T>, fn: (value: T) => U): Ok<U>;
139
126
  export function map<T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E>;
140
127
  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>;
128
+ if (isErr(result)) return result;
129
+ return fn(result) as Ok<U>;
143
130
  }
144
131
 
145
132
  /**
@@ -172,8 +159,8 @@ export function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Resu
172
159
  export function flatMap<T, U, E>(result: Err<E>, fn: (value: T) => Result<U, E>): Err<E>;
173
160
  export function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
174
161
  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>);
162
+ if (isErr(result)) return result;
163
+ return fn(result);
177
164
  }
178
165
 
179
166
  /**
@@ -185,7 +172,7 @@ export function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<
185
172
  export function andThen<T, U, E>(result: Err<E>, fn: (value: T) => Result<U, E>): Err<E>;
186
173
  export function andThen<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
187
174
  export function andThen<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E> {
188
- return flatMap(result, fn);
175
+ return isErr(result) ? result : fn(result);
189
176
  }
190
177
 
191
178
  /**
@@ -219,7 +206,7 @@ export function tapErr<E>(result: Err<E>, fn: (error: E) => void): Err<E>;
219
206
  export function tapErr<T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E>;
220
207
  export function tapErr<T, E>(result: Result<T, E>, fn: (error: E) => void): Result<T, E> {
221
208
  if (isErr(result)) {
222
- fn((result as Err<E>).error);
209
+ fn(result.error);
223
210
  }
224
211
  return result;
225
212
  }
@@ -242,19 +229,22 @@ export function bimap<T, U, E, F>(result: Result<T, E>, okFn: (value: T) => U, e
242
229
  }
243
230
 
244
231
  /**
245
- * Extracts the Ok value, throws if Err.
232
+ * Extracts the Ok value, throws Err if not Ok.
233
+ * Use with safeTry for Rust-like ? operator ergonomics.
246
234
  * @param result - The Result to unwrap
247
235
  * @returns The contained Ok value
248
- * @throws Error if result is Err
236
+ * @throws The Err object itself
249
237
  * @example
250
238
  * unwrap(ok(42)) // 42
251
- * unwrap(err('failed')) // throws Error
239
+ * unwrap(err('failed')) // throws Err
240
+ * safeTry(() => {
241
+ * const a = unwrap(getValue());
242
+ * return a + 1;
243
+ * });
252
244
  */
253
245
  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;
246
+ if (isErr(result)) throw result.error;
247
+ return result;
258
248
  }
259
249
 
260
250
  /**
@@ -339,9 +329,9 @@ export function mapOrElse<T, E, U>(result: Result<T, E>, defaultFn: () => U, fn:
339
329
  */
340
330
  export function expect<T, E>(result: Result<T, E>, message: string): T {
341
331
  if (isErr(result)) {
342
- throw new Error(`${message}: ${String((result as Err<E>).error)}`);
332
+ throw new Error(`${message}: ${String(result.error)}`);
343
333
  }
344
- return result as Ok<T>;
334
+ return result;
345
335
  }
346
336
 
347
337
  /**
@@ -421,7 +411,7 @@ export function toOption<T, E>(result: Result<T, E>): Option<T> {
421
411
  * toErrorOption(ok(42)) // None
422
412
  */
423
413
  export function toErrorOption<T, E>(result: Result<T, E>): Option<E> {
424
- return isErr(result) ? optionOf((result as Err<E>).error) : NONE;
414
+ return isErr(result) ? optionOf(result.error) : NONE;
425
415
  }
426
416
 
427
417
  /**
@@ -434,9 +424,9 @@ export function toErrorOption<T, E>(result: Result<T, E>): Option<E> {
434
424
  * zip(ok(1), err('e')) // Err('e')
435
425
  */
436
426
  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]>;
427
+ if (isErr(left)) return left;
428
+ if (isErr(right)) return right;
429
+ return [left, right] as Ok<[T, U]>;
440
430
  }
441
431
 
442
432
  /**
@@ -449,9 +439,9 @@ export function zip<T, U, E>(left: Result<T, E>, right: Result<U, E>): Result<[T
449
439
  * zipWith(ok(2), ok(3), (a, b) => a + b) // Ok(5)
450
440
  */
451
441
  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>;
442
+ if (isErr(left)) return left;
443
+ if (isErr(right)) return right;
444
+ return fn(left, right) as Ok<V>;
455
445
  }
456
446
 
457
447
  /**
@@ -503,6 +493,36 @@ export function partition<T, E>(results: Result<T, E>[]): [T[], E[]] {
503
493
  return [oks, errs];
504
494
  }
505
495
 
496
+ /**
497
+ * Extracts all Ok values from an iterable of Results.
498
+ * @param results - Iterable of Results
499
+ * @returns Array of Ok values
500
+ * @example
501
+ * filterOk([ok(1), err('a'), ok(2)]) // [1, 2]
502
+ */
503
+ export function filterOk<T, E>(results: Iterable<Result<T, E>>): T[] {
504
+ const oks: T[] = [];
505
+ for (const result of results) {
506
+ if (isOk(result)) oks.push(result);
507
+ }
508
+ return oks;
509
+ }
510
+
511
+ /**
512
+ * Extracts all Err values from an iterable of Results.
513
+ * @param results - Iterable of Results
514
+ * @returns Array of error values
515
+ * @example
516
+ * filterErr([ok(1), err('a'), ok(2)]) // ['a']
517
+ */
518
+ export function filterErr<T, E>(results: Iterable<Result<T, E>>): E[] {
519
+ const errs: E[] = [];
520
+ for (const result of results) {
521
+ if (isErr(result)) errs.push(result.error);
522
+ }
523
+ return errs;
524
+ }
525
+
506
526
  /**
507
527
  * Collects an array of Results into a Result of an array. Fails on first Err.
508
528
  * @param results - Array of Results
@@ -515,10 +535,8 @@ export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
515
535
  const values: T[] = [];
516
536
 
517
537
  for (const result of results) {
518
- if (isErr(result)) {
519
- return result as Err<E>;
520
- }
521
- values.push(result as Ok<T>);
538
+ if (isErr(result)) return result;
539
+ values.push(result);
522
540
  }
523
541
 
524
542
  return values as Ok<T[]>;
@@ -542,8 +560,8 @@ export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
542
560
  * @param results - Array of Results
543
561
  * @returns Ok(values) if all Ok, first Err otherwise
544
562
  */
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>>;
563
+ export function all<T, E>(results: Result<T, E>[]): Result<readonly Widen<T>[], WidenNever<E>> {
564
+ return collect(results) as Result<readonly Widen<T>[], WidenNever<E>>;
547
565
  }
548
566
 
549
567
  /**
@@ -555,12 +573,11 @@ export function all<T, E>(results: Result<T, E>[]): Result<Widen<T>[], WidenNeve
555
573
  * any([err('a'), err('b')]) // Err(['a', 'b'])
556
574
  */
557
575
  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++) {
576
+ const errors: WidenNever<E>[] = [];
577
+ for (let i = 0; i < results.length; i++) {
561
578
  const result = results[i];
562
579
  if (isOk(result)) return result as Ok<Widen<T>>;
563
- errors[i] = (result as Err<WidenNever<E>>).error;
580
+ errors.push((result as Err<WidenNever<E>>).error);
564
581
  }
565
582
  return ERR(errors);
566
583
  }
@@ -576,7 +593,7 @@ export function any<T, E>(results: Result<T, E>[]): Result<Widen<T>, WidenNever<
576
593
  */
577
594
  export function transpose<T, E>(result: Result<Option<T>, E>): Option<Result<T, E>> {
578
595
  if (isErr(result)) {
579
- return ERR((result as Err<E>).error) as Option<Result<T, E>>;
596
+ return ERR(result.error) as Option<Result<T, E>>;
580
597
  }
581
598
  const opt = result as Option<T>;
582
599
  return isNone(opt) ? NONE : (opt as unknown as Ok<T> as Option<Result<T, E>>);
@@ -606,71 +623,157 @@ export function isOkAnd<T, E>(result: Result<T, E>, predicate: (value: T) => boo
606
623
  * isErrAnd(ok(42), e => true) // false
607
624
  */
608
625
  export function isErrAnd<T, E>(result: Result<T, E>, predicate: (error: E) => boolean): boolean {
609
- return isErr(result) && predicate((result as Err<E>).error);
626
+ return isErr(result) && predicate(result.error);
610
627
  }
611
628
 
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);
629
+ export function settledToResult<T, E>(result: PromiseSettledResult<Result<T, E>>): Result<T, E> {
630
+ if (result.status === 'fulfilled') return result.value;
631
+ return ERR(result.reason);
628
632
  }
629
633
 
630
634
  /**
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
635
+ * Partitions an async iterable of Results.
636
+ * @param results - Iterable of Promise Results
637
+ * @returns Promise of [Ok values, Err values]
635
638
  * @example
636
- * await andThenAsync(ok(2), async x => ok(x * 2)) // Ok(4)
639
+ * await partitionAsync([Promise.resolve(ok(1)), Promise.resolve(err('a'))])
640
+ * // [[1], ['a']]
637
641
  */
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>);
642
+ export async function partitionAsync<T, E>(promises: Iterable<Promise<Result<T, E>>>): Promise<[Widen<T>[], WidenNever<E>[]]> {
643
+ const settled = await Promise.allSettled(promises);
644
+ return partition(settled.map(settledToResult)) as [Widen<T>[], WidenNever<E>[]];
641
645
  }
642
646
 
643
647
  /**
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
648
+ * Settles an array of MaybePromise values into Results.
649
+ * Returns synchronously if all inputs are sync, avoiding Promise overhead.
650
+ * @param values - Array of values that may or may not be Promises
651
+ * @returns Array of Results (sync) or Promise of Results (if any async)
652
+ * @example
653
+ * settleMaybePromise([1, 2, 3]) // [Ok(1), Ok(2), Ok(3)] - sync
654
+ * settleMaybePromise([1, Promise.resolve(2)]) // Promise<[Ok(1), Ok(2)]>
655
+ * settleMaybePromise([Promise.reject('e')]) // Promise<[Err('e')]>
649
656
  */
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);
657
+ export function settleMaybePromise<T, E = unknown>(values: MaybePromise<T>[]): Result<T, E>[] | Promise<Result<T, E>[]> {
658
+ const len = values.length;
659
+ const results = new Array<Result<T, E>>(len);
660
+ let pendingIndices: number[] | undefined;
661
+ let pendingPromises: Promise<T>[] | undefined;
662
+
663
+ for (let i = 0; i < len; i++) {
664
+ const v = values[i];
665
+ if (isThenable(v)) {
666
+ (pendingIndices ??= []).push(i);
667
+ (pendingPromises ??= []).push(Promise.resolve(v));
668
+ } else {
669
+ results[i] = v as Ok<T>;
670
+ }
671
+ }
672
+
673
+ if (!pendingPromises) return results;
674
+
675
+ return Promise.allSettled(pendingPromises).then((settled) => {
676
+ for (let i = 0; i < settled.length; i++) {
677
+ const s = settled[i];
678
+ results[pendingIndices![i]] = s.status === 'fulfilled' ? (s.value as Ok<T>) : ERR(s.reason as E);
679
+ }
680
+ return results;
681
+ });
682
+ }
683
+
684
+ export async function partitionMaybePromiseAsync<T, E>(
685
+ values: MaybePromise<Result<T, E>>[],
686
+ oks: Widen<T>[],
687
+ errs: WidenNever<E>[],
688
+ startIndex: number = 0,
689
+ ): Promise<[Widen<T>[], WidenNever<E>[]]> {
690
+ const suffixLength = values.length - startIndex;
691
+ const pending = new Array<Promise<Result<T, E>>>(suffixLength);
692
+
693
+ for (let i = 0; i < suffixLength; i++) {
694
+ const value = values[startIndex + i];
695
+ pending[i] = Promise.resolve(value).then(
696
+ (result) => result as Result<T, E>,
697
+ (error) => ERR(error as E),
698
+ );
699
+ }
700
+
701
+ const resolved = await Promise.all(pending);
702
+ for (let i = 0; i < resolved.length; i++) {
703
+ const result = resolved[i];
704
+ if (isOk(result)) {
705
+ oks.push(result as Widen<T>);
706
+ } else {
707
+ errs.push((result as Err<WidenNever<E>>).error);
708
+ }
709
+ }
710
+ return [oks, errs] as [Widen<T>[], WidenNever<E>[]];
652
711
  }
653
712
 
654
713
  /**
655
- * Partitions an async iterable of Results.
656
- * @param results - Iterable of Promise Results
657
- * @returns Promise of [Ok values, Err values]
714
+ * Partitions MaybePromise Results into Ok and Err values.
715
+ * Returns synchronously if all inputs are sync, avoiding Promise overhead.
716
+ * @param values - Array of MaybePromise Results
717
+ * @returns [Ok values, Err values] (sync) or Promise of same (if any async)
658
718
  * @example
659
- * await partitionAsync([Promise.resolve(ok(1)), Promise.resolve(err('a'))])
660
- * // [[1], ['a']]
719
+ * partitionMaybePromise([ok(1), err('a')]) // [[1], ['a']] - sync
720
+ * partitionMaybePromise([ok(1), Promise.resolve(err('a'))]) // Promise<[[1], ['a']]>
661
721
  */
662
- export async function partitionAsync<T, E>(results: Iterable<Promise<Result<T, E>>>): Promise<[Widen<T>[], WidenNever<E>[]]> {
722
+ export function partitionMaybePromise<T, E>(values: MaybePromise<Result<T, E>>[]): [Widen<T>[], WidenNever<E>[]] | Promise<[Widen<T>[], WidenNever<E>[]]> {
723
+ const len = values.length;
663
724
  const oks: Widen<T>[] = [];
664
725
  const errs: WidenNever<E>[] = [];
665
726
 
666
- for (const promise of results) {
667
- const result = await promise;
668
- if (isOk(result)) {
669
- oks.push(result as Ok<Widen<T>>);
727
+ for (let i = 0; i < len; i++) {
728
+ const value = values[i];
729
+ if (isThenable(value)) {
730
+ return partitionMaybePromiseAsync(values, oks, errs, i);
731
+ }
732
+ if (isOk(value)) {
733
+ oks.push(value as Widen<T>);
670
734
  } else {
671
- errs.push((result as Err<WidenNever<E>>).error);
735
+ errs.push((value as Err<WidenNever<E>>).error);
672
736
  }
673
737
  }
674
738
 
675
739
  return [oks, errs];
676
740
  }
741
+
742
+ /**
743
+ * Executes a function, catching thrown Err values.
744
+ * Use with unwrap for Rust-like ? operator ergonomics.
745
+ * @param fn - Function that may throw Err via unwrap
746
+ * @returns Ok(return value) or the caught Err
747
+ * @example
748
+ * const result = safeTry(() => {
749
+ * const a = unwrap(parseNumber('10'));
750
+ * const b = unwrap(parseNumber('5'));
751
+ * return a + b;
752
+ * }); // Ok(15) or Err(...)
753
+ */
754
+ export function safeTry<T>(fn: () => T): Result<T, unknown> {
755
+ try {
756
+ return fn() as Ok<T>;
757
+ } catch (e) {
758
+ return ERR(e);
759
+ }
760
+ }
761
+
762
+ /**
763
+ * Async version of safeTry.
764
+ * @param fn - Async function that may throw Err via unwrap
765
+ * @returns Promise of Ok(return value) or the caught Err
766
+ * @example
767
+ * const result = await safeTryAsync(async () => {
768
+ * const user = unwrap(await fetchUser(id));
769
+ * const posts = unwrap(await fetchPosts(user.id));
770
+ * return { user, posts };
771
+ * });
772
+ */
773
+ export async function safeTryAsync<T>(fn: () => Promise<T>): Promise<Result<T, unknown>> {
774
+ try {
775
+ return (await fn()) as Ok<T>;
776
+ } catch (e) {
777
+ return ERR(e);
778
+ }
779
+ }
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';