nalloc 0.1.0 → 0.2.1

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/safe.d.ts CHANGED
@@ -1,6 +1,27 @@
1
1
  export { some, none, ok, err, isOk, isErr, isSome, isNone, isThenable, isSync } from './types.js';
2
2
  export type { Some, None, Ok, Err, Option as OptionType, OptionValue, IsOption, InferSome, Result as ResultType, ResultValue, ResultErrorType, IsResult, InferErr, MaybePromise, } from './types.js';
3
- export { safeTry, safeTryAsync, unwrap } from './result.js';
3
+ export { safeTry, safeTryAsync, unwrap, gen, genAsync } from './result.js';
4
4
  export * as Option from './option.js';
5
5
  export * as Result from './result.js';
6
6
  export * as Iter from './iter.js';
7
+ /**
8
+ * Threads a value through a sequence of unary functions, left to right.
9
+ * @param value - The initial value
10
+ * @param fns - Functions to apply in order
11
+ * @returns The result of applying all functions
12
+ * @example
13
+ * pipe(
14
+ * Result.tryCatch(() => JSON.parse(input)),
15
+ * r => Result.flatMap(r, validate),
16
+ * r => Result.map(r, transform),
17
+ * )
18
+ */
19
+ export declare function pipe<A>(a: A): A;
20
+ export declare function pipe<A, B>(a: A, ab: (a: A) => B): B;
21
+ export declare function pipe<A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C;
22
+ export declare function pipe<A, B, C, D>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D;
23
+ export declare 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;
24
+ export declare 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;
25
+ export declare 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;
26
+ export declare function pipe<A, B, C, D, E, F, G, H>(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, gh: (g: G) => H): H;
27
+ export declare function pipe<A, B, C, D, E, F, G, H, I>(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, gh: (g: G) => H, hi: (h: H) => I): I;
package/build/safe.js CHANGED
@@ -1,7 +1,14 @@
1
1
  export { some, none, ok, err, isOk, isErr, isSome, isNone, isThenable, isSync } from "./types.js";
2
- export { safeTry, safeTryAsync, unwrap } from "./result.js";
2
+ export { safeTry, safeTryAsync, unwrap, gen, genAsync } from "./result.js";
3
3
  export * as Option from "./option.js";
4
4
  export * as Result from "./result.js";
5
5
  export * as Iter from "./iter.js";
6
+ export function pipe(a, ...fns) {
7
+ let result = a;
8
+ for(let i = 0; i < fns.length; i++){
9
+ result = fns[i](result);
10
+ }
11
+ return result;
12
+ }
6
13
 
7
14
  //# sourceMappingURL=safe.js.map
package/build/safe.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/safe.ts"],"sourcesContent":["export { some, none, ok, err, isOk, isErr, isSome, isNone, isThenable, isSync } from './types.js';\nexport type {\n Some,\n None,\n Ok,\n Err,\n Option as OptionType,\n OptionValue,\n IsOption,\n InferSome,\n Result as ResultType,\n ResultValue,\n ResultErrorType,\n IsResult,\n InferErr,\n MaybePromise,\n} from './types.js';\nexport { safeTry, safeTryAsync, unwrap } from './result.js';\nexport * as Option from './option.js';\nexport * as Result from './result.js';\nexport * as Iter from './iter.js';\n"],"names":["some","none","ok","err","isOk","isErr","isSome","isNone","isThenable","isSync","safeTry","safeTryAsync","unwrap","Option","Result","Iter"],"mappings":"AAAA,SAASA,IAAI,EAAEC,IAAI,EAAEC,EAAE,EAAEC,GAAG,EAAEC,IAAI,EAAEC,KAAK,EAAEC,MAAM,EAAEC,MAAM,EAAEC,UAAU,EAAEC,MAAM,QAAQ,aAAa;AAiBlG,SAASC,OAAO,EAAEC,YAAY,EAAEC,MAAM,QAAQ,cAAc;AAC5D,OAAO,KAAKC,MAAM,MAAM,cAAc;AACtC,OAAO,KAAKC,MAAM,MAAM,cAAc;AACtC,OAAO,KAAKC,IAAI,MAAM,YAAY"}
1
+ {"version":3,"sources":["../src/safe.ts"],"sourcesContent":["export { some, none, ok, err, isOk, isErr, isSome, isNone, isThenable, isSync } from './types.js';\nexport type {\n Some,\n None,\n Ok,\n Err,\n Option as OptionType,\n OptionValue,\n IsOption,\n InferSome,\n Result as ResultType,\n ResultValue,\n ResultErrorType,\n IsResult,\n InferErr,\n MaybePromise,\n} from './types.js';\nexport { safeTry, safeTryAsync, unwrap, gen, genAsync } from './result.js';\nexport * as Option from './option.js';\nexport * as Result from './result.js';\nexport * as Iter from './iter.js';\n\n/**\n * Threads a value through a sequence of unary functions, left to right.\n * @param value - The initial value\n * @param fns - Functions to apply in order\n * @returns The result of applying all functions\n * @example\n * pipe(\n * Result.tryCatch(() => JSON.parse(input)),\n * r => Result.flatMap(r, validate),\n * r => Result.map(r, transform),\n * )\n */\nexport function pipe<A>(a: A): A;\nexport function pipe<A, B>(a: A, ab: (a: A) => B): B;\nexport function pipe<A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C;\nexport function pipe<A, B, C, D>(a: A, ab: (a: A) => B, bc: (b: B) => C, cd: (c: C) => D): D;\nexport 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;\nexport 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;\nexport 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;\nexport function pipe<A, B, C, D, E, F, G, H>(\n a: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n fg: (f: F) => G,\n gh: (g: G) => H,\n): H;\nexport function pipe<A, B, C, D, E, F, G, H, I>(\n a: A,\n ab: (a: A) => B,\n bc: (b: B) => C,\n cd: (c: C) => D,\n de: (d: D) => E,\n ef: (e: E) => F,\n fg: (f: F) => G,\n gh: (g: G) => H,\n hi: (h: H) => I,\n): I;\nexport function pipe(a: unknown, ...fns: ((v: unknown) => unknown)[]): unknown {\n let result = a;\n for (let i = 0; i < fns.length; i++) {\n result = fns[i](result);\n }\n return result;\n}\n"],"names":["some","none","ok","err","isOk","isErr","isSome","isNone","isThenable","isSync","safeTry","safeTryAsync","unwrap","gen","genAsync","Option","Result","Iter","pipe","a","fns","result","i","length"],"mappings":"AAAA,SAASA,IAAI,EAAEC,IAAI,EAAEC,EAAE,EAAEC,GAAG,EAAEC,IAAI,EAAEC,KAAK,EAAEC,MAAM,EAAEC,MAAM,EAAEC,UAAU,EAAEC,MAAM,QAAQ,aAAa;AAiBlG,SAASC,OAAO,EAAEC,YAAY,EAAEC,MAAM,EAAEC,GAAG,EAAEC,QAAQ,QAAQ,cAAc;AAC3E,OAAO,KAAKC,MAAM,MAAM,cAAc;AACtC,OAAO,KAAKC,MAAM,MAAM,cAAc;AACtC,OAAO,KAAKC,IAAI,MAAM,YAAY;AA0ClC,OAAO,SAASC,KAAKC,CAAU,EAAE,GAAGC,GAAgC;IAClE,IAAIC,SAASF;IACb,IAAK,IAAIG,IAAI,GAAGA,IAAIF,IAAIG,MAAM,EAAED,IAAK;QACnCD,SAASD,GAAG,CAACE,EAAE,CAACD;IAClB;IACA,OAAOA;AACT"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nalloc",
3
3
  "description": "Rust-like Option and Result for TypeScript with near-zero allocations for extreme performance",
4
- "version": "0.1.0",
4
+ "version": "0.2.1",
5
5
  "type": "module",
6
6
  "types": "build/index.d.ts",
7
7
  "main": "build/index.cjs",
@@ -102,7 +102,7 @@
102
102
  "build": "rm -rf build && inop src build -i __tests__ -i *.tmp.ts && tsc --declaration --emitDeclarationOnly",
103
103
  "lint": "eslint .",
104
104
  "test": "vitest run",
105
- "bench": "overtake benchmarks/* --pin-cores -w 2 --progress",
105
+ "bench": "overtake benchmarks/* --pin-cores -w 1 --progress",
106
106
  "bench:save": "pnpm bench --save-baseline .bench-baseline.json",
107
107
  "bench:test": "pnpm bench --compare-baseline .bench-baseline.json"
108
108
  }
@@ -1,6 +1,8 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import * as index from '../index.js';
3
+ import { pipe, Result } from '../index.js';
3
4
  import { isThenable, isSync } from '../types.js';
5
+ import { ok, err } from '../types.js';
4
6
 
5
7
  describe('index.ts exports', () => {
6
8
  it('exports exist', () => {
@@ -52,4 +54,36 @@ describe('MaybePromise guards', () => {
52
54
  expect(isSync(undefined)).toBe(true);
53
55
  });
54
56
  });
57
+ });
58
+
59
+ describe('pipe', () => {
60
+ it('returns value with no functions', () => {
61
+ expect(pipe(42)).toBe(42);
62
+ });
63
+
64
+ it('applies single function', () => {
65
+ expect(pipe(2, x => x * 3)).toBe(6);
66
+ });
67
+
68
+ it('applies multiple functions left to right', () => {
69
+ expect(pipe(2, x => x * 3, x => x + 1, x => String(x))).toBe('7');
70
+ });
71
+
72
+ it('works with Result operations', () => {
73
+ const result = pipe(
74
+ Result.tryCatch(() => JSON.parse('{"a":1}')),
75
+ r => Result.map(r, (v: { a: number }) => v.a),
76
+ r => Result.unwrapOr(r, 0),
77
+ );
78
+ expect(result).toBe(1);
79
+ });
80
+
81
+ it('works with Result error path', () => {
82
+ const result = pipe(
83
+ Result.tryCatch(() => JSON.parse('invalid')),
84
+ r => Result.map(r, (v: unknown) => v),
85
+ r => Result.unwrapOr(r, 'fallback'),
86
+ );
87
+ expect(result).toBe('fallback');
88
+ });
55
89
  });
@@ -38,7 +38,8 @@ import {
38
38
  satisfiesOption,
39
39
  filterMap,
40
40
  isNoneOr,
41
- findMap
41
+ findMap,
42
+ tapNone
42
43
  } from '../option.js';
43
44
  import { ok, err, some, none } from '../types.js';
44
45
 
@@ -176,6 +177,27 @@ describe('Option', () => {
176
177
  });
177
178
  });
178
179
 
180
+ describe('tapNone', () => {
181
+ it('runs for None and returns original', () => {
182
+ let called = false;
183
+ const result = tapNone(none, () => {
184
+ called = true;
185
+ });
186
+ expect(isNone(result)).toBe(true);
187
+ expect(called).toBe(true);
188
+ });
189
+
190
+ it('does not run for Some', () => {
191
+ let called = false;
192
+ const opt = of(5);
193
+ const result = tapNone(opt, () => {
194
+ called = true;
195
+ });
196
+ expect(result).toBe(opt);
197
+ expect(called).toBe(false);
198
+ });
199
+ });
200
+
179
201
  describe('filter', () => {
180
202
  it('keeps Some if predicate is true', () => {
181
203
  const result = filter(of(5), x => x > 3);
@@ -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
  }