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.
@@ -32,7 +32,8 @@ assert<Equals<typeof noneValue, None>>;
32
32
  const optValue: Option<number> = Math.random() > 0 ? of(5) : none;
33
33
  if (isSome(optValue)) {
34
34
  assert<Equals<typeof optValue, Some<number>>>;
35
- const directAccess: number = optValue;}
35
+ const _: number = optValue;
36
+ }
36
37
  if (isNone(optValue)) {
37
38
  assert<Equals<typeof optValue, None>>;
38
39
  }
@@ -33,6 +33,8 @@ import {
33
33
  isOkAnd,
34
34
  isErrAnd,
35
35
  tryCatch,
36
+ wrap,
37
+ toThrowable,
36
38
  unwrapOrReturn,
37
39
  assertOk,
38
40
  assertErr,
@@ -46,8 +48,12 @@ import {
46
48
  filterErr,
47
49
  safeTry,
48
50
  safeTryAsync,
49
- fromPromise
51
+ fromPromise,
52
+ fromSchema,
53
+ gen,
54
+ genAsync
50
55
  } from '../result.js';
56
+ import type { StandardSchema } from '../result.js';
51
57
  import { ok, err, isOk, isErr, optionOf as optOf, none } from '../types.js';
52
58
 
53
59
  describe('Result', () => {
@@ -600,6 +606,63 @@ describe('Result', () => {
600
606
 
601
607
  });
602
608
 
609
+ describe('wrap', () => {
610
+ it('wraps a function that succeeds', () => {
611
+ const safeParse = wrap(JSON.parse);
612
+ const result = safeParse('{"a":1}');
613
+ expect(isOk(result)).toBe(true);
614
+ expect(unwrap(result)).toEqual({ a: 1 });
615
+ });
616
+
617
+ it('wraps a function that throws', () => {
618
+ const safeParse = wrap(JSON.parse);
619
+ const result = safeParse('invalid');
620
+ expect(isErr(result)).toBe(true);
621
+ expect((result as any).error).toBeInstanceOf(SyntaxError);
622
+ });
623
+
624
+ it('preserves multi-arg signatures', () => {
625
+ const safeSlice = wrap((s: string, start: number, end: number) => s.slice(start, end));
626
+ const result = safeSlice('hello', 1, 3);
627
+ expect(unwrap(result)).toBe('el');
628
+ });
629
+
630
+ it('uses onError mapper', () => {
631
+ const safeParse = wrap(JSON.parse, (e) => (e as Error).message);
632
+ const result = safeParse('invalid');
633
+ expect(isErr(result)).toBe(true);
634
+ expect((result as any).error).toContain('JSON');
635
+ });
636
+ });
637
+
638
+ describe('toThrowable', () => {
639
+ it('returns Ok value for successful Result', () => {
640
+ const fn = (x: number) => ok(x * 2);
641
+ const throwing = toThrowable(fn);
642
+ expect(throwing(5)).toBe(10);
643
+ });
644
+
645
+ it('throws Err error for failed Result', () => {
646
+ const fn = (x: number) => x > 0 ? ok(x) : err('negative');
647
+ const throwing = toThrowable(fn);
648
+ expect(() => throwing(-1)).toThrow('negative');
649
+ });
650
+
651
+ it('throws Error instances directly', () => {
652
+ const fn = () => err(new TypeError('bad type'));
653
+ const throwing = toThrowable(fn);
654
+ expect(() => throwing()).toThrow(TypeError);
655
+ });
656
+
657
+ it('round-trips with wrap', () => {
658
+ const original = JSON.parse;
659
+ const safe = wrap(original);
660
+ const restored = toThrowable(safe);
661
+ expect(restored('{"a":1}')).toEqual({ a: 1 });
662
+ expect(() => restored('invalid')).toThrow(SyntaxError);
663
+ });
664
+ });
665
+
603
666
  describe('control helpers', () => {
604
667
  it('unwrapOrReturn returns value for Ok', () => {
605
668
  const value = unwrapOrReturn(ok(42), () => 'fallback');
@@ -788,6 +851,16 @@ describe('Result', () => {
788
851
  expect(await result).toEqual([[1, 2], ['a']]);
789
852
  });
790
853
 
854
+ it('preserves input order with interleaved sync/async', async () => {
855
+ const result = await partitionMaybePromise([
856
+ Promise.resolve(ok(1)),
857
+ ok(2),
858
+ Promise.resolve(err('a')),
859
+ err('b'),
860
+ ]);
861
+ expect(result).toEqual([[1, 2], ['a', 'b']]);
862
+ });
863
+
791
864
  it('handles all async values', async () => {
792
865
  const result = await partitionMaybePromise([
793
866
  Promise.resolve(ok(1)),
@@ -949,4 +1022,124 @@ describe('Result', () => {
949
1022
  });
950
1023
  });
951
1024
 
1025
+ describe('fromSchema', () => {
1026
+ const validSchema: StandardSchema<string> = {
1027
+ '~standard': {
1028
+ validate: (value) => typeof value === 'string'
1029
+ ? { value }
1030
+ : { issues: [{ message: 'Expected string' }] },
1031
+ },
1032
+ };
1033
+
1034
+ const asyncSchema: StandardSchema<number> = {
1035
+ '~standard': {
1036
+ validate: (value) => Promise.resolve(
1037
+ typeof value === 'number'
1038
+ ? { value }
1039
+ : { issues: [{ message: 'Expected number' }] },
1040
+ ),
1041
+ },
1042
+ };
1043
+
1044
+ it('returns Ok for valid sync schema', () => {
1045
+ const result = fromSchema(validSchema, 'hello');
1046
+ expect(isOk(result)).toBe(true);
1047
+ expect(result).toBe('hello');
1048
+ });
1049
+
1050
+ it('returns Err with issues for invalid sync schema', () => {
1051
+ const result = fromSchema(validSchema, 42);
1052
+ expect(isErr(result)).toBe(true);
1053
+ expect((result as any).error).toEqual([{ message: 'Expected string' }]);
1054
+ });
1055
+
1056
+ it('returns Ok for valid async schema', async () => {
1057
+ const result = await fromSchema(asyncSchema, 42);
1058
+ expect(isOk(result)).toBe(true);
1059
+ expect(result).toBe(42);
1060
+ });
1061
+
1062
+ it('returns Err for invalid async schema', async () => {
1063
+ const result = await fromSchema(asyncSchema, 'hello');
1064
+ expect(isErr(result)).toBe(true);
1065
+ expect((result as any).error).toEqual([{ message: 'Expected number' }]);
1066
+ });
1067
+ });
1068
+
1069
+ describe('gen', () => {
1070
+ it('returns Ok for successful execution', () => {
1071
+ const result = gen(function*($) {
1072
+ const a = yield* $(ok(10));
1073
+ const b = yield* $(ok(5));
1074
+ return a + b;
1075
+ });
1076
+ expect(isOk(result)).toBe(true);
1077
+ expect(result).toBe(15);
1078
+ });
1079
+
1080
+ it('short-circuits on first Err', () => {
1081
+ const result = gen(function*($) {
1082
+ const a = yield* $(ok(10));
1083
+ const b = yield* $(err('fail') as Result<number, string>);
1084
+ const c = yield* $(ok(5));
1085
+ return a + b + c;
1086
+ });
1087
+ expect(isErr(result)).toBe(true);
1088
+ expect((result as any).error).toBe('fail');
1089
+ });
1090
+
1091
+ it('propagates Err through chain', () => {
1092
+ const parse = (s: string) => s === 'bad' ? err('parse error') : ok(Number(s));
1093
+ const result = gen(function*($) {
1094
+ const a = yield* $(parse('10'));
1095
+ const b = yield* $(parse('bad'));
1096
+ const c = yield* $(parse('5'));
1097
+ return a + b + c;
1098
+ });
1099
+ expect(isErr(result)).toBe(true);
1100
+ expect((result as any).error).toBe('parse error');
1101
+ });
1102
+
1103
+ it('returns Ok for empty generator', () => {
1104
+ const result = gen(function*() {
1105
+ return 42;
1106
+ });
1107
+ expect(isOk(result)).toBe(true);
1108
+ expect(result).toBe(42);
1109
+ });
1110
+ });
1111
+
1112
+ describe('genAsync', () => {
1113
+ it('returns Ok for successful async execution', async () => {
1114
+ const result = await genAsync(async function*($) {
1115
+ const a = yield* $(ok(10));
1116
+ const b = yield* $(await fromPromise(Promise.resolve(5)));
1117
+ return a + b;
1118
+ });
1119
+ expect(isOk(result)).toBe(true);
1120
+ expect(result).toBe(15);
1121
+ });
1122
+
1123
+ it('short-circuits on first Err', async () => {
1124
+ const result = await genAsync(async function*($) {
1125
+ const a = yield* $(ok(10));
1126
+ const b = yield* $(err('async fail') as Result<number, string>);
1127
+ return a + b;
1128
+ });
1129
+ expect(isErr(result)).toBe(true);
1130
+ expect((result as any).error).toBe('async fail');
1131
+ });
1132
+
1133
+ it('handles rejected promises via fromPromise', async () => {
1134
+ const result = await genAsync(async function*($) {
1135
+ const a = yield* $(await fromPromise(Promise.resolve(10)));
1136
+ const b = yield* $(await fromPromise(Promise.reject('boom')));
1137
+ return a + b;
1138
+ });
1139
+ expect(isErr(result)).toBe(true);
1140
+ expect((result as any).error).toBe('boom');
1141
+ });
1142
+ });
1143
+
952
1144
  });
1145
+
@@ -6,12 +6,8 @@ import {
6
6
  mapErr,
7
7
  flatMap,
8
8
  bimap,
9
- unwrap,
10
- unwrapErr,
11
9
  unwrapOr,
12
10
  unwrapOrElse,
13
- expect,
14
- expectErr,
15
11
  and,
16
12
  or,
17
13
  orElse,
@@ -165,6 +161,12 @@ const tryResultTyped = of(() => {
165
161
  });
166
162
  assert<Equals<typeof tryResultTyped, Result<number, unknown>>>;
167
163
 
164
+ const tryResultResult= of(() => {
165
+ if (Math.random() > 0.5) err("error");
166
+ return 42;
167
+ });
168
+ assert<Equals<typeof tryResultResult, Result<number, unknown>>>;
169
+
168
170
 
169
171
  const unwrapOrResult = unwrapOr(err<string>("error") as Result<number, string>, 42);
170
172
  assert<Equals<typeof unwrapOrResult, number>>;
@@ -205,6 +207,17 @@ const tryCatchTyped = tryCatch<number, string>(() => {
205
207
  }, error => (error as Error).message);
206
208
  assert<Equals<typeof tryCatchTyped, Result<number, string>>>;
207
209
 
210
+ const tryCatchPassthroughErr = tryCatch(() => err('payload'));
211
+ assert<Equals<typeof tryCatchPassthroughErr, Err<unknown>>>;
212
+
213
+ const tryCatchPassthroughResult = tryCatch(() => Math.random() > 0.5 ? ok(1) : err('fail'));
214
+ assert<Equals<typeof tryCatchPassthroughResult, Result<1, unknown>>>;
215
+
216
+ const tryCatchPassthroughErrTyped = tryCatch(
217
+ () => err('payload'),
218
+ () => 0,
219
+ );
220
+ assert<Equals<typeof tryCatchPassthroughErrTyped, Err<string | number>>>;
208
221
 
209
222
  const unwrapFallback = unwrapOrReturn(ok(1) as Result<number, string>, () => 'fallback');
210
223
  assert<Equals<typeof unwrapFallback, number | 'fallback'>>;
package/src/iter.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { isSome, isErr, err as ERR } from './types.js';
2
2
  import type { Option, Result, Ok } from './types.js';
3
3
 
4
+ const ITER_DONE: IteratorResult<never> = Object.freeze({ value: undefined as never, done: true as const });
5
+
4
6
  /**
5
7
  * Yields mapped values while the mapping function returns Some, stops at the first None.
6
8
  * @param source - The iterable to map over
@@ -42,7 +44,7 @@ export function safeIter<T>(source: Iterable<T>): IterableIterator<Result<T, unk
42
44
  return this;
43
45
  },
44
46
  next(): IteratorResult<Result<T, unknown>> {
45
- if (done) return { value: undefined, done: true };
47
+ if (done) return ITER_DONE;
46
48
  try {
47
49
  const next = iter.next();
48
50
  if (next.done) {
@@ -64,7 +66,7 @@ export function safeIter<T>(source: Iterable<T>): IterableIterator<Result<T, unk
64
66
  // suppressed
65
67
  }
66
68
  }
67
- return { value: undefined, done: true };
69
+ return ITER_DONE;
68
70
  },
69
71
  };
70
72
  }
package/src/option.ts CHANGED
@@ -4,6 +4,8 @@ import type { Some, None, Option, NoneValueType, ValueType, Result, Ok, Widen }
4
4
  export type { Some, None, Option };
5
5
  export { isSome, isNone, of };
6
6
 
7
+ const NONE_PAIR: readonly [None, None] = Object.freeze([NONE, NONE]);
8
+
7
9
  /**
8
10
  * Creates an Option from a nullable value with widened types.
9
11
  * @param value - The value to wrap
@@ -178,6 +180,24 @@ export function tap<T>(opt: Option<T>, fn: (value: T) => void): Option<T> {
178
180
  return opt;
179
181
  }
180
182
 
183
+ /**
184
+ * Executes a side effect if None, then returns the original Option.
185
+ * @param opt - The Option to tap
186
+ * @param fn - Side effect function
187
+ * @returns The original Option unchanged
188
+ * @example
189
+ * tapNone(none, () => console.log('missing')) // logs 'missing', returns None
190
+ */
191
+ export function tapNone<T>(opt: Some<T>, fn: () => void): Some<T>;
192
+ export function tapNone(opt: None, fn: () => void): None;
193
+ export function tapNone<T>(opt: Option<T>, fn: () => void): Option<T>;
194
+ export function tapNone<T>(opt: Option<T>, fn: () => void): Option<T> {
195
+ if (isNone(opt)) {
196
+ fn();
197
+ }
198
+ return opt;
199
+ }
200
+
181
201
  /**
182
202
  * Returns true if None, or if Some and predicate returns true.
183
203
  * @param opt - The Option to check
@@ -351,7 +371,7 @@ export function zip<T, U>(opt: Option<T>, other: Option<U>): Option<[T, U]> {
351
371
  * unzip(none) // [None, None]
352
372
  */
353
373
  export function unzip<T, U>(opt: Option<[T, U]>): [Option<T>, Option<U>] {
354
- if (isNone(opt)) return [NONE, NONE];
374
+ if (isNone(opt)) return NONE_PAIR as [Option<T>, Option<U>];
355
375
  const [a, b] = opt;
356
376
  return [of(a), of(b)];
357
377
  }