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/README.md +28 -0
- package/build/nonempty.cjs +35 -0
- package/build/nonempty.cjs.map +1 -0
- package/build/nonempty.d.ts +34 -0
- package/build/nonempty.js +14 -0
- package/build/nonempty.js.map +1 -0
- package/build/option.cjs +1 -1
- package/build/option.cjs.map +1 -1
- package/build/option.d.ts +2 -2
- package/build/option.js +1 -1
- package/build/option.js.map +1 -1
- package/build/result.cjs +10 -7
- package/build/result.cjs.map +1 -1
- package/build/result.d.ts +3 -2
- package/build/result.js +10 -7
- package/build/result.js.map +1 -1
- package/build/safe.cjs +4 -0
- package/build/safe.cjs.map +1 -1
- package/build/safe.d.ts +2 -0
- package/build/safe.js +1 -0
- package/build/safe.js.map +1 -1
- package/package.json +14 -10
- package/src/__tests__/iter.ts +18 -0
- package/src/__tests__/nonempty.ts +46 -0
- package/src/__tests__/nonempty.types.ts +38 -0
- package/src/__tests__/option.ts +4 -0
- package/src/__tests__/result.ts +32 -6
- package/src/__tests__/result.types.ts +2 -2
- package/src/nonempty.ts +48 -0
- package/src/option.ts +3 -4
- package/src/result.ts +18 -12
- package/src/safe.ts +2 -0
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;
|
|
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.
|
|
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
|
-
"@
|
|
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
|
-
"
|
|
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.
|
|
105
|
-
"
|
|
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": "
|
|
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",
|
package/src/__tests__/iter.ts
CHANGED
|
@@ -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>;
|
package/src/__tests__/option.ts
CHANGED
|
@@ -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', () => {
|
package/src/__tests__/result.ts
CHANGED
|
@@ -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[]]>>>;
|
package/src/nonempty.ts
ADDED
|
@@ -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
|
|
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
|
-
|
|
87
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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.
|