@peerigon/typescript-toolkit 1.1.0 → 2.0.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 +5 -1
- package/dist/assert/assert.d.ts +6 -7
- package/dist/assert/assert.d.ts.map +1 -1
- package/dist/assert/assert.js +7 -8
- package/dist/assert/assert.js.map +1 -1
- package/dist/dedupe/dedupe.d.ts +17 -0
- package/dist/dedupe/dedupe.d.ts.map +1 -0
- package/dist/dedupe/dedupe.js +19 -0
- package/dist/dedupe/dedupe.js.map +1 -0
- package/dist/enums/enums.d.ts +96 -0
- package/dist/enums/enums.d.ts.map +1 -0
- package/dist/enums/enums.js +98 -0
- package/dist/enums/enums.js.map +1 -0
- package/dist/main.d.ts +4 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -1
- package/dist/match/match.d.ts +72 -0
- package/dist/match/match.d.ts.map +1 -0
- package/dist/match/match.js +28 -0
- package/dist/match/match.js.map +1 -0
- package/dist/need/need.d.ts +22 -0
- package/dist/need/need.d.ts.map +1 -0
- package/dist/need/need.js +28 -0
- package/dist/need/need.js.map +1 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -39,7 +39,11 @@ import { assert } from "@peerigon/typescript-toolkit/assert";
|
|
|
39
39
|
|
|
40
40
|
| Module | Description | Docs |
|
|
41
41
|
| ---------------------------------- | -------------------------------------------------------------------------------- | --------------------------- |
|
|
42
|
-
| [`assert`](./src/assert/README.md) | Assert a value is not `null
|
|
42
|
+
| [`assert`](./src/assert/README.md) | Assert a value is not `null` or `undefined`, with TypeScript narrowing | [→](./src/assert/README.md) |
|
|
43
|
+
| [`need`](./src/need/README.md) | Assert a value is not `null` or `undefined` and return it with a narrowed type | [→](./src/need/README.md) |
|
|
44
|
+
| [`dedupe`](./src/dedupe/README.md) | Remove duplicate values from an array while preserving first-occurrence order | [→](./src/dedupe/README.md) |
|
|
45
|
+
| [`enums`](./src/enums/README.md) | Lightweight string-enum alternative for `erasableSyntaxOnly` TypeScript projects | [→](./src/enums/README.md) |
|
|
46
|
+
| [`match`](./src/match/README.md) | Exhaustive pattern matching with compile-time case checks, similar to `switch` | [→](./src/match/README.md) |
|
|
43
47
|
|
|
44
48
|
## License
|
|
45
49
|
|
package/dist/assert/assert.d.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { type ErrorMessage } from "../lib/error-message.ts";
|
|
2
2
|
/**
|
|
3
|
-
* Asserts that a given `value` is not `null
|
|
4
|
-
* and narrows its type.
|
|
3
|
+
* Asserts that a given `value` is not `null` or `undefined`, and narrows its type.
|
|
5
4
|
*
|
|
6
|
-
* Unlike regular truthiness checks, `assert` only rejects `null
|
|
7
|
-
*
|
|
5
|
+
* Unlike regular truthiness checks, `assert` only rejects `null` and `undefined`
|
|
6
|
+
* while allowing other falsy values like `false`, `0`, `""`, and `NaN` to pass through.
|
|
8
7
|
*
|
|
9
|
-
* @param value - The value that shouldn't be `null
|
|
10
|
-
* @param errorMessage - The error message to throw if the value is `null
|
|
8
|
+
* @param value - The value that shouldn't be `null` or `undefined`.
|
|
9
|
+
* @param errorMessage - The error message to throw if the value is `null` or `undefined`.
|
|
11
10
|
*/
|
|
12
11
|
export declare const assert: {
|
|
13
|
-
<Value>(value: Value, errorMessage?: ErrorMessage): asserts value is NonNullable<Value
|
|
12
|
+
<Value>(value: Value, errorMessage?: ErrorMessage): asserts value is NonNullable<Value>;
|
|
14
13
|
truthy(value: unknown, errorMessage?: ErrorMessage): asserts value;
|
|
15
14
|
};
|
|
16
15
|
//# sourceMappingURL=assert.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../src/assert/assert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG7E
|
|
1
|
+
{"version":3,"file":"assert.d.ts","sourceRoot":"","sources":["../../src/assert/assert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG7E;;;;;;;;GAQG;AACH,eAAO,MAAM,MAAM;KAAI,KAAK,SACnB,KAAK,iBACG,YAAY,GAC1B,QAAQ,KAAK,IAAI,WAAW,CAAC,KAAK,CAAC;kBAiB7B,OAAO,iBACC,YAAY,GAC1B,QAAQ,KAAK;CAff,CAAC"}
|
package/dist/assert/assert.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { getErrorMessage } from "../lib/error-message.js";
|
|
2
2
|
import { simpleStringify } from "../lib/string.js";
|
|
3
3
|
/**
|
|
4
|
-
* Asserts that a given `value` is not `null
|
|
5
|
-
* and narrows its type.
|
|
4
|
+
* Asserts that a given `value` is not `null` or `undefined`, and narrows its type.
|
|
6
5
|
*
|
|
7
|
-
* Unlike regular truthiness checks, `assert` only rejects `null
|
|
8
|
-
*
|
|
6
|
+
* Unlike regular truthiness checks, `assert` only rejects `null` and `undefined`
|
|
7
|
+
* while allowing other falsy values like `false`, `0`, `""`, and `NaN` to pass through.
|
|
9
8
|
*
|
|
10
|
-
* @param value - The value that shouldn't be `null
|
|
11
|
-
* @param errorMessage - The error message to throw if the value is `null
|
|
9
|
+
* @param value - The value that shouldn't be `null` or `undefined`.
|
|
10
|
+
* @param errorMessage - The error message to throw if the value is `null` or `undefined`.
|
|
12
11
|
*/
|
|
13
12
|
export const assert = (value, errorMessage) => {
|
|
14
|
-
if (value === undefined || value === null
|
|
15
|
-
throwTypeError(value, errorMessage, "neither null
|
|
13
|
+
if (value === undefined || value === null) {
|
|
14
|
+
throwTypeError(value, errorMessage, "neither null nor undefined");
|
|
16
15
|
}
|
|
17
16
|
};
|
|
18
17
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert.js","sourceRoot":"","sources":["../../src/assert/assert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAqB,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD
|
|
1
|
+
{"version":3,"file":"assert.js","sourceRoot":"","sources":["../../src/assert/assert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAqB,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,KAAY,EACZ,YAA2B,EACU,EAAE;IACvC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1C,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,4BAA4B,CAAC,CAAC;IACpE,CAAC;AACH,CAAC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,GAAG,CACd,KAAc,EACd,YAA2B,EACZ,EAAE;IACjB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,cAAc,CAAC,CAAC;IACtD,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,cAAc,GAAG,CACrB,KAAc,EACd,YAA0B,EAC1B,WAAmB,EACnB,EAAE;IACF,MAAM,IAAI,SAAS,CACjB,eAAe,CACb,YAAY,EACZ,GAAG,EAAE,CACH,8BAA8B,WAAW,aAAa,eAAe,CAAC,KAAK,CAAC,EAAE,CACjF,CACF,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { getErrorMessage, type ErrorMessage } from \"../lib/error-message.ts\";\nimport { simpleStringify } from \"../lib/string.ts\";\n\n/**\n * Asserts that a given `value` is not `null` or `undefined`, and narrows its type.\n *\n * Unlike regular truthiness checks, `assert` only rejects `null` and `undefined`\n * while allowing other falsy values like `false`, `0`, `\"\"`, and `NaN` to pass through.\n *\n * @param value - The value that shouldn't be `null` or `undefined`.\n * @param errorMessage - The error message to throw if the value is `null` or `undefined`.\n */\nexport const assert = <Value>(\n value: Value,\n errorMessage?: ErrorMessage,\n): asserts value is NonNullable<Value> => {\n if (value === undefined || value === null) {\n throwTypeError(value, errorMessage, \"neither null nor undefined\");\n }\n};\n\n/**\n * Asserts that a given `value` is truthy,\n * and narrows its type to exclude falsy values.\n *\n * This function performs a standard truthiness check, rejecting\n * all falsy values: `false`, `0`, `-0`, `0n`, `\"\"`, `null`, `undefined`, and `NaN`.\n *\n * @param value - The value to assert the truthy check on.\n * @param errorMessage - The error message to throw if the value is falsy.\n */\nassert.truthy = (\n value: unknown,\n errorMessage?: ErrorMessage,\n): asserts value => {\n if (!value) {\n throwTypeError(value, errorMessage, \"truthy value\");\n }\n};\n\nconst throwTypeError = (\n value: unknown,\n errorMessage: ErrorMessage,\n expectation: string,\n) => {\n throw new TypeError(\n getErrorMessage(\n errorMessage,\n () =>\n `Assertion failed: expected ${expectation}, but got ${simpleStringify(value)}`,\n ),\n );\n};\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove duplicates from an array using a Set.
|
|
3
|
+
*
|
|
4
|
+
* @param array - The array to remove duplicates from
|
|
5
|
+
* @returns A new array with duplicate values removed, preserving order of first occurrence
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const numbers = [1, 2, 2, 3, 1, 4];
|
|
10
|
+
* const unique = dedupe(numbers); // [1, 2, 3, 4]
|
|
11
|
+
*
|
|
12
|
+
* const strings = ["a", "b", "a", "c"];
|
|
13
|
+
* const uniqueStrings = dedupe(strings); // ["a", "b", "c"]
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare const dedupe: <Item>(array: Array<Item>) => Array<Item>;
|
|
17
|
+
//# sourceMappingURL=dedupe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe.d.ts","sourceRoot":"","sources":["../../src/dedupe/dedupe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,MAAM,GAAI,IAAI,EAAE,OAAO,KAAK,CAAC,IAAI,CAAC,KAAG,KAAK,CAAC,IAAI,CAE3D,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove duplicates from an array using a Set.
|
|
3
|
+
*
|
|
4
|
+
* @param array - The array to remove duplicates from
|
|
5
|
+
* @returns A new array with duplicate values removed, preserving order of first occurrence
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const numbers = [1, 2, 2, 3, 1, 4];
|
|
10
|
+
* const unique = dedupe(numbers); // [1, 2, 3, 4]
|
|
11
|
+
*
|
|
12
|
+
* const strings = ["a", "b", "a", "c"];
|
|
13
|
+
* const uniqueStrings = dedupe(strings); // ["a", "b", "c"]
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export const dedupe = (array) => [
|
|
17
|
+
...new Set(array),
|
|
18
|
+
];
|
|
19
|
+
//# sourceMappingURL=dedupe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../../src/dedupe/dedupe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAO,KAAkB,EAAe,EAAE,CAAC;IAC/D,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC;CAClB,CAAC","sourcesContent":["/**\n * Remove duplicates from an array using a Set.\n *\n * @param array - The array to remove duplicates from\n * @returns A new array with duplicate values removed, preserving order of first occurrence\n *\n * @example\n * ```ts\n * const numbers = [1, 2, 2, 3, 1, 4];\n * const unique = dedupe(numbers); // [1, 2, 3, 4]\n *\n * const strings = [\"a\", \"b\", \"a\", \"c\"];\n * const uniqueStrings = dedupe(strings); // [\"a\", \"b\", \"c\"]\n * ```\n */\nexport const dedupe = <Item>(array: Array<Item>): Array<Item> => [\n ...new Set(array),\n];\n"]}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
type EnumDefinition<Brand extends symbol, Definition extends Record<string, unknown>> = {
|
|
2
|
+
[Key in keyof Definition]: (Definition[Key] extends true ? Key : Definition[Key]) & {
|
|
3
|
+
readonly brand: Brand;
|
|
4
|
+
};
|
|
5
|
+
};
|
|
6
|
+
declare const enumsBrand: unique symbol;
|
|
7
|
+
/**
|
|
8
|
+
* Extract the union type of all enum values from an enum definition.
|
|
9
|
+
*
|
|
10
|
+
* @template Definition - The enum definition object type
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const Color = enums.define({ Red: true, Green: true, Blue: true });
|
|
15
|
+
* type Color = Enums<typeof Color>; // "Red" | "Green" | "Blue"
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export type Enums<Definition> = Definition[keyof Definition];
|
|
19
|
+
/**
|
|
20
|
+
* Utilities for creating type-safe enums with branded types to prevent mixing different enums.
|
|
21
|
+
*/
|
|
22
|
+
export declare const enums: {
|
|
23
|
+
/**
|
|
24
|
+
* Define a type-safe enum from an object definition.
|
|
25
|
+
*
|
|
26
|
+
* When a property value is `true`, the property key becomes the enum value.
|
|
27
|
+
* Otherwise, the provided value is used as the enum value.
|
|
28
|
+
*
|
|
29
|
+
* @param definition - Object defining the enum keys and values
|
|
30
|
+
* @returns A frozen enum object with type-safe values
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* // Using property keys as values
|
|
35
|
+
* const Direction = enums.define({
|
|
36
|
+
* North: true,
|
|
37
|
+
* South: true,
|
|
38
|
+
* East: true,
|
|
39
|
+
* West: true
|
|
40
|
+
* });
|
|
41
|
+
* console.log(Direction.North); // "North"
|
|
42
|
+
*
|
|
43
|
+
* // Using custom values
|
|
44
|
+
* const Status = enums.define({
|
|
45
|
+
* Inactive: 0,
|
|
46
|
+
* Pending: 1,
|
|
47
|
+
* Active: 2,
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
define: {
|
|
52
|
+
<const Definition extends Record<string, unknown>>(definition: Definition): EnumDefinition<typeof enumsBrand, Definition>;
|
|
53
|
+
branded: <const Brand extends symbol, const Definition extends Record<string, unknown>>(brand: Brand, definition: Definition) => EnumDefinition<Brand, Definition>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Parse a value to ensure it's a valid enum value.
|
|
57
|
+
*
|
|
58
|
+
* @param definition - The enum object created with enums.define
|
|
59
|
+
* @param value - The value to parse
|
|
60
|
+
* @returns The value typed as the enum type
|
|
61
|
+
* @throws {TypeError} If the value is not a valid enum value
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* const Direction = enums.define({
|
|
66
|
+
* North: true,
|
|
67
|
+
* South: true
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* const dir = enums.parse(Direction, "North"); // Returns Direction.North
|
|
71
|
+
* const invalid = enums.parse(Direction, "West"); // Throws TypeError
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
parse: <Definition extends Record<string, unknown>>(definition: Definition, value: unknown) => Enums<Definition>;
|
|
75
|
+
/**
|
|
76
|
+
* Get an array of [key, value] tuples from an enum definition.
|
|
77
|
+
*
|
|
78
|
+
* @param definition - The enum object created with enums.define
|
|
79
|
+
* @returns Array of [key, value] tuples
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* const Direction = enums.define({
|
|
84
|
+
* North: true,
|
|
85
|
+
* South: true,
|
|
86
|
+
* East: "east-value",
|
|
87
|
+
* });
|
|
88
|
+
*
|
|
89
|
+
* const entries = enums.entries(Direction);
|
|
90
|
+
* // [["North", "North"], ["South", "South"], ["East", "east-value"]]
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
entries: <Definition extends Record<string, unknown>>(definition: Definition) => Array<[keyof Definition, Enums<Definition>]>;
|
|
94
|
+
};
|
|
95
|
+
export {};
|
|
96
|
+
//# sourceMappingURL=enums.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/enums/enums.ts"],"names":[],"mappings":"AAEA,KAAK,cAAc,CACjB,KAAK,SAAS,MAAM,EACpB,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACxC;KACD,GAAG,IAAI,MAAM,UAAU,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,SAAS,IAAI,GACpD,GAAG,GACH,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG;QACrB,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;KACvB;CACF,CAAC;AAqBF,QAAA,MAAM,UAAU,eAAkB,CAAC;AAUnC;;;;;;;;;;GAUG;AACH,MAAM,MAAM,KAAK,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,UAAU,CAAC,CAAC;AAE7D;;GAEG;AACH,eAAO,MAAM,KAAK;IAChB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;;eApDsB,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,cACvD,UAAU,GACrB,cAAc,CAAC,OAAO,UAAU,EAAE,UAAU,CAAC;wBAtBxC,KAAK,SAAS,MAAM,QACpB,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,SAEzC,KAAK,cACA,UAAU,KACrB,cAAc,CAAC,KAAK,EAAE,UAAU,CAAC;;IAsElC;;;;;;;;;;;;;;;;;;OAkBG;YACK,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,cACpC,UAAU,SACf,OAAO,KACb,KAAK,CAAC,UAAU,CAAC;IAepB;;;;;;;;;;;;;;;;;OAiBG;cACO,UAAU,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,cACtC,UAAU,KACrB,KAAK,CAAC,CAAC,MAAM,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;CAKhD,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { stringify } from "../lib/string.js";
|
|
2
|
+
const defineBrandedEnums = (brand, definition) => {
|
|
3
|
+
const enumsDefinition = Object.fromEntries(Object.entries(definition).map(([key, value]) => [
|
|
4
|
+
key,
|
|
5
|
+
value === true ? key : value,
|
|
6
|
+
]));
|
|
7
|
+
Object.freeze(enumsDefinition);
|
|
8
|
+
return enumsDefinition;
|
|
9
|
+
};
|
|
10
|
+
const enumsBrand = Symbol("enums");
|
|
11
|
+
const defineEnums = (definition) => {
|
|
12
|
+
return defineBrandedEnums(enumsBrand, definition);
|
|
13
|
+
};
|
|
14
|
+
defineEnums.branded = defineBrandedEnums;
|
|
15
|
+
/**
|
|
16
|
+
* Utilities for creating type-safe enums with branded types to prevent mixing different enums.
|
|
17
|
+
*/
|
|
18
|
+
export const enums = {
|
|
19
|
+
/**
|
|
20
|
+
* Define a type-safe enum from an object definition.
|
|
21
|
+
*
|
|
22
|
+
* When a property value is `true`, the property key becomes the enum value.
|
|
23
|
+
* Otherwise, the provided value is used as the enum value.
|
|
24
|
+
*
|
|
25
|
+
* @param definition - Object defining the enum keys and values
|
|
26
|
+
* @returns A frozen enum object with type-safe values
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* // Using property keys as values
|
|
31
|
+
* const Direction = enums.define({
|
|
32
|
+
* North: true,
|
|
33
|
+
* South: true,
|
|
34
|
+
* East: true,
|
|
35
|
+
* West: true
|
|
36
|
+
* });
|
|
37
|
+
* console.log(Direction.North); // "North"
|
|
38
|
+
*
|
|
39
|
+
* // Using custom values
|
|
40
|
+
* const Status = enums.define({
|
|
41
|
+
* Inactive: 0,
|
|
42
|
+
* Pending: 1,
|
|
43
|
+
* Active: 2,
|
|
44
|
+
* });
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
define: defineEnums,
|
|
48
|
+
/**
|
|
49
|
+
* Parse a value to ensure it's a valid enum value.
|
|
50
|
+
*
|
|
51
|
+
* @param definition - The enum object created with enums.define
|
|
52
|
+
* @param value - The value to parse
|
|
53
|
+
* @returns The value typed as the enum type
|
|
54
|
+
* @throws {TypeError} If the value is not a valid enum value
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* const Direction = enums.define({
|
|
59
|
+
* North: true,
|
|
60
|
+
* South: true
|
|
61
|
+
* });
|
|
62
|
+
*
|
|
63
|
+
* const dir = enums.parse(Direction, "North"); // Returns Direction.North
|
|
64
|
+
* const invalid = enums.parse(Direction, "West"); // Throws TypeError
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
parse: (definition, value) => {
|
|
68
|
+
const enumValues = Object.values(definition);
|
|
69
|
+
if (enumValues.includes(value)) {
|
|
70
|
+
return value;
|
|
71
|
+
}
|
|
72
|
+
throw new TypeError(`Invalid enum value ${stringify(value)}. Expected one of: ${enumValues.map((value) => stringify(value)).join(", ")}`, {
|
|
73
|
+
cause: { enum: definition, value },
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
/**
|
|
77
|
+
* Get an array of [key, value] tuples from an enum definition.
|
|
78
|
+
*
|
|
79
|
+
* @param definition - The enum object created with enums.define
|
|
80
|
+
* @returns Array of [key, value] tuples
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const Direction = enums.define({
|
|
85
|
+
* North: true,
|
|
86
|
+
* South: true,
|
|
87
|
+
* East: "east-value",
|
|
88
|
+
* });
|
|
89
|
+
*
|
|
90
|
+
* const entries = enums.entries(Direction);
|
|
91
|
+
* // [["North", "North"], ["South", "South"], ["East", "east-value"]]
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
entries: (definition) => {
|
|
95
|
+
return Object.entries(definition);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=enums.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../../src/enums/enums.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAa7C,MAAM,kBAAkB,GAAG,CAIzB,KAAY,EACZ,UAAsB,EACa,EAAE;IACrC,MAAM,eAAe,GAAG,MAAM,CAAC,WAAW,CACxC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;QAC/C,GAAG;QACH,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK;KAC7B,CAAC,CACkC,CAAC;IAEvC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;IAE/B,OAAO,eAAe,CAAC;AACzB,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;AAEnC,MAAM,WAAW,GAAG,CAClB,UAAsB,EACyB,EAAE;IACjD,OAAO,kBAAkB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AACpD,CAAC,CAAC;AAEF,WAAW,CAAC,OAAO,GAAG,kBAAkB,CAAC;AAezC;;GAEG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,MAAM,EAAE,WAAW;IAEnB;;;;;;;;;;;;;;;;;;OAkBG;IACH,KAAK,EAAE,CACL,UAAsB,EACtB,KAAc,EACK,EAAE;QACrB,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QAE7C,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,OAAO,KAA0B,CAAC;QACpC,CAAC;QAED,MAAM,IAAI,SAAS,CACjB,sBAAsB,SAAS,CAAC,KAAK,CAAC,sBAAsB,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EACpH;YACE,KAAK,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE;SACnC,CACF,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,EAAE,CACP,UAAsB,EACwB,EAAE;QAChD,OAAO,MAAM,CAAC,OAAO,CAAC,UAAU,CAE/B,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import { stringify } from \"../lib/string.ts\";\n\ntype EnumDefinition<\n Brand extends symbol,\n Definition extends Record<string, unknown>,\n> = {\n [Key in keyof Definition]: (Definition[Key] extends true\n ? Key\n : Definition[Key]) & {\n readonly brand: Brand;\n };\n};\n\nconst defineBrandedEnums = <\n const Brand extends symbol,\n const Definition extends Record<string, unknown>,\n>(\n brand: Brand,\n definition: Definition,\n): EnumDefinition<Brand, Definition> => {\n const enumsDefinition = Object.fromEntries(\n Object.entries(definition).map(([key, value]) => [\n key,\n value === true ? key : value,\n ]),\n ) as EnumDefinition<Brand, Definition>;\n\n Object.freeze(enumsDefinition);\n\n return enumsDefinition;\n};\n\nconst enumsBrand = Symbol(\"enums\");\n\nconst defineEnums = <const Definition extends Record<string, unknown>>(\n definition: Definition,\n): EnumDefinition<typeof enumsBrand, Definition> => {\n return defineBrandedEnums(enumsBrand, definition);\n};\n\ndefineEnums.branded = defineBrandedEnums;\n\n/**\n * Extract the union type of all enum values from an enum definition.\n *\n * @template Definition - The enum definition object type\n *\n * @example\n * ```ts\n * const Color = enums.define({ Red: true, Green: true, Blue: true });\n * type Color = Enums<typeof Color>; // \"Red\" | \"Green\" | \"Blue\"\n * ```\n */\nexport type Enums<Definition> = Definition[keyof Definition];\n\n/**\n * Utilities for creating type-safe enums with branded types to prevent mixing different enums.\n */\nexport const enums = {\n /**\n * Define a type-safe enum from an object definition.\n *\n * When a property value is `true`, the property key becomes the enum value.\n * Otherwise, the provided value is used as the enum value.\n *\n * @param definition - Object defining the enum keys and values\n * @returns A frozen enum object with type-safe values\n *\n * @example\n * ```ts\n * // Using property keys as values\n * const Direction = enums.define({\n * North: true,\n * South: true,\n * East: true,\n * West: true\n * });\n * console.log(Direction.North); // \"North\"\n *\n * // Using custom values\n * const Status = enums.define({\n * Inactive: 0,\n * Pending: 1,\n * Active: 2,\n * });\n * ```\n */\n define: defineEnums,\n\n /**\n * Parse a value to ensure it's a valid enum value.\n *\n * @param definition - The enum object created with enums.define\n * @param value - The value to parse\n * @returns The value typed as the enum type\n * @throws {TypeError} If the value is not a valid enum value\n *\n * @example\n * ```ts\n * const Direction = enums.define({\n * North: true,\n * South: true\n * });\n *\n * const dir = enums.parse(Direction, \"North\"); // Returns Direction.North\n * const invalid = enums.parse(Direction, \"West\"); // Throws TypeError\n * ```\n */\n parse: <Definition extends Record<string, unknown>>(\n definition: Definition,\n value: unknown,\n ): Enums<Definition> => {\n const enumValues = Object.values(definition);\n\n if (enumValues.includes(value)) {\n return value as Enums<Definition>;\n }\n\n throw new TypeError(\n `Invalid enum value ${stringify(value)}. Expected one of: ${enumValues.map((value) => stringify(value)).join(\", \")}`,\n {\n cause: { enum: definition, value },\n },\n );\n },\n\n /**\n * Get an array of [key, value] tuples from an enum definition.\n *\n * @param definition - The enum object created with enums.define\n * @returns Array of [key, value] tuples\n *\n * @example\n * ```ts\n * const Direction = enums.define({\n * North: true,\n * South: true,\n * East: \"east-value\",\n * });\n *\n * const entries = enums.entries(Direction);\n * // [[\"North\", \"North\"], [\"South\", \"South\"], [\"East\", \"east-value\"]]\n * ```\n */\n entries: <Definition extends Record<string, unknown>>(\n definition: Definition,\n ): Array<[keyof Definition, Enums<Definition>]> => {\n return Object.entries(definition) as Array<\n [keyof Definition, Enums<Definition>]\n >;\n },\n};\n"]}
|
package/dist/main.d.ts
CHANGED
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC"}
|
package/dist/main.js
CHANGED
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC","sourcesContent":["export * from \"./assert/assert.ts\";\n"]}
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC","sourcesContent":["export * from \"./assert/assert.ts\";\nexport * from \"./dedupe/dedupe.ts\";\nexport * from \"./enums/enums.ts\";\nexport * from \"./match/match.ts\";\nexport * from \"./need/need.ts\";\n"]}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
declare const defaultSymbol: unique symbol;
|
|
2
|
+
declare const catchSymbol: unique symbol;
|
|
3
|
+
/**
|
|
4
|
+
* Match the given `value` against `cases` and return the matching `Result`.
|
|
5
|
+
*
|
|
6
|
+
* This function provides exhaustive pattern matching with TypeScript, ensuring all cases
|
|
7
|
+
* are handled at compile time. It works similarly to a regular `switch`/`case` statement,
|
|
8
|
+
* but has a decisive advantage: TypeScript issues a type error if not all cases have been
|
|
9
|
+
* implemented. An error is also thrown at runtime if a value doesn't match any case.
|
|
10
|
+
*
|
|
11
|
+
* ## Usage:
|
|
12
|
+
*
|
|
13
|
+
* Match uses tuple-based matching, ideal for enums or any type of values:
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const Direction = enums.define({ Up: "North", Down: "South" });
|
|
17
|
+
* type Direction = Enums<typeof Direction>;
|
|
18
|
+
* match(Direction.Up).case([
|
|
19
|
+
* [Direction.Up, "going up"],
|
|
20
|
+
* [Direction.Down, "going down"],
|
|
21
|
+
* ]); // "going up"
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* ## Special symbols:
|
|
25
|
+
*
|
|
26
|
+
* `match.default` - Provides a default case. When used, TypeScript no longer requires
|
|
27
|
+
* all cases to be specified, allowing partial matches with a fallback.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* match<"A" | "B">("A").case([
|
|
32
|
+
* ["B", "result B"],
|
|
33
|
+
* [match.default, "default result"],
|
|
34
|
+
* ]); // "default result"
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* `match.catch` - Handles unexpected runtime values while still requiring all compile-time
|
|
38
|
+
* cases to be specified. Use this when you need type safety but want to handle edge cases
|
|
39
|
+
* at runtime without throwing errors.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* match("C" as "A" | "B").case([
|
|
44
|
+
* ["A", "result A"],
|
|
45
|
+
* ["B", "result B"],
|
|
46
|
+
* [match.catch, "unknown value"],
|
|
47
|
+
* ]); // "unknown value"
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
declare function match<const Value>(value: Value): {
|
|
51
|
+
case: {
|
|
52
|
+
<const Result>(cases: Cases<readonly [Value, Result], readonly [typeof defaultSymbol, Result]>): Result;
|
|
53
|
+
<const Result, const GivenCases extends Cases<readonly [Value, Result], readonly [typeof catchSymbol | Value, Result]>>(cases: If<IsExact<Exclude<ExtractValueFromCases<GivenCases>, typeof catchSymbol>, Value>, {
|
|
54
|
+
then: GivenCases;
|
|
55
|
+
else: never;
|
|
56
|
+
}>): Result;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
declare namespace match {
|
|
60
|
+
var _a: typeof defaultSymbol;
|
|
61
|
+
var _b: typeof catchSymbol;
|
|
62
|
+
export { _a as default, _b as catch };
|
|
63
|
+
}
|
|
64
|
+
type Cases<Tuple extends readonly [any, any] = readonly [any, any], LastTuple extends readonly [any, any] = Tuple> = readonly [...ReadonlyArray<Tuple>, LastTuple];
|
|
65
|
+
type If<ValueToTest, Body extends {
|
|
66
|
+
then: any;
|
|
67
|
+
else: any;
|
|
68
|
+
}> = ValueToTest extends true ? Body["then"] : Body["else"];
|
|
69
|
+
type IsExact<Type1, Type2> = [Type1] extends [Type2] ? [Type2] extends [Type1] ? true : false : false;
|
|
70
|
+
type ExtractValueFromCases<GivenCases extends Cases> = GivenCases[number][0];
|
|
71
|
+
export { match };
|
|
72
|
+
//# sourceMappingURL=match.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.d.ts","sourceRoot":"","sources":["../../src/match/match.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,aAAa,eAA0B,CAAC;AAC9C,QAAA,MAAM,WAAW,eAAwB,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,iBAAS,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG;IACzC,IAAI,EAAE;QACJ,CAAC,KAAK,CAAC,MAAM,EACX,KAAK,EAAE,KAAK,CACV,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,EACxB,SAAS,CAAC,OAAO,aAAa,EAAE,MAAM,CAAC,CACxC,GACA,MAAM,CAAC;QACV,CACE,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,UAAU,SAAS,KAAK,CAC5B,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,EACxB,SAAS,CAAC,OAAO,WAAW,GAAG,KAAK,EAAE,MAAM,CAAC,CAC9C,EAED,KAAK,EAAE,EAAE,CACP,OAAO,CACL,OAAO,CAAC,qBAAqB,CAAC,UAAU,CAAC,EAAE,OAAO,WAAW,CAAC,EAC9D,KAAK,CACN,EACD;YACE,IAAI,EAAE,UAAU,CAAC;YACjB,IAAI,EAAE,KAAK,CAAC;SACb,CACF,GACA,MAAM,CAAC;KACX,CAAC;CACH,CAAC;kBA3BO,KAAK;;;;;AA6Dd,KAAK,KAAK,CACR,KAAK,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,EACvD,SAAS,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,KAAK,IAC3C,SAAS,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;AAElD,KAAK,EAAE,CACL,WAAW,EACX,IAAI,SAAS;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,GAAG,CAAA;CAAE,IACnC,WAAW,SAAS,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;AAE3D,KAAK,OAAO,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,GAChD,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,GACrB,IAAI,GACJ,KAAK,GACP,KAAK,CAAC;AAEV,KAAK,qBAAqB,CAAC,UAAU,SAAS,KAAK,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAK7E,OAAO,EAAE,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* eslint-disable prefer-arrow/prefer-arrow-functions */
|
|
2
|
+
import { stringify } from "../lib/string.js";
|
|
3
|
+
import { need } from "./../need/need.js";
|
|
4
|
+
const defaultSymbol = Symbol("match.default");
|
|
5
|
+
const catchSymbol = Symbol("match.catch");
|
|
6
|
+
function match(value) {
|
|
7
|
+
return {
|
|
8
|
+
case: (cases) => {
|
|
9
|
+
for (let i = 0; i < cases.length - 1; i++) {
|
|
10
|
+
const [valueInCase, resultInCase] = cases[i];
|
|
11
|
+
if (Object.is(value, valueInCase)) {
|
|
12
|
+
return resultInCase;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const [valueInLastCase, resultInLastCase] = need(cases.at(-1));
|
|
16
|
+
if (Object.is(value, valueInLastCase) ||
|
|
17
|
+
valueInLastCase === defaultSymbol ||
|
|
18
|
+
valueInLastCase === catchSymbol) {
|
|
19
|
+
return resultInLastCase;
|
|
20
|
+
}
|
|
21
|
+
throw new Error(`No match found for ${stringify(value)}. Expected one of: ${cases.map(([possibleValue]) => stringify(possibleValue)).join(", ")}`);
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
match.default = defaultSymbol;
|
|
26
|
+
match.catch = catchSymbol;
|
|
27
|
+
export { match };
|
|
28
|
+
//# sourceMappingURL=match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"match.js","sourceRoot":"","sources":["../../src/match/match.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAExD,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,MAAM,aAAa,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC;AAC9C,MAAM,WAAW,GAAG,MAAM,CAAC,aAAa,CAAC,CAAC;AA6E1C,SAAS,KAAK,CAAc,KAAY;IACtC,OAAO;QACL,IAAI,EAAE,CACJ,KAGC,EACO,EAAE;YACV,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;gBAC1C,MAAM,CAAC,WAAW,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;gBAE9C,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,EAAE,CAAC;oBAClC,OAAO,YAAY,CAAC;gBACtB,CAAC;YACH,CAAC;YAED,MAAM,CAAC,eAAe,EAAE,gBAAgB,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAE/D,IACE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,CAAC;gBACjC,eAAe,KAAK,aAAa;gBACjC,eAAe,KAAK,WAAW,EAC/B,CAAC;gBACD,OAAO,gBAAgB,CAAC;YAC1B,CAAC;YAED,MAAM,IAAI,KAAK,CACb,sBAAsB,SAAS,CAAC,KAAK,CAAC,sBAAsB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAClI,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAoBD,KAAK,CAAC,OAAO,GAAG,aAAa,CAAC;AAC9B,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC;AAE1B,OAAO,EAAE,KAAK,EAAE,CAAC","sourcesContent":["/* eslint-disable prefer-arrow/prefer-arrow-functions */\n\nimport { stringify } from \"../lib/string.ts\";\nimport { need } from \"./../need/need.ts\";\n\nconst defaultSymbol = Symbol(\"match.default\");\nconst catchSymbol = Symbol(\"match.catch\");\n\n/**\n * Match the given `value` against `cases` and return the matching `Result`.\n *\n * This function provides exhaustive pattern matching with TypeScript, ensuring all cases\n * are handled at compile time. It works similarly to a regular `switch`/`case` statement,\n * but has a decisive advantage: TypeScript issues a type error if not all cases have been\n * implemented. An error is also thrown at runtime if a value doesn't match any case.\n *\n * ## Usage:\n *\n * Match uses tuple-based matching, ideal for enums or any type of values:\n * @example\n * ```ts\n * const Direction = enums.define({ Up: \"North\", Down: \"South\" });\n * type Direction = Enums<typeof Direction>;\n * match(Direction.Up).case([\n * [Direction.Up, \"going up\"],\n * [Direction.Down, \"going down\"],\n * ]); // \"going up\"\n * ```\n *\n * ## Special symbols:\n *\n * `match.default` - Provides a default case. When used, TypeScript no longer requires\n * all cases to be specified, allowing partial matches with a fallback.\n *\n * @example\n * ```ts\n * match<\"A\" | \"B\">(\"A\").case([\n * [\"B\", \"result B\"],\n * [match.default, \"default result\"],\n * ]); // \"default result\"\n * ```\n *\n * `match.catch` - Handles unexpected runtime values while still requiring all compile-time\n * cases to be specified. Use this when you need type safety but want to handle edge cases\n * at runtime without throwing errors.\n *\n * @example\n * ```ts\n * match(\"C\" as \"A\" | \"B\").case([\n * [\"A\", \"result A\"],\n * [\"B\", \"result B\"],\n * [match.catch, \"unknown value\"],\n * ]); // \"unknown value\"\n * ```\n */\nfunction match<const Value>(value: Value): {\n case: {\n <const Result>(\n cases: Cases<\n readonly [Value, Result],\n readonly [typeof defaultSymbol, Result]\n >,\n ): Result;\n <\n const Result,\n const GivenCases extends Cases<\n readonly [Value, Result],\n readonly [typeof catchSymbol | Value, Result]\n >,\n >(\n cases: If<\n IsExact<\n Exclude<ExtractValueFromCases<GivenCases>, typeof catchSymbol>,\n Value\n >,\n {\n then: GivenCases;\n else: never;\n }\n >,\n ): Result;\n };\n};\nfunction match<const Value>(value: Value): any {\n return {\n case: <const Result>(\n cases: Cases<\n readonly [Value, Result],\n readonly [typeof defaultSymbol | typeof catchSymbol | Value, Result]\n >,\n ): Result => {\n for (let i = 0; i < cases.length - 1; i++) {\n const [valueInCase, resultInCase] = cases[i]!;\n\n if (Object.is(value, valueInCase)) {\n return resultInCase;\n }\n }\n\n const [valueInLastCase, resultInLastCase] = need(cases.at(-1));\n\n if (\n Object.is(value, valueInLastCase) ||\n valueInLastCase === defaultSymbol ||\n valueInLastCase === catchSymbol\n ) {\n return resultInLastCase;\n }\n\n throw new Error(\n `No match found for ${stringify(value)}. Expected one of: ${cases.map(([possibleValue]) => stringify(possibleValue)).join(\", \")}`,\n );\n },\n };\n}\n\ntype Cases<\n Tuple extends readonly [any, any] = readonly [any, any],\n LastTuple extends readonly [any, any] = Tuple,\n> = readonly [...ReadonlyArray<Tuple>, LastTuple];\n\ntype If<\n ValueToTest,\n Body extends { then: any; else: any },\n> = ValueToTest extends true ? Body[\"then\"] : Body[\"else\"];\n\ntype IsExact<Type1, Type2> = [Type1] extends [Type2]\n ? [Type2] extends [Type1]\n ? true\n : false\n : false;\n\ntype ExtractValueFromCases<GivenCases extends Cases> = GivenCases[number][0];\n\nmatch.default = defaultSymbol;\nmatch.catch = catchSymbol;\n\nexport { match };\n"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ErrorMessage } from "../lib/error-message.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Assert that a given value is not `null` or `undefined`, and narrow its type.
|
|
4
|
+
*
|
|
5
|
+
* Like `assert`, this function only handles nullish values (`null`, `undefined`)
|
|
6
|
+
* and does not reject other falsy values. Unlike `assert`, it returns the value
|
|
7
|
+
* so it can be used in expressions.
|
|
8
|
+
*
|
|
9
|
+
* @param value - The value to check for nullish values
|
|
10
|
+
* @param errorMessage - Custom error message or function that returns an error message
|
|
11
|
+
* @returns The value with nullish types removed from its type signature
|
|
12
|
+
* @throws {TypeError} When value is `null` or `undefined`
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const maybeString: string | null = getValue();
|
|
17
|
+
* const definiteString = need(maybeString); // throws if null
|
|
18
|
+
* // TypeScript now knows definiteString is string, not string | null
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare const need: <Value>(value: Value, errorMessage?: ErrorMessage) => NonNullable<Value>;
|
|
22
|
+
//# sourceMappingURL=need.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"need.d.ts","sourceRoot":"","sources":["../../src/need/need.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG7E;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,EACxB,OAAO,KAAK,EACZ,eAAe,YAAY,KAC1B,WAAW,CAAC,KAAK,CAUnB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getErrorMessage } from "../lib/error-message.js";
|
|
2
|
+
import { simpleStringify } from "../lib/string.js";
|
|
3
|
+
/**
|
|
4
|
+
* Assert that a given value is not `null` or `undefined`, and narrow its type.
|
|
5
|
+
*
|
|
6
|
+
* Like `assert`, this function only handles nullish values (`null`, `undefined`)
|
|
7
|
+
* and does not reject other falsy values. Unlike `assert`, it returns the value
|
|
8
|
+
* so it can be used in expressions.
|
|
9
|
+
*
|
|
10
|
+
* @param value - The value to check for nullish values
|
|
11
|
+
* @param errorMessage - Custom error message or function that returns an error message
|
|
12
|
+
* @returns The value with nullish types removed from its type signature
|
|
13
|
+
* @throws {TypeError} When value is `null` or `undefined`
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* const maybeString: string | null = getValue();
|
|
18
|
+
* const definiteString = need(maybeString); // throws if null
|
|
19
|
+
* // TypeScript now knows definiteString is string, not string | null
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const need = (value, errorMessage) => {
|
|
23
|
+
if (value === undefined || value === null) {
|
|
24
|
+
throw new TypeError(getErrorMessage(errorMessage, () => `Expected value to be defined, but got ${simpleStringify(value)}`));
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=need.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"need.js","sourceRoot":"","sources":["../../src/need/need.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAqB,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,CAClB,KAAY,EACZ,YAA2B,EACP,EAAE;IACtB,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1C,MAAM,IAAI,SAAS,CACjB,eAAe,CACb,YAAY,EACZ,GAAG,EAAE,CAAC,yCAAyC,eAAe,CAAC,KAAK,CAAC,EAAE,CACxE,CACF,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC","sourcesContent":["import { getErrorMessage, type ErrorMessage } from \"../lib/error-message.ts\";\nimport { simpleStringify } from \"../lib/string.ts\";\n\n/**\n * Assert that a given value is not `null` or `undefined`, and narrow its type.\n *\n * Like `assert`, this function only handles nullish values (`null`, `undefined`)\n * and does not reject other falsy values. Unlike `assert`, it returns the value\n * so it can be used in expressions.\n *\n * @param value - The value to check for nullish values\n * @param errorMessage - Custom error message or function that returns an error message\n * @returns The value with nullish types removed from its type signature\n * @throws {TypeError} When value is `null` or `undefined`\n *\n * @example\n * ```ts\n * const maybeString: string | null = getValue();\n * const definiteString = need(maybeString); // throws if null\n * // TypeScript now knows definiteString is string, not string | null\n * ```\n */\nexport const need = <Value>(\n value: Value,\n errorMessage?: ErrorMessage,\n): NonNullable<Value> => {\n if (value === undefined || value === null) {\n throw new TypeError(\n getErrorMessage(\n errorMessage,\n () => `Expected value to be defined, but got ${simpleStringify(value)}`,\n ),\n );\n }\n return value;\n};\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@peerigon/typescript-toolkit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "🔧✨ Tiny helpers for TypeScript applications",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/peerigon/typescript-toolkit#readme",
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"type": "module",
|
|
18
18
|
"exports": {
|
|
19
19
|
".": "./dist/main.js",
|
|
20
|
-
"./
|
|
20
|
+
"./assert": "./dist/assert/assert.js",
|
|
21
|
+
"./dedupe": "./dist/dedupe/dedupe.js",
|
|
22
|
+
"./enums": "./dist/enums/enums.js",
|
|
23
|
+
"./match": "./dist/match/match.js",
|
|
24
|
+
"./need": "./dist/need/need.js"
|
|
21
25
|
},
|
|
22
26
|
"main": "./dist/main.js",
|
|
23
27
|
"files": [
|