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/build/iter.cjs +6 -8
- package/build/iter.cjs.map +1 -1
- package/build/iter.js +6 -8
- package/build/iter.js.map +1 -1
- package/build/option.cjs +14 -4
- package/build/option.cjs.map +1 -1
- package/build/option.d.ts +11 -0
- package/build/option.js +11 -4
- package/build/option.js.map +1 -1
- package/build/result.cjs +107 -39
- package/build/result.cjs.map +1 -1
- package/build/result.d.ts +95 -12
- package/build/result.js +92 -36
- package/build/result.js.map +1 -1
- package/build/safe.cjs +16 -0
- package/build/safe.cjs.map +1 -1
- package/build/safe.d.ts +22 -1
- package/build/safe.js +8 -1
- package/build/safe.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/index.ts +34 -0
- package/src/__tests__/option.ts +23 -1
- package/src/__tests__/option.types.ts +2 -1
- package/src/__tests__/result.ts +194 -1
- package/src/__tests__/result.types.ts +17 -4
- package/src/iter.ts +4 -2
- package/src/option.ts +21 -1
- package/src/result.ts +192 -52
- package/src/safe.ts +49 -1
|
@@ -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
|
|
35
|
+
const _: number = optValue;
|
|
36
|
+
}
|
|
36
37
|
if (isNone(optValue)) {
|
|
37
38
|
assert<Equals<typeof optValue, None>>;
|
|
38
39
|
}
|
package/src/__tests__/result.ts
CHANGED
|
@@ -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
|
|
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
|
|
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 [
|
|
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
|
}
|