nalloc 0.0.1 → 0.0.2

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.
Files changed (64) hide show
  1. package/README.md +124 -38
  2. package/build/index.cjs +12 -68
  3. package/build/index.cjs.map +1 -1
  4. package/build/index.d.ts +1 -4
  5. package/build/index.js +1 -3
  6. package/build/index.js.map +1 -1
  7. package/build/iter.cjs +105 -0
  8. package/build/iter.cjs.map +1 -0
  9. package/build/iter.d.ts +61 -0
  10. package/build/iter.js +78 -0
  11. package/build/iter.js.map +1 -0
  12. package/build/option.cjs +19 -5
  13. package/build/option.cjs.map +1 -1
  14. package/build/option.d.ts +22 -1
  15. package/build/option.js +14 -6
  16. package/build/option.js.map +1 -1
  17. package/build/result.cjs +125 -54
  18. package/build/result.cjs.map +1 -1
  19. package/build/result.d.ts +83 -53
  20. package/build/result.js +100 -38
  21. package/build/result.js.map +1 -1
  22. package/build/safe.cjs +34 -15
  23. package/build/safe.cjs.map +1 -1
  24. package/build/safe.d.ts +4 -27
  25. package/build/safe.js +3 -14
  26. package/build/safe.js.map +1 -1
  27. package/build/types.cjs +38 -7
  28. package/build/types.cjs.map +1 -1
  29. package/build/types.d.ts +26 -4
  30. package/build/types.js +23 -7
  31. package/build/types.js.map +1 -1
  32. package/build/unsafe.cjs +14 -61
  33. package/build/unsafe.cjs.map +1 -1
  34. package/build/unsafe.d.ts +2 -27
  35. package/build/unsafe.js +2 -9
  36. package/build/unsafe.js.map +1 -1
  37. package/package.json +13 -16
  38. package/src/__tests__/index.ts +42 -0
  39. package/src/__tests__/iter.ts +218 -0
  40. package/src/__tests__/option.ts +48 -19
  41. package/src/__tests__/result.ts +286 -91
  42. package/src/__tests__/result.types.ts +3 -22
  43. package/src/__tests__/safe.ts +9 -15
  44. package/src/__tests__/unsafe.ts +11 -12
  45. package/src/index.ts +1 -18
  46. package/src/iter.ts +129 -0
  47. package/src/option.ts +36 -7
  48. package/src/result.ts +216 -113
  49. package/src/safe.ts +5 -42
  50. package/src/types.ts +52 -14
  51. package/src/unsafe.ts +2 -47
  52. package/build/devtools.cjs +0 -79
  53. package/build/devtools.cjs.map +0 -1
  54. package/build/devtools.d.ts +0 -82
  55. package/build/devtools.js +0 -43
  56. package/build/devtools.js.map +0 -1
  57. package/build/testing.cjs +0 -111
  58. package/build/testing.cjs.map +0 -1
  59. package/build/testing.d.ts +0 -85
  60. package/build/testing.js +0 -81
  61. package/build/testing.js.map +0 -1
  62. package/src/__tests__/tooling.ts +0 -86
  63. package/src/devtools.ts +0 -97
  64. package/src/testing.ts +0 -159
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.0.1",
4
+ "version": "0.0.2",
5
5
  "type": "module",
6
6
  "types": "build/index.d.ts",
7
7
  "main": "build/index.cjs",
@@ -31,13 +31,9 @@
31
31
  "require": "./build/unsafe.cjs",
32
32
  "import": "./build/unsafe.js"
33
33
  },
34
- "./testing": {
35
- "require": "./build/testing.cjs",
36
- "import": "./build/testing.js"
37
- },
38
- "./devtools": {
39
- "require": "./build/devtools.cjs",
40
- "import": "./build/devtools.js"
34
+ "./iter": {
35
+ "require": "./build/iter.cjs",
36
+ "import": "./build/iter.js"
41
37
  },
42
38
  "./package.json": "./package.json"
43
39
  },
@@ -71,17 +67,18 @@
71
67
  },
72
68
  "homepage": "https://github.com/3axap4eHko/nalloc#readme",
73
69
  "devDependencies": {
74
- "@eslint/js": "^9.39.2",
75
- "@vitest/coverage-v8": "^4.0.16",
76
- "eslint": "^9.39.2",
77
- "eslint-plugin-prettier": "^5.5.4",
78
- "inop": "^0.8.10",
79
- "prettier": "^3.7.4",
70
+ "@eslint/js": "^10.0.1",
71
+ "@vitest/coverage-v8": "^4.0.18",
72
+ "eslint": "^10.0.2",
73
+ "eslint-plugin-prettier": "^5.5.5",
74
+ "inop": "^0.9.0",
75
+ "overtake": "^1.4.0",
76
+ "prettier": "^3.8.1",
80
77
  "tsafe": "^1.8.12",
81
78
  "tslib": "^2.8.1",
82
79
  "typescript": "^5.9.3",
83
- "typescript-eslint": "^8.52.0",
84
- "vitest": "^4.0.16"
80
+ "typescript-eslint": "^8.56.1",
81
+ "vitest": "^4.0.18"
85
82
  },
86
83
  "scripts": {
87
84
  "check": "tsc src/__tests__/option.types.ts src/__tests__/result.types.ts --noEmit --lib esnext,dom --target esnext --module nodenext --moduleResolution nodenext",
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from 'vitest';
2
2
  import * as index from '../index.js';
3
+ import { isThenable, isSync } from '../types.js';
3
4
 
4
5
  describe('index.ts exports', () => {
5
6
  it('exports exist', () => {
@@ -10,4 +11,45 @@ describe('index.ts exports', () => {
10
11
  expect(index.Option).toBeDefined();
11
12
  expect(index.Result).toBeDefined();
12
13
  });
14
+ });
15
+
16
+ describe('MaybePromise guards', () => {
17
+ describe('isThenable', () => {
18
+ it('returns true for Promise', () => {
19
+ expect(isThenable(Promise.resolve(1))).toBe(true);
20
+ });
21
+
22
+ it('returns true for PromiseLike', () => {
23
+ const thenable = { then: (cb: (v: number) => void) => cb(1) };
24
+ expect(isThenable(thenable)).toBe(true);
25
+ });
26
+
27
+ it('returns false for plain value', () => {
28
+ expect(isThenable(42)).toBe(false);
29
+ expect(isThenable('string')).toBe(false);
30
+ expect(isThenable({ x: 1 })).toBe(false);
31
+ });
32
+
33
+ it('returns false for null/undefined', () => {
34
+ expect(isThenable(null)).toBe(false);
35
+ expect(isThenable(undefined)).toBe(false);
36
+ });
37
+ });
38
+
39
+ describe('isSync', () => {
40
+ it('returns false for Promise', () => {
41
+ expect(isSync(Promise.resolve(1))).toBe(false);
42
+ });
43
+
44
+ it('returns true for plain value', () => {
45
+ expect(isSync(42)).toBe(true);
46
+ expect(isSync('string')).toBe(true);
47
+ expect(isSync({ x: 1 })).toBe(true);
48
+ });
49
+
50
+ it('returns true for null/undefined', () => {
51
+ expect(isSync(null)).toBe(true);
52
+ expect(isSync(undefined)).toBe(true);
53
+ });
54
+ });
13
55
  });
@@ -0,0 +1,218 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ mapWhile,
4
+ safeIter,
5
+ tryCollect,
6
+ tryFold,
7
+ tryForEach,
8
+ } from '../iter.js';
9
+ import { ok, err, isOk, isErr, some, none } from '../types.js';
10
+ import type { Result } from '../types.js';
11
+
12
+ describe('Iter', () => {
13
+ describe('mapWhile', () => {
14
+ it('yields while Some, stops at first None', () => {
15
+ const result = [...mapWhile([1, 2, 3, 4, 5], n => n < 4 ? some(n * 10) : none)];
16
+ expect(result).toEqual([10, 20, 30]);
17
+ });
18
+
19
+ it('yields nothing when first is None', () => {
20
+ const result = [...mapWhile([1, 2, 3], () => none)];
21
+ expect(result).toEqual([]);
22
+ });
23
+
24
+ it('yields all when all Some', () => {
25
+ const result = [...mapWhile([1, 2, 3], n => some(n))];
26
+ expect(result).toEqual([1, 2, 3]);
27
+ });
28
+
29
+ it('handles empty source', () => {
30
+ const result = [...mapWhile([], () => some(1))];
31
+ expect(result).toEqual([]);
32
+ });
33
+
34
+ it('does not call fn after first None', () => {
35
+ let calls = 0;
36
+ const result = [...mapWhile([1, 2, 3, 4], n => {
37
+ calls++;
38
+ return n <= 2 ? some(n) : none;
39
+ })];
40
+ expect(result).toEqual([1, 2]);
41
+ expect(calls).toBe(3);
42
+ });
43
+ });
44
+
45
+ describe('safeIter', () => {
46
+ it('wraps normal values as Ok', () => {
47
+ const results = [...safeIter([1, 2, 3])];
48
+ expect(results).toHaveLength(3);
49
+ expect(results.every(r => isOk(r))).toBe(true);
50
+ expect(results).toEqual([1, 2, 3]);
51
+ });
52
+
53
+ it('catches per-item throws as Err', () => {
54
+ function* throwing() {
55
+ yield 1;
56
+ throw new Error('boom');
57
+ }
58
+ const results = [...safeIter(throwing())];
59
+ expect(results).toHaveLength(2);
60
+ expect(isOk(results[0])).toBe(true);
61
+ expect(results[0]).toBe(1);
62
+ expect(isErr(results[1])).toBe(true);
63
+ expect((results[1] as { error: Error }).error.message).toBe('boom');
64
+ });
65
+
66
+ it('handles empty source', () => {
67
+ expect([...safeIter([])]).toEqual([]);
68
+ });
69
+
70
+ it('stops after first throw from a persistently throwing iterator', () => {
71
+ const iter: Iterable<number> = {
72
+ [Symbol.iterator]: () => ({
73
+ next() { throw new Error('always'); },
74
+ }),
75
+ };
76
+ const results = [...safeIter(iter)];
77
+ expect(results).toHaveLength(1);
78
+ expect(isErr(results[0])).toBe(true);
79
+ expect((results[0] as { error: Error }).error.message).toBe('always');
80
+ });
81
+
82
+ it('calls iter.return() when consumer breaks early', () => {
83
+ let returned = false;
84
+ const iter: Iterable<number> = {
85
+ [Symbol.iterator]: () => {
86
+ let i = 0;
87
+ return {
88
+ next() { return { value: ++i, done: false }; },
89
+ return() { returned = true; return { value: undefined, done: true }; },
90
+ };
91
+ },
92
+ };
93
+ const gen = safeIter(iter);
94
+ gen.next(); // consume one value
95
+ gen.return(undefined as any); // consumer breaks
96
+ expect(returned).toBe(true);
97
+ });
98
+
99
+ it('yields values then stops on throw', () => {
100
+ function* twoThenThrow() {
101
+ yield 1;
102
+ yield 2;
103
+ throw new Error('third');
104
+ }
105
+ const results = [...safeIter(twoThenThrow())];
106
+ expect(results).toHaveLength(3);
107
+ expect(isOk(results[0])).toBe(true);
108
+ expect(results[0]).toBe(1);
109
+ expect(isOk(results[1])).toBe(true);
110
+ expect(results[1]).toBe(2);
111
+ expect(isErr(results[2])).toBe(true);
112
+ expect((results[2] as { error: Error }).error.message).toBe('third');
113
+ });
114
+ });
115
+
116
+ describe('tryCollect', () => {
117
+ it('collects all Ok values into Result<T[], E>', () => {
118
+ const results: Result<number, string>[] = [ok(1), ok(2), ok(3)];
119
+ const collected = tryCollect(results);
120
+ expect(isOk(collected)).toBe(true);
121
+ expect(collected).toEqual([1, 2, 3]);
122
+ });
123
+
124
+ it('short-circuits on first Err', () => {
125
+ let calls = 0;
126
+ function* source(): Generator<Result<number, string>> {
127
+ calls++; yield ok(1);
128
+ calls++; yield err('fail');
129
+ calls++; yield ok(3);
130
+ }
131
+ const collected = tryCollect(source());
132
+ expect(isErr(collected)).toBe(true);
133
+ expect((collected as { error: string }).error).toBe('fail');
134
+ expect(calls).toBe(2);
135
+ });
136
+
137
+ it('handles empty source', () => {
138
+ const collected = tryCollect([]);
139
+ expect(isOk(collected)).toBe(true);
140
+ expect(collected).toEqual([]);
141
+ });
142
+
143
+ it('works with lazy generators', () => {
144
+ function* source(): Generator<Result<number, string>> {
145
+ yield ok(2);
146
+ yield ok(4);
147
+ yield ok(6);
148
+ }
149
+ const collected = tryCollect(source());
150
+ expect(isOk(collected)).toBe(true);
151
+ expect(collected).toEqual([2, 4, 6]);
152
+ });
153
+ });
154
+
155
+ describe('tryFold', () => {
156
+ it('folds all values when no error', () => {
157
+ const result = tryFold([1, 2, 3], 0, (acc, n) => ok(acc + n));
158
+ expect(isOk(result)).toBe(true);
159
+ expect(result).toBe(6);
160
+ });
161
+
162
+ it('short-circuits on first Err', () => {
163
+ let calls = 0;
164
+ const result = tryFold([1, 2, 3, 4], 0, (acc, n) => {
165
+ calls++;
166
+ return n === 3 ? err('stopped') as Result<number, string> : ok(acc + n);
167
+ });
168
+ expect(isErr(result)).toBe(true);
169
+ expect((result as { error: string }).error).toBe('stopped');
170
+ expect(calls).toBe(3);
171
+ });
172
+
173
+ it('handles empty source', () => {
174
+ const result = tryFold([], 42, () => ok(0));
175
+ expect(isOk(result)).toBe(true);
176
+ expect(result).toBe(42);
177
+ });
178
+ });
179
+
180
+ describe('tryForEach', () => {
181
+ it('iterates all items when no error', () => {
182
+ const seen: number[] = [];
183
+ const result = tryForEach([1, 2, 3], n => {
184
+ seen.push(n);
185
+ return ok(undefined);
186
+ });
187
+ expect(isOk(result)).toBe(true);
188
+ expect(seen).toEqual([1, 2, 3]);
189
+ });
190
+
191
+ it('short-circuits on first Err', () => {
192
+ const seen: number[] = [];
193
+ const result = tryForEach([1, 2, 3, 4], n => {
194
+ seen.push(n);
195
+ return n === 2 ? err('stop') as Result<void, string> : ok(undefined);
196
+ });
197
+ expect(isErr(result)).toBe(true);
198
+ expect((result as { error: string }).error).toBe('stop');
199
+ expect(seen).toEqual([1, 2]);
200
+ });
201
+
202
+ it('handles empty source', () => {
203
+ const result = tryForEach([], () => ok(undefined));
204
+ expect(isOk(result)).toBe(true);
205
+ });
206
+ });
207
+
208
+ describe('composition', () => {
209
+ it('safeIter -> native filter(isOk) collects safe values', () => {
210
+ function* unstable() {
211
+ yield 1;
212
+ throw new Error('oops');
213
+ }
214
+ const safe = [...safeIter(unstable())].filter(isOk);
215
+ expect(safe).toEqual([1]);
216
+ });
217
+ });
218
+ });
@@ -36,9 +36,10 @@ import {
36
36
  unwrapOrReturn,
37
37
  assertSome,
38
38
  satisfiesOption,
39
- filterMap
39
+ filterMap,
40
+ isNoneOr,
41
+ findMap
40
42
  } from '../option.js';
41
- import { formatOption, inspectOption } from '../devtools.js';
42
43
  import { ok, err, some, none } from '../types.js';
43
44
 
44
45
  describe('Option', () => {
@@ -65,11 +66,9 @@ describe('Option', () => {
65
66
  expect(opt).toBe(42);
66
67
  });
67
68
 
68
- it('some does not throw on null/undefined', () => {
69
- expect(() => some(null as any)).not.toThrow();
70
- expect(() => some(undefined as any)).not.toThrow();
71
- expect(isNone(some(null as any))).toBe(true);
72
- expect(isNone(some(undefined as any))).toBe(true);
69
+ it('some throws on null/undefined', () => {
70
+ expect(() => some(null as any)).toThrow('some() requires a non-nullable value');
71
+ expect(() => some(undefined as any)).toThrow('some() requires a non-nullable value');
73
72
  });
74
73
 
75
74
  it('none creates None', () => {
@@ -564,9 +563,19 @@ describe('Option', () => {
564
563
  expect(isSome(opt)).toBe(true);
565
564
  expect(unwrap(opt)).toBe(99);
566
565
  });
566
+
567
+ it('fromPromise returns None on rejection without callback', async () => {
568
+ const opt = await fromPromise(Promise.reject(new Error('boom')));
569
+ expect(isNone(opt)).toBe(true);
570
+ });
567
571
  });
568
572
 
569
573
  describe('control helpers', () => {
574
+ it('unwrapOrReturn returns value for Some', () => {
575
+ const result = unwrapOrReturn(of(42), () => 77);
576
+ expect(result).toBe(42);
577
+ });
578
+
570
579
  it('unwrapOrReturn returns fallback for None', () => {
571
580
  const result = unwrapOrReturn(none, () => 77);
572
581
  expect(result).toBe(77);
@@ -588,18 +597,6 @@ describe('Option', () => {
588
597
  });
589
598
  });
590
599
 
591
- describe('introspection helpers', () => {
592
- it('inspectOption exposes discriminated metadata', () => {
593
- expect(inspectOption(of(4))).toEqual({ kind: 'some', value: 4 });
594
- expect(inspectOption(none)).toEqual({ kind: 'none' });
595
- });
596
-
597
- it('formatOption prints human friendly value', () => {
598
- expect(formatOption(of(1))).toBe('Some(1)');
599
- expect(formatOption(none)).toBe('None');
600
- });
601
- });
602
-
603
600
  describe('filterMap', () => {
604
601
  it('collects Some values from iterable', () => {
605
602
  const items = [of(1), none, of(3)];
@@ -607,4 +604,36 @@ describe('Option', () => {
607
604
  expect(result).toEqual([1, 3]);
608
605
  });
609
606
  });
607
+
608
+ describe('isNoneOr', () => {
609
+ it('returns true for None', () => {
610
+ expect(isNoneOr(none, () => false)).toBe(true);
611
+ });
612
+
613
+ it('returns true for Some when predicate is true', () => {
614
+ expect(isNoneOr(some(5), x => x > 2)).toBe(true);
615
+ });
616
+
617
+ it('returns false for Some when predicate is false', () => {
618
+ expect(isNoneOr(some(1), x => x > 2)).toBe(false);
619
+ });
620
+ });
621
+
622
+ describe('findMap', () => {
623
+ it('returns first Some result', () => {
624
+ const result = findMap([1, 2, 3], n => n > 1 ? some(n * 2) : none);
625
+ expect(isSome(result)).toBe(true);
626
+ expect(result).toBe(4);
627
+ });
628
+
629
+ it('returns None when no match', () => {
630
+ const result = findMap([1, 2, 3], n => n > 5 ? some(n) : none);
631
+ expect(isNone(result)).toBe(true);
632
+ });
633
+
634
+ it('returns None for empty iterable', () => {
635
+ const result = findMap([], () => some(1));
636
+ expect(isNone(result)).toBe(true);
637
+ });
638
+ });
610
639
  });