nalloc 0.0.3 → 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>
@@ -118,7 +208,7 @@ export function assertOk<T, E>(result: Result<T, E>, message?: string): asserts
118
208
  */
119
209
  export function assertErr<T, E>(result: Result<T, E>, message?: string): asserts result is Err<E> {
120
210
  if (isOk(result)) {
121
- throw new Error(message ?? 'Expected Err result.');
211
+ throw new Error(message ?? `Expected Err result. Received value: ${String(result)}`);
122
212
  }
123
213
  }
124
214
 
@@ -188,11 +278,7 @@ export function flatMap<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<
188
278
  * @param fn - Function returning a Result
189
279
  * @returns The result of fn(value) if Ok, Err unchanged
190
280
  */
191
- export function andThen<T, U, E>(result: Err<E>, fn: (value: T) => Result<U, E>): Err<E>;
192
- export function andThen<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E>;
193
- export function andThen<T, U, E>(result: Result<T, E>, fn: (value: T) => Result<U, E>): Result<U, E> {
194
- return isErr(result) ? result : fn(result);
195
- }
281
+ export const andThen: typeof flatMap = flatMap;
196
282
 
197
283
  /**
198
284
  * Executes a side effect if Ok, then returns the original Result.
@@ -501,7 +587,8 @@ export function partition<T, E>(results: Result<T, E>[]): [T[], E[]] {
501
587
  const oks: T[] = [];
502
588
  const errs: E[] = [];
503
589
 
504
- for (const result of results) {
590
+ for (let i = 0; i < results.length; i++) {
591
+ const result = results[i];
505
592
  if (isOk(result)) {
506
593
  oks.push(result);
507
594
  } else {
@@ -513,30 +600,32 @@ export function partition<T, E>(results: Result<T, E>[]): [T[], E[]] {
513
600
  }
514
601
 
515
602
  /**
516
- * Extracts all Ok values from an iterable of Results.
517
- * @param results - Iterable of Results
603
+ * Extracts all Ok values from an array of Results.
604
+ * @param results - Array of Results
518
605
  * @returns Array of Ok values
519
606
  * @example
520
607
  * filterOk([ok(1), err('a'), ok(2)]) // [1, 2]
521
608
  */
522
- export function filterOk<T, E>(results: Iterable<Result<T, E>>): T[] {
609
+ export function filterOk<T, E>(results: Result<T, E>[]): T[] {
523
610
  const oks: T[] = [];
524
- for (const result of results) {
611
+ for (let i = 0; i < results.length; i++) {
612
+ const result = results[i];
525
613
  if (isOk(result)) oks.push(result);
526
614
  }
527
615
  return oks;
528
616
  }
529
617
 
530
618
  /**
531
- * Extracts all Err values from an iterable of Results.
532
- * @param results - Iterable of Results
619
+ * Extracts all Err values from an array of Results.
620
+ * @param results - Array of Results
533
621
  * @returns Array of error values
534
622
  * @example
535
623
  * filterErr([ok(1), err('a'), ok(2)]) // ['a']
536
624
  */
537
- export function filterErr<T, E>(results: Iterable<Result<T, E>>): E[] {
625
+ export function filterErr<T, E>(results: Result<T, E>[]): E[] {
538
626
  const errs: E[] = [];
539
- for (const result of results) {
627
+ for (let i = 0; i < results.length; i++) {
628
+ const result = results[i];
540
629
  if (isErr(result)) errs.push(result.error);
541
630
  }
542
631
  return errs;
@@ -553,7 +642,8 @@ export function filterErr<T, E>(results: Iterable<Result<T, E>>): E[] {
553
642
  export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
554
643
  const values: T[] = [];
555
644
 
556
- for (const result of results) {
645
+ for (let i = 0; i < results.length; i++) {
646
+ const result = results[i];
557
647
  if (isErr(result)) return result;
558
648
  values.push(result);
559
649
  }
@@ -572,7 +662,8 @@ export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
572
662
  export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
573
663
  const oks: T[] = [];
574
664
  const errs: E[] = [];
575
- for (const result of results) {
665
+ for (let i = 0; i < results.length; i++) {
666
+ const result = results[i];
576
667
  if (isOk(result)) {
577
668
  oks.push(result);
578
669
  } else {
@@ -653,11 +744,6 @@ export function isErrAnd<T, E>(result: Result<T, E>, predicate: (error: E) => bo
653
744
  return isErr(result) && predicate(result.error);
654
745
  }
655
746
 
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);
659
- }
660
-
661
747
  /**
662
748
  * Partitions an async iterable of Results.
663
749
  * @param results - Iterable of Promise Results
@@ -668,7 +754,26 @@ export function settledToResult<T, E>(result: PromiseSettledResult<Result<T, E>>
668
754
  */
669
755
  export async function partitionAsync<T, E>(promises: Iterable<Promise<Result<T, E>>>): Promise<[Widen<T>[], WidenNever<E>[]]> {
670
756
  const settled = await Promise.allSettled(promises);
671
- 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];
672
777
  }
673
778
 
674
779
  /**
@@ -683,7 +788,7 @@ export async function partitionAsync<T, E>(promises: Iterable<Promise<Result<T,
683
788
  */
684
789
  export function settleMaybePromise<T, E = unknown>(values: MaybePromise<T>[]): Result<T, E>[] | Promise<Result<T, E>[]> {
685
790
  const len = values.length;
686
- const results = new Array<Result<T, E>>(len);
791
+ const results = new Array<Result<T, E>>(len).fill(0 as never);
687
792
  let pendingIndices: number[] | undefined;
688
793
  let pendingPromises: Promise<T>[] | undefined;
689
794
 
@@ -714,18 +819,15 @@ export async function partitionMaybePromiseAsync<T, E>(
714
819
  errs: WidenNever<E>[],
715
820
  startIndex: number = 0,
716
821
  ): Promise<[Widen<T>[], WidenNever<E>[]]> {
717
- const suffixLength = values.length - startIndex;
718
- const pending = new Array<Promise<Result<T, E>>>(suffixLength);
822
+ const remaining = values.length - startIndex;
823
+ const promises = new Array<Promise<Result<T, E>>>(remaining).fill(0 as never);
719
824
 
720
- for (let i = 0; i < suffixLength; i++) {
825
+ for (let i = 0; i < remaining; i++) {
721
826
  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
- );
827
+ promises[i] = isThenable(value) ? Promise.resolve(value).then(identity, wrapErr) : Promise.resolve(value);
726
828
  }
727
829
 
728
- const resolved = await Promise.all(pending);
830
+ const resolved = await Promise.all(promises);
729
831
  for (let i = 0; i < resolved.length; i++) {
730
832
  const result = resolved[i];
731
833
  if (isOk(result)) {
@@ -734,6 +836,7 @@ export async function partitionMaybePromiseAsync<T, E>(
734
836
  errs.push((result as Err<WidenNever<E>>).error);
735
837
  }
736
838
  }
839
+
737
840
  return [oks, errs] as [Widen<T>[], WidenNever<E>[]];
738
841
  }
739
842
 
@@ -804,3 +907,51 @@ export async function safeTryAsync<T>(fn: () => Promise<T>): Promise<Result<T, u
804
907
  return ERR(e);
805
908
  }
806
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
+ }
package/src/types.ts CHANGED
@@ -28,6 +28,8 @@ export type Some<T> = ValueType<T> & { readonly [SOME_BRAND]: true };
28
28
  /** Represents the absence of a value (null or undefined). */
29
29
  export type None = NoneValueType & { readonly [SOME_BRAND]: false };
30
30
 
31
+ export type UnOption<V, T extends Option<V>> = T extends Some<V> ? V : None;
32
+
31
33
  /** Constant representing None. Use this instead of null/undefined for clarity. */
32
34
  export const NONE = undefined as None;
33
35
 
@@ -226,7 +228,7 @@ export type MaybePromise<T> = T | Promise<T> | PromiseLike<T>;
226
228
  * @returns true if value is a PromiseLike
227
229
  */
228
230
  export function isThenable<T>(value: MaybePromise<T>): value is PromiseLike<T> {
229
- return typeof (value as PromiseLike<T>)?.then === 'function';
231
+ return (typeof value === 'object' || typeof value === 'function') && value !== null && typeof (value as PromiseLike<T>).then === 'function';
230
232
  }
231
233
 
232
234
  /**
@@ -235,5 +237,5 @@ export function isThenable<T>(value: MaybePromise<T>): value is PromiseLike<T> {
235
237
  * @returns true if value is not a PromiseLike
236
238
  */
237
239
  export function isSync<T>(value: MaybePromise<T>): value is T {
238
- return typeof (value as PromiseLike<T>)?.then !== 'function';
240
+ return !(typeof value === 'object' || typeof value === 'function') || value === null || typeof (value as PromiseLike<T>).then !== 'function';
239
241
  }