nalloc 0.2.2 → 0.4.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/safe.js CHANGED
@@ -3,6 +3,7 @@ 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 * as NonEmpty from "./nonempty.js";
6
7
  export function pipe(a, ...fns) {
7
8
  let result = a;
8
9
  for(let i = 0; i < fns.length; i++){
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, 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"}
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 type { NonEmptyArray, ReadonlyNonEmptyArray } from './nonempty.js';\nexport * as Option from './option.js';\nexport * as Result from './result.js';\nexport * as Iter from './iter.js';\nexport * as NonEmpty from './nonempty.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","NonEmpty","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;AAE3E,OAAO,KAAKC,MAAM,MAAM,cAAc;AACtC,OAAO,KAAKC,MAAM,MAAM,cAAc;AACtC,OAAO,KAAKC,IAAI,MAAM,YAAY;AAClC,OAAO,KAAKC,QAAQ,MAAM,gBAAgB;AA0C1C,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.2.2",
4
+ "version": "0.4.0",
5
5
  "type": "module",
6
6
  "types": "build/index.d.ts",
7
7
  "main": "build/index.cjs",
@@ -35,6 +35,10 @@
35
35
  "require": "./build/iter.cjs",
36
36
  "import": "./build/iter.js"
37
37
  },
38
+ "./nonempty": {
39
+ "require": "./build/nonempty.cjs",
40
+ "import": "./build/nonempty.js"
41
+ },
38
42
  "./package.json": "./package.json"
39
43
  },
40
44
  "files": [
@@ -54,6 +58,8 @@
54
58
  "err",
55
59
  "some",
56
60
  "none",
61
+ "nonempty",
62
+ "non-empty-array",
57
63
  "unwrap",
58
64
  "rust",
59
65
  "typescript",
@@ -92,23 +98,21 @@
92
98
  },
93
99
  "homepage": "https://github.com/3axap4eHko/nalloc#readme",
94
100
  "devDependencies": {
95
- "@eslint/js": "^10.0.1",
96
- "@vitest/coverage-v8": "^4.1.2",
97
- "eslint": "^10.2.0",
98
- "eslint-plugin-prettier": "^5.5.5",
101
+ "@vitest/coverage-v8": "^4.1.7",
99
102
  "inop": "^0.9.0",
100
103
  "overtake": "^2.1.1",
101
- "prettier": "^3.8.1",
104
+ "oxlint": "^1.67.0",
105
+ "prettier": "^3.8.3",
102
106
  "tsafe": "^1.8.12",
103
107
  "tslib": "^2.8.1",
104
- "typescript": "^6.0.2",
105
- "typescript-eslint": "^8.58.0",
106
- "vitest": "^4.1.2"
108
+ "typescript": "^6.0.3",
109
+ "vitest": "^4.1.7"
107
110
  },
108
111
  "scripts": {
109
112
  "check": "tsc --noEmit -p tsconfig.check.json",
110
113
  "build": "rm -rf build && inop src build -i __tests__ -i *.tmp.ts && tsc --declaration --emitDeclarationOnly",
111
- "lint": "eslint .",
114
+ "lint": "oxlint",
115
+ "format": "prettier --check .",
112
116
  "test": "vitest run",
113
117
  "bench": "overtake benchmarks/* --pin-cores -w 1 --progress",
114
118
  "bench:save": "pnpm bench --save-baseline .bench-baseline.json",
@@ -96,6 +96,24 @@ describe('Iter', () => {
96
96
  expect(returned).toBe(true);
97
97
  });
98
98
 
99
+ it('return() after exhaustion is a no-op', () => {
100
+ let returnCalls = 0;
101
+ const iter: Iterable<number> = {
102
+ [Symbol.iterator]: () => {
103
+ let i = 0;
104
+ return {
105
+ next() { return i++ < 1 ? { value: i, done: false } : { value: undefined as unknown as number, done: true }; },
106
+ return() { returnCalls++; return { value: undefined, done: true }; },
107
+ };
108
+ },
109
+ };
110
+ const gen = safeIter(iter);
111
+ expect([...gen]).toHaveLength(1);
112
+ const final = gen.return!(undefined as never);
113
+ expect(final.done).toBe(true);
114
+ expect(returnCalls).toBe(0);
115
+ });
116
+
99
117
  it('yields values then stops on throw', () => {
100
118
  function* twoThenThrow() {
101
119
  yield 1;
@@ -0,0 +1,46 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isNonEmpty, assertNonEmpty, fromArray } from '../nonempty.js';
3
+ import { isSome, isNone } from '../types.js';
4
+
5
+ describe('NonEmpty', () => {
6
+ describe('isNonEmpty', () => {
7
+ it('returns false for an empty array', () => {
8
+ expect(isNonEmpty([])).toBe(false);
9
+ });
10
+
11
+ it('returns true for a single-element array', () => {
12
+ expect(isNonEmpty([1])).toBe(true);
13
+ });
14
+
15
+ it('returns true for a multi-element array', () => {
16
+ expect(isNonEmpty([1, 2, 3])).toBe(true);
17
+ });
18
+ });
19
+
20
+ describe('assertNonEmpty', () => {
21
+ it('throws on an empty array', () => {
22
+ expect(() => assertNonEmpty([])).toThrow('Expected array to be non-empty');
23
+ });
24
+
25
+ it('throws with a custom message', () => {
26
+ expect(() => assertNonEmpty([], 'nope')).toThrow('nope');
27
+ });
28
+
29
+ it('does not throw on a non-empty array', () => {
30
+ expect(() => assertNonEmpty([1])).not.toThrow();
31
+ });
32
+ });
33
+
34
+ describe('fromArray', () => {
35
+ it('returns None for an empty array', () => {
36
+ expect(isNone(fromArray([]))).toBe(true);
37
+ });
38
+
39
+ it('returns Some wrapping the same array value for a non-empty array', () => {
40
+ const input = [1, 2];
41
+ const result = fromArray(input);
42
+ expect(isSome(result)).toBe(true);
43
+ expect(result).toBe(input);
44
+ });
45
+ });
46
+ });
@@ -0,0 +1,38 @@
1
+ import { assert, type Equals } from 'tsafe';
2
+ import {
3
+ isNonEmpty,
4
+ assertNonEmpty,
5
+ fromArray,
6
+ type NonEmptyArray,
7
+ type ReadonlyNonEmptyArray,
8
+ } from '../nonempty.js';
9
+ import type { Option } from '../types.js';
10
+
11
+ const readonlyValues: readonly number[] = [1, 2, 3];
12
+ const mutableValues: number[] = [1, 2, 3];
13
+
14
+ if (isNonEmpty(readonlyValues)) {
15
+ assert<Equals<typeof readonlyValues, ReadonlyNonEmptyArray<number>>>;
16
+ const _head: number = readonlyValues[0];
17
+ }
18
+
19
+ if (isNonEmpty(mutableValues)) {
20
+ const _head: number = mutableValues[0];
21
+ }
22
+
23
+ const asserted: readonly number[] = [1];
24
+ assertNonEmpty(asserted);
25
+ assert<Equals<typeof asserted, ReadonlyNonEmptyArray<number>>>;
26
+ const _assertedHead: number = asserted[0];
27
+
28
+ const maybeNonEmpty = fromArray<number>([1, 2]);
29
+ assert<Equals<typeof maybeNonEmpty, Option<ReadonlyNonEmptyArray<number>>>>;
30
+
31
+ const fromEmpty = fromArray([] as const);
32
+ assert<Equals<typeof fromEmpty, Option<ReadonlyNonEmptyArray<never>>>>;
33
+
34
+ type _MutableShape = Equals<NonEmptyArray<number>, [number, ...number[]]>;
35
+ assert<_MutableShape>;
36
+
37
+ type _ReadonlyShape = Equals<ReadonlyNonEmptyArray<number>, readonly [number, ...number[]]>;
38
+ assert<_ReadonlyShape>;
@@ -426,6 +426,10 @@ describe('Option', () => {
426
426
  it('returns false for None', () => {
427
427
  expect(contains(none, 42)).toBe(false);
428
428
  });
429
+
430
+ it('treats NaN as equal to NaN', () => {
431
+ expect(contains(of(NaN), NaN)).toBe(true);
432
+ });
429
433
  });
430
434
 
431
435
  describe('isSomeAnd', () => {
@@ -748,12 +748,6 @@ describe('Result', () => {
748
748
  expect(isOk(outcome)).toBe(true);
749
749
  expect(unwrap(outcome)).toBe(3);
750
750
  });
751
-
752
- it('any returns empty errors for empty array', () => {
753
- const outcome = any([]);
754
- expect(isErr(outcome)).toBe(true);
755
- expect((outcome as any).error).toEqual([]);
756
- });
757
751
  });
758
752
 
759
753
  describe('tryCatchMaybePromise', () => {
@@ -1107,6 +1101,22 @@ describe('Result', () => {
1107
1101
  expect(isOk(result)).toBe(true);
1108
1102
  expect(result).toBe(42);
1109
1103
  });
1104
+
1105
+ it('runs try/finally cleanup when short-circuiting on Err', () => {
1106
+ let cleanedUp = false;
1107
+ const result = gen(function*($) {
1108
+ try {
1109
+ const a = yield* $(ok(10));
1110
+ const b = yield* $(err('fail') as Result<number, string>);
1111
+ return a + b;
1112
+ } finally {
1113
+ cleanedUp = true;
1114
+ }
1115
+ });
1116
+ expect(isErr(result)).toBe(true);
1117
+ expect((result as any).error).toBe('fail');
1118
+ expect(cleanedUp).toBe(true);
1119
+ });
1110
1120
  });
1111
1121
 
1112
1122
  describe('genAsync', () => {
@@ -1139,6 +1149,22 @@ describe('Result', () => {
1139
1149
  expect(isErr(result)).toBe(true);
1140
1150
  expect((result as any).error).toBe('boom');
1141
1151
  });
1152
+
1153
+ it('runs try/finally cleanup when short-circuiting on Err', async () => {
1154
+ let cleanedUp = false;
1155
+ const result = await genAsync(async function*($) {
1156
+ try {
1157
+ const a = yield* $(ok(10));
1158
+ const b = yield* $(err('async fail') as Result<number, string>);
1159
+ return a + b;
1160
+ } finally {
1161
+ cleanedUp = true;
1162
+ }
1163
+ });
1164
+ expect(isErr(result)).toBe(true);
1165
+ expect((result as any).error).toBe('async fail');
1166
+ expect(cleanedUp).toBe(true);
1167
+ });
1142
1168
  });
1143
1169
 
1144
1170
  });
@@ -138,7 +138,7 @@ const collected = collect(results);
138
138
  assert<Equals<typeof collected, Result<number[], string>>>;
139
139
 
140
140
  const collectedAll = collectAll(results);
141
- assert<Equals<typeof collectedAll, Result<number[], string[]>>>;
141
+ assert<Equals<typeof collectedAll, Result<number[], [string, ...string[]]>>>;
142
142
 
143
143
  const transposed = transpose(ok(42 as Option<number>) as Result<Option<number>, string>);
144
144
  assert<Equals<typeof transposed, Option<Result<number, string>>>>;
@@ -234,7 +234,7 @@ const allResult = all([ok(1), ok(2)]);
234
234
  assert<Equals<typeof allResult, Result<readonly number[], unknown>>>;
235
235
 
236
236
  const anyResult = any([err('a'), ok(3)]);
237
- assert<Equals<typeof anyResult, Result<number, string[]>>>;
237
+ assert<Equals<typeof anyResult, Result<number, [string, ...string[]]>>>;
238
238
 
239
239
  const partitionAsyncResult = partitionAsync([Promise.resolve(ok(1)), Promise.resolve(err('a'))]);
240
240
  assert<Equals<typeof partitionAsyncResult, Promise<[number[], string[]]>>>;
@@ -0,0 +1,48 @@
1
+ import { NONE } from './types.js';
2
+ import type { Option, Some } from './types.js';
3
+
4
+ /** An array proven to contain at least one element. The runtime value is a plain array. */
5
+ export type NonEmptyArray<T> = [T, ...T[]];
6
+
7
+ /** A readonly array proven to contain at least one element. The runtime value is a plain array. */
8
+ export type ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];
9
+
10
+ /**
11
+ * Checks whether an array has at least one element.
12
+ * @param values - The array to check
13
+ * @returns true if the array is non-empty
14
+ * @example
15
+ * isNonEmpty([]) // false
16
+ * isNonEmpty([1]) // true
17
+ */
18
+ export function isNonEmpty<T>(values: readonly T[]): values is ReadonlyNonEmptyArray<T> {
19
+ return values.length > 0;
20
+ }
21
+
22
+ /**
23
+ * Asserts that an array is non-empty, throwing otherwise.
24
+ * @param values - The array to check
25
+ * @param message - Custom error message
26
+ * @throws Error if the array is empty
27
+ * @example
28
+ * assertNonEmpty([1]) // passes
29
+ * assertNonEmpty([]) // throws Error
30
+ */
31
+ export function assertNonEmpty<T>(values: readonly T[], message?: string): asserts values is ReadonlyNonEmptyArray<T> {
32
+ if (values.length === 0) {
33
+ throw new Error(message ?? 'Expected array to be non-empty');
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Converts an array to an Option of a non-empty array. Returns the same array
39
+ * value when non-empty - no allocation or cloning.
40
+ * @param values - The array to convert
41
+ * @returns Some(values) typed as non-empty if length > 0, None otherwise
42
+ * @example
43
+ * fromArray([]) // None
44
+ * fromArray([1, 2]) // Some([1, 2]) with non-empty type
45
+ */
46
+ export function fromArray<T>(values: readonly T[]): Option<ReadonlyNonEmptyArray<T>> {
47
+ return values.length > 0 ? (values as Some<ReadonlyNonEmptyArray<T>>) : NONE;
48
+ }
package/src/option.ts CHANGED
@@ -77,15 +77,14 @@ export function assertSome<T>(opt: Option<T>, message?: string): asserts opt is
77
77
  * runtime to preserve zero-allocation semantics. Use assertSome() if you
78
78
  * need runtime validation that a value is Some.
79
79
  *
80
- * @param _ - The value to assert as Option (not validated at runtime)
80
+ * @param value - The value to assert as Option (not validated at runtime)
81
81
  * @example
82
82
  * const value: number | null = getValue();
83
83
  * satisfiesOption(value); // Compiles, but no runtime check
84
84
  * // value is now typed as Option<number>
85
85
  */
86
- export function satisfiesOption<T>(_: Option<T> | T): asserts _ is Option<T> {
87
- // Compile-time only - no runtime validation to preserve zero-allocation semantics.
88
- }
86
+ // oxlint-disable-next-line no-unused-vars
87
+ export function satisfiesOption<T>(value: Option<T> | T): asserts value is Option<T> {}
89
88
 
90
89
  /**
91
90
  * Maps and filters an iterable, collecting only Some values.
package/src/result.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { err as ERR, isOk, isErr, isSome, isNone, NONE, optionOf, isThenable } from './types.js';
2
2
  import type { Ok, Err, Result, Option, Widen, WidenNever, MaybePromise } from './types.js';
3
+ import type { NonEmptyArray } from './nonempty.js';
3
4
 
4
5
  export type { Ok, Err, Result };
5
6
  export { isOk, isErr };
@@ -659,7 +660,7 @@ export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
659
660
  * collectAll([ok(1), ok(2)]) // Ok([1, 2])
660
661
  * collectAll([ok(1), err('a'), err('b')]) // Err(['a', 'b'])
661
662
  */
662
- export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
663
+ export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], NonEmptyArray<E>> {
663
664
  const oks: T[] = [];
664
665
  const errs: E[] = [];
665
666
  for (let i = 0; i < results.length; i++) {
@@ -670,7 +671,7 @@ export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
670
671
  errs.push(result.error);
671
672
  }
672
673
  }
673
- return errs.length > 0 ? ERR(errs) : (oks as Ok<T[]>);
674
+ return errs.length > 0 ? ERR(errs as NonEmptyArray<E>) : (oks as Ok<T[]>);
674
675
  }
675
676
 
676
677
  /**
@@ -690,14 +691,14 @@ export function all<T, E>(results: Result<T, E>[]): Result<readonly Widen<T>[],
690
691
  * any([err('a'), ok(1), err('b')]) // Ok(1)
691
692
  * any([err('a'), err('b')]) // Err(['a', 'b'])
692
693
  */
693
- export function any<T, E>(results: Result<T, E>[]): Result<Widen<T>, WidenNever<E>[]> {
694
+ export function any<T, E>(results: NonEmptyArray<Result<T, E>>): Result<Widen<T>, NonEmptyArray<WidenNever<E>>> {
694
695
  const errors: WidenNever<E>[] = [];
695
696
  for (let i = 0; i < results.length; i++) {
696
697
  const result = results[i];
697
698
  if (isOk(result)) return result as Ok<Widen<T>>;
698
699
  errors.push((result as Err<WidenNever<E>>).error);
699
700
  }
700
- return ERR(errors);
701
+ return ERR(errors as NonEmptyArray<WidenNever<E>>);
701
702
  }
702
703
 
703
704
  /**
@@ -711,7 +712,7 @@ export function any<T, E>(results: Result<T, E>[]): Result<Widen<T>, WidenNever<
711
712
  */
712
713
  export function transpose<T, E>(result: Result<Option<T>, E>): Option<Result<T, E>> {
713
714
  if (isErr(result)) {
714
- return ERR(result.error) as Option<Result<T, E>>;
715
+ return result as Option<Result<T, E>>;
715
716
  }
716
717
  const opt = result as Option<T>;
717
718
  return isNone(opt) ? NONE : (opt as unknown as Ok<T> as Option<Result<T, E>>);
@@ -909,11 +910,8 @@ export async function safeTryAsync<T>(fn: () => Promise<T>): Promise<Result<T, u
909
910
  }
910
911
 
911
912
  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;
913
+ if (isErr(result)) yield result;
914
+ return result as T;
917
915
  }
918
916
 
919
917
  type Unwrapper = <T, E>(result: Result<T, E>) => Generator<Err<E>, T>;
@@ -934,7 +932,11 @@ type Unwrapper = <T, E>(result: Result<T, E>) => Generator<Err<E>, T>;
934
932
  export function gen<E, T>(fn: ($: Unwrapper) => Generator<Err<E>, T>): Result<T, E> {
935
933
  const iter = fn(unwrapYield);
936
934
  const step = iter.next();
937
- if (!step.done) return step.value;
935
+ if (!step.done) {
936
+ // short-circuit abandons the generator mid-body; resume it so try/finally cleanup runs
937
+ iter.return(undefined as never);
938
+ return step.value;
939
+ }
938
940
  return step.value as Ok<T>;
939
941
  }
940
942
 
@@ -952,6 +954,10 @@ export function gen<E, T>(fn: ($: Unwrapper) => Generator<Err<E>, T>): Result<T,
952
954
  export async function genAsync<E, T>(fn: ($: Unwrapper) => AsyncGenerator<Err<E>, T>): Promise<Result<T, E>> {
953
955
  const iter = fn(unwrapYield);
954
956
  const step = await iter.next();
955
- if (!step.done) return step.value;
957
+ if (!step.done) {
958
+ // short-circuit abandons the generator mid-body; resume it so try/finally cleanup runs
959
+ await iter.return(undefined as never);
960
+ return step.value;
961
+ }
956
962
  return step.value as Ok<T>;
957
963
  }
package/src/safe.ts CHANGED
@@ -16,9 +16,11 @@ export type {
16
16
  MaybePromise,
17
17
  } from './types.js';
18
18
  export { safeTry, safeTryAsync, unwrap, gen, genAsync } from './result.js';
19
+ export type { NonEmptyArray, ReadonlyNonEmptyArray } from './nonempty.js';
19
20
  export * as Option from './option.js';
20
21
  export * as Result from './result.js';
21
22
  export * as Iter from './iter.js';
23
+ export * as NonEmpty from './nonempty.js';
22
24
 
23
25
  /**
24
26
  * Threads a value through a sequence of unary functions, left to right.