nalloc 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/result.ts CHANGED
@@ -4,6 +4,9 @@ import type { Ok, Err, Result, Option, Widen, WidenNever, MaybePromise } from '.
4
4
  export type { Ok, Err, Result };
5
5
  export { isOk, isErr };
6
6
 
7
+ const identity = <T>(v: T): T => v;
8
+ const wrapErr = (error: unknown): Err<never> => ERR(error) as Err<never>;
9
+
7
10
  /**
8
11
  * Executes a function and captures the result or error.
9
12
  * @param fn - Function to execute
@@ -14,8 +17,11 @@ export { isOk, isErr };
14
17
  * tryCatch(() => JSON.parse('invalid')) // Err(SyntaxError)
15
18
  * tryCatch(() => { throw 'oops' }, e => e) // Err('oops')
16
19
  */
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>;
20
+ export function tryCatch<T>(fn: () => T): T extends Err<any> ? Err<unknown> : T extends Result<infer U, any> ? Result<U, unknown> : Result<T, unknown>;
21
+ export function tryCatch<T, E>(
22
+ fn: () => T,
23
+ onError: (error: unknown) => E,
24
+ ): T extends Err<infer F> ? Err<F | E> : T extends Result<infer U, infer F> ? Result<U, F | E> : Result<T, E>;
19
25
  export function tryCatch<T, E = unknown>(fn: () => T, onError?: (error: unknown) => E): Result<T, E> {
20
26
  try {
21
27
  return fn() as Ok<T>;
@@ -29,8 +35,55 @@ export function tryCatch<T, E = unknown>(fn: () => T, onError?: (error: unknown)
29
35
  * @param fn - Function to execute
30
36
  * @returns Ok(result) if successful, Err(error) if thrown
31
37
  */
38
+ export function of<T>(fn: () => T): T extends Err<any> ? Err<unknown> : T extends Result<infer U, any> ? Result<U, unknown> : Result<T, unknown>;
32
39
  export function of<T>(fn: () => T): Result<T, unknown> {
33
- return tryCatch(fn);
40
+ try {
41
+ return fn() as Ok<T>;
42
+ } catch (error) {
43
+ return ERR(error);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Wraps a throwing function so it returns a Result instead.
49
+ * The wrapper is created once and reused, avoiding per-call closure allocation.
50
+ * @param fn - The function to wrap
51
+ * @param onError - Optional error transformer
52
+ * @returns A new function with the same signature that returns Result
53
+ * @example
54
+ * const safeParse = wrap(JSON.parse);
55
+ * safeParse('{"a":1}') // Ok({a: 1})
56
+ * safeParse('invalid') // Err(SyntaxError)
57
+ */
58
+ export function wrap<A extends unknown[], R>(fn: (...args: A) => R): (...args: A) => Result<R, unknown>;
59
+ export function wrap<A extends unknown[], R, E>(fn: (...args: A) => R, onError: (error: unknown) => E): (...args: A) => Result<R, E>;
60
+ export function wrap<A extends unknown[], R, E = unknown>(fn: (...args: A) => R, onError?: (error: unknown) => E): (...args: A) => Result<R, E> {
61
+ return (...args: A) => {
62
+ try {
63
+ return fn(...args) as Ok<R>;
64
+ } catch (error) {
65
+ return ERR(onError ? onError(error) : (error as E));
66
+ }
67
+ };
68
+ }
69
+
70
+ /**
71
+ * Converts a Result-returning function into a throwing function.
72
+ * Inverse of wrap. Use at ecosystem boundaries where libraries expect exceptions
73
+ * (Express error handlers, Drizzle transactions, Passport.js, etc.).
74
+ * @param fn - A function that returns a Result
75
+ * @returns A new function that returns the Ok value or throws the Err error
76
+ * @example
77
+ * const findUser = (id: string): Result<User, NotFoundError> => { ... };
78
+ * const throwingFind = toThrowable(findUser);
79
+ * throwingFind('123') // returns User or throws NotFoundError
80
+ */
81
+ export function toThrowable<A extends unknown[], R, E>(fn: (...args: A) => Result<R, E>): (...args: A) => R {
82
+ return (...args: A) => {
83
+ const result = fn(...args);
84
+ if (isErr(result)) throw result.error;
85
+ return result;
86
+ };
34
87
  }
35
88
 
36
89
  /**
@@ -42,8 +95,8 @@ export function of<T>(fn: () => T): Result<T, unknown> {
42
95
  * await fromPromise(fetch('/api')) // Ok(Response) or Err(unknown)
43
96
  * await fromPromise(fetch('/api'), e => String(e)) // Ok(Response) or Err(string)
44
97
  */
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>>;
98
+ export async function fromPromise<T>(promise: Promise<Exclude<T, Err<any>>>): Promise<Result<T, unknown>>;
99
+ export async function fromPromise<T, E>(promise: Promise<Exclude<T, Err<any>>>, onError: (error: unknown) => E): Promise<Result<T, E>>;
47
100
  export async function fromPromise<T, E = unknown>(promise: Promise<T>, onError?: (error: unknown) => E): Promise<Result<T, E>> {
48
101
  try {
49
102
  return (await promise) as Ok<T>;
@@ -52,6 +105,43 @@ export async function fromPromise<T, E = unknown>(promise: Promise<T>, onError?:
52
105
  }
53
106
  }
54
107
 
108
+ /** A validation issue from a Standard Schema validator. */
109
+ export interface SchemaIssue {
110
+ readonly message: string;
111
+ readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>;
112
+ }
113
+
114
+ /** Minimal Standard Schema v1 interface (duck-typed, no external dependency). */
115
+ export interface StandardSchema<O = unknown> {
116
+ readonly '~standard': {
117
+ readonly validate: (value: unknown) => StandardSchemaResult<O> | Promise<StandardSchemaResult<O>>;
118
+ };
119
+ }
120
+
121
+ type StandardSchemaResult<O> = { readonly value: O; readonly issues?: undefined } | { readonly issues: ReadonlyArray<SchemaIssue> };
122
+
123
+ function schemaResultToResult<O>(sr: StandardSchemaResult<O>): Result<O, readonly SchemaIssue[]> {
124
+ return sr.issues ? ERR(sr.issues) : (sr.value as Ok<O>);
125
+ }
126
+
127
+ /**
128
+ * Validates a value against a Standard Schema and returns a Result.
129
+ * Works with any Standard Schema v1 compliant library (Zod, Valibot, ArkType, etc.).
130
+ * Returns synchronously if the schema validates synchronously.
131
+ * @param schema - A Standard Schema v1 compliant schema
132
+ * @param value - The value to validate
133
+ * @returns Ok(parsed) if valid, Err(issues) if invalid
134
+ * @example
135
+ * import { z } from 'zod';
136
+ * const result = fromSchema(z.string().email(), input);
137
+ * // Result<string, readonly SchemaIssue[]>
138
+ */
139
+ export function fromSchema<O>(schema: StandardSchema<O>, value: unknown): Result<O, readonly SchemaIssue[]> | Promise<Result<O, readonly SchemaIssue[]>> {
140
+ const sr = schema['~standard'].validate(value);
141
+ if (isThenable(sr)) return sr.then(schemaResultToResult);
142
+ return schemaResultToResult(sr);
143
+ }
144
+
55
145
  /**
56
146
  * Executes a function that may return sync or async, preserving sync execution when possible.
57
147
  * @param fn - Function that may return T or Promise<T>
@@ -497,7 +587,8 @@ export function partition<T, E>(results: Result<T, E>[]): [T[], E[]] {
497
587
  const oks: T[] = [];
498
588
  const errs: E[] = [];
499
589
 
500
- for (const result of results) {
590
+ for (let i = 0; i < results.length; i++) {
591
+ const result = results[i];
501
592
  if (isOk(result)) {
502
593
  oks.push(result);
503
594
  } else {
@@ -509,30 +600,32 @@ export function partition<T, E>(results: Result<T, E>[]): [T[], E[]] {
509
600
  }
510
601
 
511
602
  /**
512
- * Extracts all Ok values from an iterable of Results.
513
- * @param results - Iterable of Results
603
+ * Extracts all Ok values from an array of Results.
604
+ * @param results - Array of Results
514
605
  * @returns Array of Ok values
515
606
  * @example
516
607
  * filterOk([ok(1), err('a'), ok(2)]) // [1, 2]
517
608
  */
518
- export function filterOk<T, E>(results: Iterable<Result<T, E>>): T[] {
609
+ export function filterOk<T, E>(results: Result<T, E>[]): T[] {
519
610
  const oks: T[] = [];
520
- for (const result of results) {
611
+ for (let i = 0; i < results.length; i++) {
612
+ const result = results[i];
521
613
  if (isOk(result)) oks.push(result);
522
614
  }
523
615
  return oks;
524
616
  }
525
617
 
526
618
  /**
527
- * Extracts all Err values from an iterable of Results.
528
- * @param results - Iterable of Results
619
+ * Extracts all Err values from an array of Results.
620
+ * @param results - Array of Results
529
621
  * @returns Array of error values
530
622
  * @example
531
623
  * filterErr([ok(1), err('a'), ok(2)]) // ['a']
532
624
  */
533
- export function filterErr<T, E>(results: Iterable<Result<T, E>>): E[] {
625
+ export function filterErr<T, E>(results: Result<T, E>[]): E[] {
534
626
  const errs: E[] = [];
535
- for (const result of results) {
627
+ for (let i = 0; i < results.length; i++) {
628
+ const result = results[i];
536
629
  if (isErr(result)) errs.push(result.error);
537
630
  }
538
631
  return errs;
@@ -547,13 +640,12 @@ export function filterErr<T, E>(results: Iterable<Result<T, E>>): E[] {
547
640
  * collect([ok(1), err('e')]) // Err('e')
548
641
  */
549
642
  export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
550
- const len = results.length;
551
- const values = new Array<T>(len);
643
+ const values: T[] = [];
552
644
 
553
- for (let i = 0; i < len; i++) {
645
+ for (let i = 0; i < results.length; i++) {
554
646
  const result = results[i];
555
647
  if (isErr(result)) return result;
556
- values[i] = result;
648
+ values.push(result);
557
649
  }
558
650
 
559
651
  return values as Ok<T[]>;
@@ -570,7 +662,8 @@ export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
570
662
  export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
571
663
  const oks: T[] = [];
572
664
  const errs: E[] = [];
573
- for (const result of results) {
665
+ for (let i = 0; i < results.length; i++) {
666
+ const result = results[i];
574
667
  if (isOk(result)) {
575
668
  oks.push(result);
576
669
  } else {
@@ -651,11 +744,6 @@ export function isErrAnd<T, E>(result: Result<T, E>, predicate: (error: E) => bo
651
744
  return isErr(result) && predicate(result.error);
652
745
  }
653
746
 
654
- export function settledToResult<T, E>(result: PromiseSettledResult<Result<T, E>>): Result<T, E> {
655
- if (result.status === 'fulfilled') return result.value;
656
- return ERR(result.reason);
657
- }
658
-
659
747
  /**
660
748
  * Partitions an async iterable of Results.
661
749
  * @param results - Iterable of Promise Results
@@ -666,7 +754,26 @@ export function settledToResult<T, E>(result: PromiseSettledResult<Result<T, E>>
666
754
  */
667
755
  export async function partitionAsync<T, E>(promises: Iterable<Promise<Result<T, E>>>): Promise<[Widen<T>[], WidenNever<E>[]]> {
668
756
  const settled = await Promise.allSettled(promises);
669
- return partition(settled.map(settledToResult)) as [Widen<T>[], WidenNever<E>[]];
757
+ const oks: Widen<T>[] = [];
758
+ const errs: WidenNever<E>[] = [];
759
+
760
+ for (let i = 0; i < settled.length; i++) {
761
+ const entry = settled[i];
762
+
763
+ if (entry.status === 'rejected') {
764
+ errs.push(entry.reason as WidenNever<E>);
765
+ continue;
766
+ }
767
+
768
+ const result = entry.value;
769
+ if (isOk(result)) {
770
+ oks.push(result as Widen<T>);
771
+ } else {
772
+ errs.push(result.error as WidenNever<E>);
773
+ }
774
+ }
775
+
776
+ return [oks, errs];
670
777
  }
671
778
 
672
779
  /**
@@ -681,7 +788,7 @@ export async function partitionAsync<T, E>(promises: Iterable<Promise<Result<T,
681
788
  */
682
789
  export function settleMaybePromise<T, E = unknown>(values: MaybePromise<T>[]): Result<T, E>[] | Promise<Result<T, E>[]> {
683
790
  const len = values.length;
684
- const results = new Array<Result<T, E>>(len);
791
+ const results = new Array<Result<T, E>>(len).fill(0 as never);
685
792
  let pendingIndices: number[] | undefined;
686
793
  let pendingPromises: Promise<T>[] | undefined;
687
794
 
@@ -712,36 +819,21 @@ export async function partitionMaybePromiseAsync<T, E>(
712
819
  errs: WidenNever<E>[],
713
820
  startIndex: number = 0,
714
821
  ): Promise<[Widen<T>[], WidenNever<E>[]]> {
715
- const suffixLength = values.length - startIndex;
716
- const pendingPromises: Promise<Result<T, E>>[] = [];
822
+ const remaining = values.length - startIndex;
823
+ const promises = new Array<Promise<Result<T, E>>>(remaining).fill(0 as never);
717
824
 
718
- for (let i = 0; i < suffixLength; i++) {
825
+ for (let i = 0; i < remaining; i++) {
719
826
  const value = values[startIndex + i];
720
- if (isThenable(value)) {
721
- pendingPromises.push(
722
- Promise.resolve(value).then(
723
- (result) => result as Result<T, E>,
724
- (error) => ERR(error as E),
725
- ),
726
- );
727
- } else {
728
- if (isOk(value)) {
729
- oks.push(value as Widen<T>);
730
- } else {
731
- errs.push((value as Err<WidenNever<E>>).error);
732
- }
733
- }
827
+ promises[i] = isThenable(value) ? Promise.resolve(value).then(identity, wrapErr) : Promise.resolve(value);
734
828
  }
735
829
 
736
- if (pendingPromises.length > 0) {
737
- const resolved = await Promise.all(pendingPromises);
738
- for (let i = 0; i < resolved.length; i++) {
739
- const result = resolved[i];
740
- if (isOk(result)) {
741
- oks.push(result as Widen<T>);
742
- } else {
743
- errs.push((result as Err<WidenNever<E>>).error);
744
- }
830
+ const resolved = await Promise.all(promises);
831
+ for (let i = 0; i < resolved.length; i++) {
832
+ const result = resolved[i];
833
+ if (isOk(result)) {
834
+ oks.push(result as Widen<T>);
835
+ } else {
836
+ errs.push((result as Err<WidenNever<E>>).error);
745
837
  }
746
838
  }
747
839
 
@@ -815,3 +907,51 @@ export async function safeTryAsync<T>(fn: () => Promise<T>): Promise<Result<T, u
815
907
  return ERR(e);
816
908
  }
817
909
  }
910
+
911
+ function* unwrapYield<T, E>(result: Result<T, E>): Generator<Err<E>, T> {
912
+ if (isErr(result)) {
913
+ yield result;
914
+ return undefined as never;
915
+ }
916
+ return result;
917
+ }
918
+
919
+ type Unwrapper = <T, E>(result: Result<T, E>) => Generator<Err<E>, T>;
920
+
921
+ /**
922
+ * Generator-based do-notation for Result. Provides a `$` function that unwraps
923
+ * Results inside a generator, short-circuiting on the first Err.
924
+ * Preserves error types (unlike safeTry which erases to unknown).
925
+ * @param fn - Generator function receiving `$` unwrapper
926
+ * @returns Ok(return value) or the first Err encountered
927
+ * @example
928
+ * const result = gen(function*($) {
929
+ * const a = yield* $(parseNumber('10'));
930
+ * const b = yield* $(parseNumber('5'));
931
+ * return a + b;
932
+ * }); // Result<number, ParseError>
933
+ */
934
+ export function gen<E, T>(fn: ($: Unwrapper) => Generator<Err<E>, T>): Result<T, E> {
935
+ const iter = fn(unwrapYield);
936
+ const step = iter.next();
937
+ if (!step.done) return step.value;
938
+ return step.value as Ok<T>;
939
+ }
940
+
941
+ /**
942
+ * Async generator-based do-notation for Result. Like gen, but for async operations.
943
+ * @param fn - Async generator function receiving `$` unwrapper
944
+ * @returns Promise of Ok(return value) or the first Err encountered
945
+ * @example
946
+ * const result = await genAsync(async function*($) {
947
+ * const user = yield* $(await fetchUser(id));
948
+ * const posts = yield* $(await fetchPosts(user.id));
949
+ * return { user, posts };
950
+ * }); // Promise<Result<{user, posts}, FetchError>>
951
+ */
952
+ export async function genAsync<E, T>(fn: ($: Unwrapper) => AsyncGenerator<Err<E>, T>): Promise<Result<T, E>> {
953
+ const iter = fn(unwrapYield);
954
+ const step = await iter.next();
955
+ if (!step.done) return step.value;
956
+ return step.value as Ok<T>;
957
+ }
package/src/safe.ts CHANGED
@@ -15,7 +15,55 @@ export type {
15
15
  InferErr,
16
16
  MaybePromise,
17
17
  } from './types.js';
18
- export { safeTry, safeTryAsync, unwrap } from './result.js';
18
+ export { safeTry, safeTryAsync, unwrap, gen, genAsync } from './result.js';
19
19
  export * as Option from './option.js';
20
20
  export * as Result from './result.js';
21
21
  export * as Iter from './iter.js';
22
+
23
+ /**
24
+ * Threads a value through a sequence of unary functions, left to right.
25
+ * @param value - The initial value
26
+ * @param fns - Functions to apply in order
27
+ * @returns The result of applying all functions
28
+ * @example
29
+ * pipe(
30
+ * Result.tryCatch(() => JSON.parse(input)),
31
+ * r => Result.flatMap(r, validate),
32
+ * r => Result.map(r, transform),
33
+ * )
34
+ */
35
+ export function pipe<A>(a: A): A;
36
+ export function pipe<A, B>(a: A, ab: (a: A) => B): B;
37
+ export function pipe<A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C;
38
+ export function pipe<A, B, C, D>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D;
39
+ export function pipe<A, B, C, D, E>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E): E;
40
+ export function pipe<A, B, C, D, E, F>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F): F;
41
+ export function pipe<A, B, C, D, E, F, G>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D, de: (d: D) => E, ef: (e: E) => F, fg: (f: F) => G): G;
42
+ export function pipe<A, B, C, D, E, F, G, H>(
43
+ a: A,
44
+ ab: (a: A) => B,
45
+ bc: (b: B) => C,
46
+ cd: (c: C) => D,
47
+ de: (d: D) => E,
48
+ ef: (e: E) => F,
49
+ fg: (f: F) => G,
50
+ gh: (g: G) => H,
51
+ ): H;
52
+ export function pipe<A, B, C, D, E, F, G, H, I>(
53
+ a: A,
54
+ ab: (a: A) => B,
55
+ bc: (b: B) => C,
56
+ cd: (c: C) => D,
57
+ de: (d: D) => E,
58
+ ef: (e: E) => F,
59
+ fg: (f: F) => G,
60
+ gh: (g: G) => H,
61
+ hi: (h: H) => I,
62
+ ): I;
63
+ export function pipe(a: unknown, ...fns: ((v: unknown) => unknown)[]): unknown {
64
+ let result = a;
65
+ for (let i = 0; i < fns.length; i++) {
66
+ result = fns[i](result);
67
+ }
68
+ return result;
69
+ }