@peerigon/typescript-toolkit 1.1.0 → 2.1.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.
Files changed (42) hide show
  1. package/README.md +7 -1
  2. package/dist/assert/assert.d.ts +6 -7
  3. package/dist/assert/assert.d.ts.map +1 -1
  4. package/dist/assert/assert.js +7 -8
  5. package/dist/assert/assert.js.map +1 -1
  6. package/dist/dedupe/dedupe.d.ts +17 -0
  7. package/dist/dedupe/dedupe.d.ts.map +1 -0
  8. package/dist/dedupe/dedupe.js +19 -0
  9. package/dist/dedupe/dedupe.js.map +1 -0
  10. package/dist/enums/enums.d.ts +96 -0
  11. package/dist/enums/enums.d.ts.map +1 -0
  12. package/dist/enums/enums.js +98 -0
  13. package/dist/enums/enums.js.map +1 -0
  14. package/dist/lib/create-prototype.d.ts +2 -0
  15. package/dist/lib/create-prototype.d.ts.map +1 -0
  16. package/dist/lib/create-prototype.js +13 -0
  17. package/dist/lib/create-prototype.js.map +1 -0
  18. package/dist/main.d.ts +4 -0
  19. package/dist/main.d.ts.map +1 -1
  20. package/dist/main.js +4 -0
  21. package/dist/main.js.map +1 -1
  22. package/dist/match/match.d.ts +72 -0
  23. package/dist/match/match.d.ts.map +1 -0
  24. package/dist/match/match.js +28 -0
  25. package/dist/match/match.js.map +1 -0
  26. package/dist/need/need.d.ts +22 -0
  27. package/dist/need/need.d.ts.map +1 -0
  28. package/dist/need/need.js +28 -0
  29. package/dist/need/need.js.map +1 -0
  30. package/dist/result/result.d.ts +159 -0
  31. package/dist/result/result.d.ts.map +1 -0
  32. package/dist/result/result.js +192 -0
  33. package/dist/result/result.js.map +1 -0
  34. package/dist/result/result.lib.d.ts +10 -0
  35. package/dist/result/result.lib.d.ts.map +1 -0
  36. package/dist/result/result.lib.js +13 -0
  37. package/dist/result/result.lib.js.map +1 -0
  38. package/dist/unwrap/unwrap.d.ts +31 -0
  39. package/dist/unwrap/unwrap.d.ts.map +1 -0
  40. package/dist/unwrap/unwrap.js +39 -0
  41. package/dist/unwrap/unwrap.js.map +1 -0
  42. package/package.json +9 -2
package/README.md CHANGED
@@ -39,7 +39,13 @@ 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`, `undefined`, or `false`, with TypeScript narrowing | [→](./src/assert/README.md) |
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) |
47
+ | [`result`](./src/result/README.md) | Type-safe error handling with pending, success, and error states | [→](./src/result/README.md) |
48
+ | [`unwrap`](./src/unwrap/README.md) | Extract values from `Result` or nullable types, with optional fallback support | [→](./src/unwrap/README.md) |
43
49
 
44
50
  ## License
45
51
 
@@ -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`, `undefined`, or `false`,
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`, `undefined`,
7
- * and `false` while allowing other falsy values like `0`, `""`, and `NaN` to pass through.
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`, `undefined`, or `false`.
10
- * @param errorMessage - The error message to throw if the value is `null`, `undefined`, or `false`.
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> & Exclude<Value, false>;
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;;;;;;;;;GASG;AACH,eAAO,MAAM,MAAM;KAAI,KAAK,SACnB,KAAK,iBACG,YAAY,GAC1B,QAAQ,KAAK,IAAI,WAAW,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;kBAiBrD,OAAO,iBACC,YAAY,GAC1B,QAAQ,KAAK;CAff,CAAC"}
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"}
@@ -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`, `undefined`, or `false`,
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`, `undefined`,
8
- * and `false` while allowing other falsy values like `0`, `""`, and `NaN` to pass through.
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`, `undefined`, or `false`.
11
- * @param errorMessage - The error message to throw if the value is `null`, `undefined`, or `false`.
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 || value === false) {
15
- throwTypeError(value, errorMessage, "neither null, undefined, nor false");
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;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CACpB,KAAY,EACZ,YAA2B,EACkC,EAAE;IAC/D,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC7D,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,oCAAoC,CAAC,CAAC;IAC5E,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`, `undefined`, or `false`,\n * and narrows its type.\n *\n * Unlike regular truthiness checks, `assert` only rejects `null`, `undefined`,\n * and `false` while allowing other falsy values like `0`, `\"\"`, and `NaN` to pass through.\n *\n * @param value - The value that shouldn't be `null`, `undefined`, or `false`.\n * @param errorMessage - The error message to throw if the value is `null`, `undefined`, or `false`.\n */\nexport const assert = <Value>(\n value: Value,\n errorMessage?: ErrorMessage,\n): asserts value is NonNullable<Value> & Exclude<Value, false> => {\n if (value === undefined || value === null || value === false) {\n throwTypeError(value, errorMessage, \"neither null, undefined, nor false\");\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"]}
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"]}
@@ -0,0 +1,2 @@
1
+ export declare const createPrototype: <EnumerableProperties extends Record<string, unknown>, NonEnumerableProperties extends Record<string, unknown>>(enumerableProperties: EnumerableProperties, nonEnumerableProperties: NonEnumerableProperties) => EnumerableProperties & NonEnumerableProperties;
2
+ //# sourceMappingURL=create-prototype.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-prototype.d.ts","sourceRoot":"","sources":["../../src/lib/create-prototype.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,eAAe,GAC1B,oBAAoB,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpD,uBAAuB,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEvD,sBAAsB,oBAAoB,EAC1C,yBAAyB,uBAAuB,KAc3C,oBAAoB,GAAG,uBAC7B,CAAC"}
@@ -0,0 +1,13 @@
1
+ export const createPrototype = (enumerableProperties, nonEnumerableProperties) => {
2
+ return Object.create(Object.prototype, Object.fromEntries([
3
+ ...Object.entries(enumerableProperties).map(([key, value]) => [
4
+ key,
5
+ { value, enumerable: true },
6
+ ]),
7
+ ...Object.entries(nonEnumerableProperties).map(([key, value]) => [
8
+ key,
9
+ { value, enumerable: false },
10
+ ]),
11
+ ]));
12
+ };
13
+ //# sourceMappingURL=create-prototype.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-prototype.js","sourceRoot":"","sources":["../../src/lib/create-prototype.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,CAI7B,oBAA0C,EAC1C,uBAAgD,EAChD,EAAE;IACF,OAAO,MAAM,CAAC,MAAM,CAClB,MAAM,CAAC,SAAS,EAChB,MAAM,CAAC,WAAW,CAAC;QACjB,GAAG,MAAM,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YAC5D,GAAG;YACH,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE;SAC5B,CAAC;QACF,GAAG,MAAM,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YAC/D,GAAG;YACH,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE;SAC7B,CAAC;KACH,CAA2D,CACX,CAAC;AACtD,CAAC,CAAC","sourcesContent":["export const createPrototype = <\n EnumerableProperties extends Record<string, unknown>,\n NonEnumerableProperties extends Record<string, unknown>,\n>(\n enumerableProperties: EnumerableProperties,\n nonEnumerableProperties: NonEnumerableProperties,\n) => {\n return Object.create(\n Object.prototype,\n Object.fromEntries([\n ...Object.entries(enumerableProperties).map(([key, value]) => [\n key,\n { value, enumerable: true },\n ]),\n ...Object.entries(nonEnumerableProperties).map(([key, value]) => [\n key,\n { value, enumerable: false },\n ]),\n ]) as Record<keyof EnumerableProperties, PropertyDescriptor>,\n ) as EnumerableProperties & NonEnumerableProperties;\n};\n"]}
package/dist/main.d.ts CHANGED
@@ -1,2 +1,6 @@
1
1
  export * from "./assert/assert.ts";
2
+ export * from "./dedupe/dedupe.ts";
3
+ export * from "./enums/enums.ts";
4
+ export * from "./match/match.ts";
5
+ export * from "./need/need.ts";
2
6
  //# sourceMappingURL=main.d.ts.map
@@ -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
@@ -1,2 +1,6 @@
1
1
  export * from "./assert/assert.js";
2
+ export * from "./dedupe/dedupe.js";
3
+ export * from "./enums/enums.js";
4
+ export * from "./match/match.js";
5
+ export * from "./need/need.js";
2
6
  //# sourceMappingURL=main.js.map
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"]}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Represents some async data that could be in the following states:
3
+ *
4
+ * - initial (not implemented, see below)
5
+ * - pending
6
+ * - success
7
+ * - error
8
+ *
9
+ * This type is inspired by https://rametta.org/posts/elm-remote-data/ and has been
10
+ * designed to be aligned with tanstack query's useQuery() result.
11
+ * For instance, it can be used by a React component that wants to handle asynchronous
12
+ * data but does not want to call useQuery directly.
13
+ *
14
+ * The initial state has not been implemented because it's not compatible
15
+ * with tanstack query. If the initial state is needed,
16
+ * it can be represented as Result | undefined
17
+ */
18
+ export type Result<Data = unknown, GivenError extends GenericError = GenericError> = Result.Pending<Data | undefined> | Result.Success<Data> | Result.Error<GivenError, Data | undefined>;
19
+ export declare const Result: {
20
+ readonly Status: {
21
+ readonly Pending: "pending";
22
+ readonly Success: "success";
23
+ readonly Error: "error";
24
+ };
25
+ };
26
+ export declare namespace Result {
27
+ namespace Status {
28
+ type Pending = typeof Result.Status.Pending;
29
+ type Success = typeof Result.Status.Success;
30
+ type Error = typeof Result.Status.Error;
31
+ }
32
+ type Status = Status.Pending | Status.Success | Status.Error;
33
+ /**
34
+ * Represents a pending result where data is being loaded.
35
+ * Pending might have stale data, but there's new data inflight.
36
+ */
37
+ type Pending<Data = undefined> = Readonly<{
38
+ status: Status.Pending;
39
+ /** Is true when there's data */
40
+ isSuccess: false;
41
+ /** Is true when there's an error */
42
+ isError: false;
43
+ /** Is true when there's no result yet */
44
+ isPending: true;
45
+ /** Potentially stale data from a previous result */
46
+ data: Data;
47
+ error: null;
48
+ }>;
49
+ /**
50
+ * Represents a successful result that holds some data.
51
+ */
52
+ type Success<Data = unknown> = Readonly<{
53
+ status: Status.Success;
54
+ /** Is true when there's data */
55
+ isSuccess: true;
56
+ /** Is true when there's an error */
57
+ isError: false;
58
+ /** Is true when there's no result yet */
59
+ isPending: false;
60
+ data: Data;
61
+ error: null;
62
+ }>;
63
+ /**
64
+ * Represents a failed result that holds an error.
65
+ * Might also contain stale data from a previous result.
66
+ */
67
+ type Error<GivenError extends GenericError = GenericError, Data = undefined> = Readonly<{
68
+ status: Status.Error;
69
+ /** Is true when there's data */
70
+ isSuccess: false;
71
+ /** Is true when there's an error */
72
+ isError: true;
73
+ /** Is true when there's no result yet */
74
+ isPending: false;
75
+ /** Potentially stale data from a previous result */
76
+ data: Data;
77
+ /** The error that occurred */
78
+ error: GivenError;
79
+ }>;
80
+ /**
81
+ * Represents a synchronous result that can be either success or error (no pending state).
82
+ */
83
+ type Sync<Data = unknown, GivenError extends GenericError = GenericError> = Success<Data> | Error<GivenError, Data>;
84
+ }
85
+ declare const getMetadata: <Data>(result: Result<Data>) => ResultMetadata;
86
+ /**
87
+ * Creates a result in the pending state.
88
+ *
89
+ * @param options.data - Potentially stale data from a previous result
90
+ * @param options.createdAt - The date when the result was created
91
+ * @returns The pending result
92
+ */
93
+ declare const pending: <const Data = undefined>({ data, createdAt, }?: Pick<Partial<Result.Pending<Data>>, "data"> & Partial<ResultMetadata>) => Result.Pending<Data>;
94
+ /**
95
+ * Creates a result in the success state.
96
+ *
97
+ * @param options.data - The data to store in the result
98
+ * @param options.createdAt - The date when the result was created
99
+ * @returns The successful result
100
+ */
101
+ declare const success: <const Data>({ data, createdAt, }: Pick<Result.Success<Data>, "data"> & Partial<ResultMetadata>) => Result.Success<Data>;
102
+ /**
103
+ * Creates a result in the error state.
104
+ *
105
+ * @param options.error - The error to store in the result
106
+ * @param options.data - Potentially stale data from a previous result
107
+ * @returns The failed result
108
+ */
109
+ declare const error: <const GivenError extends Error, const Data = never>({ error: givenError, data, createdAt, }: Pick<Result.Error<GivenError, Data>, "error"> & Pick<Partial<Result.Error<GivenError, Data>>, "data"> & Partial<ResultMetadata>) => Result.Error<GivenError, Data>;
110
+ /**
111
+ * Calls the function and returns it as result.
112
+ * If the function throws an Error, an error result is returned.
113
+ * If the function throws anything else, it is rethrown.
114
+ *
115
+ * @param fn - The function to call
116
+ * @returns The result of the function
117
+ */
118
+ declare const from: <Data>(fn: () => Data) => Result.Sync<Data>;
119
+ /**
120
+ * Calls and awaits the async function and returns it as result.
121
+ * If the function rejects the promise, an error result is returned.
122
+ * If the function rejects with anything else, the rejection is rethrown.
123
+ *
124
+ * @param fn - The async function to call and await
125
+ * @returns The result of the async function
126
+ */
127
+ declare const fromAsync: <Data>(fn: () => Promise<Data>) => Promise<Result.Sync<Data>>;
128
+ type CaseHandlers<GivenResult extends Result | null | undefined, ReturnType = unknown> = {
129
+ pending?: ((maybeData: Extract<GivenResult, {
130
+ status: Result.Status.Pending;
131
+ }>["data"]) => ReturnType) | ReturnType;
132
+ success?: ((data: Extract<GivenResult, {
133
+ status: Result.Status.Success;
134
+ }>["data"]) => ReturnType) | ReturnType;
135
+ error?: ((error: Extract<GivenResult, {
136
+ status: Result.Status.Error;
137
+ }>["error"]) => ReturnType) | ReturnType;
138
+ else: ((result: GivenResult) => ReturnType) | ReturnType;
139
+ };
140
+ type ResultWrapper<GivenResult extends Result | null | undefined> = {
141
+ case: <ReturnType>(handlers: CaseHandlers<GivenResult, ReturnType>) => ReturnType;
142
+ };
143
+ type ResultFn = {
144
+ <GivenResult extends Result | null | undefined>(givenResult: GivenResult): ResultWrapper<GivenResult>;
145
+ from: typeof from;
146
+ fromAsync: typeof fromAsync;
147
+ pending: typeof pending;
148
+ success: typeof success;
149
+ error: typeof error;
150
+ metadata: typeof getMetadata;
151
+ };
152
+ export declare const result: ResultFn;
153
+ export { isResult } from "./result.lib.ts";
154
+ type GenericError = Error;
155
+ type ResultMetadata = {
156
+ /** The date when the result was created */
157
+ createdAt: Date;
158
+ };
159
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/result/result.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ import { createPrototype } from "../lib/create-prototype.js";
2
+ import { stringify } from "../lib/string.js";
3
+ import { symbolOfResult } from "./result.lib.js";
4
+ export const Result = {
5
+ Status: {
6
+ Pending: "pending",
7
+ Success: "success",
8
+ Error: "error",
9
+ },
10
+ };
11
+ const setMetadata = (result, metadata) => {
12
+ Object.defineProperty(result, symbolOfResult, {
13
+ value: metadata,
14
+ enumerable: false,
15
+ });
16
+ };
17
+ const getMetadata = (result) => {
18
+ return result[symbolOfResult];
19
+ };
20
+ /**
21
+ * Creates a result in the pending state.
22
+ *
23
+ * @param options.data - Potentially stale data from a previous result
24
+ * @param options.createdAt - The date when the result was created
25
+ * @returns The pending result
26
+ */
27
+ const pending = ({ data, createdAt = new Date(), } = {}) => {
28
+ const pendingResult = Object.create(pendingPrototype, {
29
+ data: { value: data, enumerable: true },
30
+ });
31
+ setMetadata(pendingResult, { createdAt });
32
+ return pendingResult;
33
+ };
34
+ const pendingPrototype = createPrototype({
35
+ status: Result.Status.Pending,
36
+ isSuccess: false,
37
+ isError: false,
38
+ isPending: true,
39
+ data: undefined,
40
+ error: null,
41
+ }, {
42
+ toString() {
43
+ return `Result.Pending(${this.data === undefined ? "" : stringify(this.data)})`;
44
+ },
45
+ });
46
+ /**
47
+ * Creates a result in the success state.
48
+ *
49
+ * @param options.data - The data to store in the result
50
+ * @param options.createdAt - The date when the result was created
51
+ * @returns The successful result
52
+ */
53
+ const success = ({ data, createdAt = new Date(), }) => {
54
+ const successResult = Object.create(successPrototype, {
55
+ data: { value: data, enumerable: true },
56
+ });
57
+ setMetadata(successResult, { createdAt });
58
+ return successResult;
59
+ };
60
+ const successPrototype = createPrototype({
61
+ status: Result.Status.Success,
62
+ isSuccess: true,
63
+ isError: false,
64
+ isPending: false,
65
+ data: undefined,
66
+ error: null,
67
+ }, {
68
+ toString() {
69
+ return `Result.Success(${stringify(this.data)})`;
70
+ },
71
+ });
72
+ /**
73
+ * Creates a result in the error state.
74
+ *
75
+ * @param options.error - The error to store in the result
76
+ * @param options.data - Potentially stale data from a previous result
77
+ * @returns The failed result
78
+ */
79
+ const error = ({ error: givenError, data, createdAt = new Date(), }) => {
80
+ const errorResult = Object.create(errorPrototype, {
81
+ data: { value: data, enumerable: true },
82
+ error: { value: givenError, enumerable: true },
83
+ });
84
+ setMetadata(errorResult, { createdAt });
85
+ return errorResult;
86
+ };
87
+ const errorPrototype = createPrototype({
88
+ status: Result.Status.Error,
89
+ isSuccess: false,
90
+ isError: true,
91
+ isPending: false,
92
+ data: undefined,
93
+ error: new Error("Default error"),
94
+ }, {
95
+ toString() {
96
+ return `Result.Error(${stringify(this.error.message)})`;
97
+ },
98
+ });
99
+ /**
100
+ * Calls the function and returns it as result.
101
+ * If the function throws an Error, an error result is returned.
102
+ * If the function throws anything else, it is rethrown.
103
+ *
104
+ * @param fn - The function to call
105
+ * @returns The result of the function
106
+ */
107
+ const from = (fn) => {
108
+ try {
109
+ return success({ data: fn() });
110
+ }
111
+ catch (caughtError) {
112
+ if (isError(caughtError)) {
113
+ return error({ error: caughtError });
114
+ }
115
+ throw caughtError;
116
+ }
117
+ };
118
+ /**
119
+ * Calls and awaits the async function and returns it as result.
120
+ * If the function rejects the promise, an error result is returned.
121
+ * If the function rejects with anything else, the rejection is rethrown.
122
+ *
123
+ * @param fn - The async function to call and await
124
+ * @returns The result of the async function
125
+ */
126
+ const fromAsync = async (fn) => {
127
+ try {
128
+ return success({ data: await fn() });
129
+ }
130
+ catch (caughtError) {
131
+ if (isError(caughtError)) {
132
+ return error({ error: caughtError });
133
+ }
134
+ throw caughtError;
135
+ }
136
+ };
137
+ /**
138
+ * Checks if the given value is an Error. Doesn't use instanceof so that
139
+ * DOMException and errors from a different realm can be checked as well.
140
+ */
141
+ const isError = (error) => {
142
+ if ("isError" in Error && typeof Error.isError === "function") {
143
+ const result = Error.isError(error);
144
+ if (typeof result === "boolean") {
145
+ return result;
146
+ }
147
+ }
148
+ return (typeof error === "object" &&
149
+ error !== null &&
150
+ "name" in error &&
151
+ typeof error.name === "string" &&
152
+ "message" in error &&
153
+ typeof error.message === "string" &&
154
+ "stack" in error &&
155
+ typeof error.stack === "string");
156
+ };
157
+ export const result = Object.assign((givenResult) => {
158
+ return {
159
+ case: (handlers) => {
160
+ if (givenResult?.status === Result.Status.Pending &&
161
+ "pending" in handlers) {
162
+ return typeof handlers.pending === "function"
163
+ ? handlers.pending(givenResult.data)
164
+ : handlers.pending;
165
+ }
166
+ if (givenResult?.status === Result.Status.Success &&
167
+ "success" in handlers) {
168
+ return typeof handlers.success === "function"
169
+ ? handlers.success(givenResult.data)
170
+ : handlers.success;
171
+ }
172
+ if (givenResult?.status === Result.Status.Error &&
173
+ "error" in handlers) {
174
+ return typeof handlers.error === "function"
175
+ ? handlers.error(givenResult.error)
176
+ : handlers.error;
177
+ }
178
+ return typeof handlers.else === "function"
179
+ ? handlers.else(givenResult)
180
+ : handlers.else;
181
+ },
182
+ };
183
+ }, {
184
+ from,
185
+ fromAsync,
186
+ pending,
187
+ success,
188
+ error,
189
+ metadata: getMetadata,
190
+ });
191
+ export { isResult } from "./result.lib.js";
192
+ //# sourceMappingURL=result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/result/result.ts"],"names":[],"mappings":"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","sourcesContent":["import { createPrototype } from \"../lib/create-prototype.ts\";\nimport { stringify } from \"../lib/string.ts\";\nimport { symbolOfResult } from \"./result.lib.ts\";\n\n// Namespaces are only used to group related types together.\n/* eslint-disable @typescript-eslint/no-namespace */\n\n// This module uses prototypes to create objects for the Result.Pending, Result.Success and Result.Error types.\n// This way unimportant properties won't show up in the debugger and we keep the memory footprint low.\n\n/**\n * Represents some async data that could be in the following states:\n *\n * - initial (not implemented, see below)\n * - pending\n * - success\n * - error\n *\n * This type is inspired by https://rametta.org/posts/elm-remote-data/ and has been\n * designed to be aligned with tanstack query's useQuery() result.\n * For instance, it can be used by a React component that wants to handle asynchronous\n * data but does not want to call useQuery directly.\n *\n * The initial state has not been implemented because it's not compatible\n * with tanstack query. If the initial state is needed,\n * it can be represented as Result | undefined\n */\nexport type Result<\n Data = unknown,\n GivenError extends GenericError = GenericError,\n> =\n | Result.Pending<Data | undefined>\n | Result.Success<Data>\n | Result.Error<GivenError, Data | undefined>;\n\nexport const Result = {\n Status: {\n Pending: \"pending\",\n Success: \"success\",\n Error: \"error\",\n },\n} as const;\n\nexport namespace Result {\n export namespace Status {\n export type Pending = typeof Result.Status.Pending;\n export type Success = typeof Result.Status.Success;\n export type Error = typeof Result.Status.Error;\n }\n\n export type Status = Status.Pending | Status.Success | Status.Error;\n\n /**\n * Represents a pending result where data is being loaded.\n * Pending might have stale data, but there's new data inflight.\n */\n export type Pending<Data = undefined> = Readonly<{\n status: Status.Pending;\n /** Is true when there's data */\n isSuccess: false;\n /** Is true when there's an error */\n isError: false;\n /** Is true when there's no result yet */\n isPending: true;\n /** Potentially stale data from a previous result */\n data: Data;\n error: null;\n }>;\n\n /**\n * Represents a successful result that holds some data.\n */\n export type Success<Data = unknown> = Readonly<{\n status: Status.Success;\n /** Is true when there's data */\n isSuccess: true;\n /** Is true when there's an error */\n isError: false;\n /** Is true when there's no result yet */\n isPending: false;\n data: Data;\n error: null;\n }>;\n\n /**\n * Represents a failed result that holds an error.\n * Might also contain stale data from a previous result.\n */\n export type Error<\n GivenError extends GenericError = GenericError,\n Data = undefined,\n > = Readonly<{\n status: Status.Error;\n /** Is true when there's data */\n isSuccess: false;\n /** Is true when there's an error */\n isError: true;\n /** Is true when there's no result yet */\n isPending: false;\n /** Potentially stale data from a previous result */\n data: Data;\n /** The error that occurred */\n error: GivenError;\n }>;\n\n /**\n * Represents a synchronous result that can be either success or error (no pending state).\n */\n export type Sync<\n Data = unknown,\n GivenError extends GenericError = GenericError,\n > = Success<Data> | Error<GivenError, Data>;\n}\n\nconst setMetadata = <Data>(result: Result<Data>, metadata: ResultMetadata) => {\n Object.defineProperty(result, symbolOfResult, {\n value: metadata,\n enumerable: false,\n });\n};\n\nconst getMetadata = <Data>(result: Result<Data>): ResultMetadata => {\n return (result as { [symbolOfResult]?: ResultMetadata })[symbolOfResult]!;\n};\n\n/**\n * Creates a result in the pending state.\n *\n * @param options.data - Potentially stale data from a previous result\n * @param options.createdAt - The date when the result was created\n * @returns The pending result\n */\nconst pending = <const Data = undefined>({\n data,\n createdAt = new Date(),\n}: Pick<Partial<Result.Pending<Data>>, \"data\"> &\n Partial<ResultMetadata> = {}): Result.Pending<Data> => {\n const pendingResult: Result.Pending<Data> = Object.create(pendingPrototype, {\n data: { value: data, enumerable: true },\n });\n\n setMetadata(pendingResult, { createdAt });\n\n return pendingResult;\n};\n\nconst pendingPrototype: Result.Pending = createPrototype(\n {\n status: Result.Status.Pending,\n isSuccess: false,\n isError: false,\n isPending: true,\n data: undefined,\n error: null,\n } as const,\n {\n toString(this: Result.Pending<unknown>) {\n return `Result.Pending(${\n this.data === undefined ? \"\" : stringify(this.data)\n })`;\n },\n },\n);\n\n/**\n * Creates a result in the success state.\n *\n * @param options.data - The data to store in the result\n * @param options.createdAt - The date when the result was created\n * @returns The successful result\n */\nconst success = <const Data>({\n data,\n createdAt = new Date(),\n}: Pick<Result.Success<Data>, \"data\"> &\n Partial<ResultMetadata>): Result.Success<Data> => {\n const successResult: Result.Success<Data> = Object.create(successPrototype, {\n data: { value: data, enumerable: true },\n });\n\n setMetadata(successResult, { createdAt });\n\n return successResult;\n};\n\nconst successPrototype: Result.Success<undefined> = createPrototype(\n {\n status: Result.Status.Success,\n isSuccess: true,\n isError: false,\n isPending: false,\n data: undefined,\n error: null,\n } as const,\n {\n toString(this: Result.Success) {\n return `Result.Success(${stringify(this.data)})`;\n },\n },\n);\n\n/**\n * Creates a result in the error state.\n *\n * @param options.error - The error to store in the result\n * @param options.data - Potentially stale data from a previous result\n * @returns The failed result\n */\nconst error = <const GivenError extends Error, const Data = never>({\n error: givenError,\n data,\n createdAt = new Date(),\n}: Pick<Result.Error<GivenError, Data>, \"error\"> &\n Pick<Partial<Result.Error<GivenError, Data>>, \"data\"> &\n Partial<ResultMetadata>): Result.Error<GivenError, Data> => {\n const errorResult: Result.Error<GivenError, Data> = Object.create(\n errorPrototype,\n {\n data: { value: data, enumerable: true },\n error: { value: givenError, enumerable: true },\n },\n );\n\n setMetadata(errorResult, { createdAt });\n\n return errorResult;\n};\n\nconst errorPrototype: Result.Error = createPrototype(\n {\n status: Result.Status.Error,\n isSuccess: false,\n isError: true,\n isPending: false,\n data: undefined,\n error: new Error(\"Default error\"),\n } as const,\n {\n toString(this: Result.Error) {\n return `Result.Error(${stringify(this.error.message)})`;\n },\n },\n);\n\n/**\n * Calls the function and returns it as result.\n * If the function throws an Error, an error result is returned.\n * If the function throws anything else, it is rethrown.\n *\n * @param fn - The function to call\n * @returns The result of the function\n */\nconst from = <Data>(fn: () => Data): Result.Sync<Data> => {\n try {\n return success({ data: fn() });\n } catch (caughtError) {\n if (isError(caughtError)) {\n return error<GenericError>({ error: caughtError });\n }\n\n throw caughtError;\n }\n};\n\n/**\n * Calls and awaits the async function and returns it as result.\n * If the function rejects the promise, an error result is returned.\n * If the function rejects with anything else, the rejection is rethrown.\n *\n * @param fn - The async function to call and await\n * @returns The result of the async function\n */\nconst fromAsync = async <Data>(\n fn: () => Promise<Data>,\n): Promise<Result.Sync<Data>> => {\n try {\n return success({ data: await fn() });\n } catch (caughtError) {\n if (isError(caughtError)) {\n return error<GenericError>({ error: caughtError });\n }\n\n throw caughtError;\n }\n};\n\n/**\n * Checks if the given value is an Error. Doesn't use instanceof so that\n * DOMException and errors from a different realm can be checked as well.\n */\nconst isError = (error: unknown): error is GenericError => {\n if (\"isError\" in Error && typeof Error.isError === \"function\") {\n const result: unknown = Error.isError(error);\n\n if (typeof result === \"boolean\") {\n return result;\n }\n }\n\n return (\n typeof error === \"object\" &&\n error !== null &&\n \"name\" in error &&\n typeof error.name === \"string\" &&\n \"message\" in error &&\n typeof error.message === \"string\" &&\n \"stack\" in error &&\n typeof error.stack === \"string\"\n );\n};\n\ntype CaseHandlers<\n GivenResult extends Result | null | undefined,\n ReturnType = unknown,\n> = {\n pending?:\n | ((\n maybeData: Extract<\n GivenResult,\n { status: Result.Status.Pending }\n >[\"data\"],\n ) => ReturnType)\n | ReturnType;\n success?:\n | ((\n data: Extract<GivenResult, { status: Result.Status.Success }>[\"data\"],\n ) => ReturnType)\n | ReturnType;\n error?:\n | ((\n error: Extract<GivenResult, { status: Result.Status.Error }>[\"error\"],\n ) => ReturnType)\n | ReturnType;\n else: ((result: GivenResult) => ReturnType) | ReturnType;\n};\n\ntype ResultWrapper<GivenResult extends Result | null | undefined> = {\n case: <ReturnType>(\n handlers: CaseHandlers<GivenResult, ReturnType>,\n ) => ReturnType;\n};\n\ntype ResultFn = {\n <GivenResult extends Result | null | undefined>(\n givenResult: GivenResult,\n ): ResultWrapper<GivenResult>;\n from: typeof from;\n fromAsync: typeof fromAsync;\n pending: typeof pending;\n success: typeof success;\n error: typeof error;\n metadata: typeof getMetadata;\n};\n\nexport const result: ResultFn = Object.assign(\n <GivenResult extends Result | null | undefined>(\n givenResult: GivenResult,\n ): ResultWrapper<GivenResult> => {\n return {\n case: <ReturnType>(\n handlers: CaseHandlers<GivenResult, ReturnType>,\n ): ReturnType => {\n if (\n givenResult?.status === Result.Status.Pending &&\n \"pending\" in handlers\n ) {\n return typeof handlers.pending === \"function\"\n ? (\n handlers.pending as Extract<\n typeof handlers.pending,\n (maybeData: unknown) => ReturnType\n >\n )(givenResult.data)\n : (handlers.pending as ReturnType);\n }\n if (\n givenResult?.status === Result.Status.Success &&\n \"success\" in handlers\n ) {\n return typeof handlers.success === \"function\"\n ? (\n handlers.success as Extract<\n typeof handlers.success,\n (data: unknown) => ReturnType\n >\n )(givenResult.data)\n : (handlers.success as ReturnType);\n }\n if (\n givenResult?.status === Result.Status.Error &&\n \"error\" in handlers\n ) {\n return typeof handlers.error === \"function\"\n ? (\n handlers.error as Extract<\n typeof handlers.error,\n (error: unknown) => ReturnType\n >\n )(givenResult.error)\n : (handlers.error as ReturnType);\n }\n return typeof handlers.else === \"function\"\n ? (\n handlers.else as Extract<\n typeof handlers.else,\n (result: unknown) => ReturnType\n >\n )(givenResult)\n : handlers.else;\n },\n };\n },\n {\n from,\n fromAsync,\n pending,\n success,\n error,\n metadata: getMetadata,\n },\n);\n\nexport { isResult } from \"./result.lib.ts\";\n\ntype GenericError = Error;\n\ntype ResultMetadata = {\n /** The date when the result was created */\n createdAt: Date;\n};\n"]}
@@ -0,0 +1,10 @@
1
+ import type { Result } from "./result.ts";
2
+ export declare const symbolOfResult: unique symbol;
3
+ /**
4
+ * Checks if the given value is a result.
5
+ *
6
+ * @param maybeValue - The value to check
7
+ * @returns True if the value is a result, false otherwise
8
+ */
9
+ export declare const isResult: (maybeValue: unknown) => maybeValue is Result;
10
+ //# sourceMappingURL=result.lib.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.lib.d.ts","sourceRoot":"","sources":["../../src/result/result.lib.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,eAAO,MAAM,cAAc,eAAmB,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,GAAI,YAAY,OAAO,KAAG,UAAU,IAAI,MAM5D,CAAC"}
@@ -0,0 +1,13 @@
1
+ export const symbolOfResult = Symbol("Result");
2
+ /**
3
+ * Checks if the given value is a result.
4
+ *
5
+ * @param maybeValue - The value to check
6
+ * @returns True if the value is a result, false otherwise
7
+ */
8
+ export const isResult = (maybeValue) => {
9
+ return (maybeValue !== null &&
10
+ typeof maybeValue === "object" &&
11
+ symbolOfResult in maybeValue);
12
+ };
13
+ //# sourceMappingURL=result.lib.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.lib.js","sourceRoot":"","sources":["../../src/result/result.lib.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,UAAmB,EAAwB,EAAE;IACpE,OAAO,CACL,UAAU,KAAK,IAAI;QACnB,OAAO,UAAU,KAAK,QAAQ;QAC9B,cAAc,IAAI,UAAU,CAC7B,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import type { Result } from \"./result.ts\";\n\nexport const symbolOfResult = Symbol(\"Result\");\n\n/**\n * Checks if the given value is a result.\n *\n * @param maybeValue - The value to check\n * @returns True if the value is a result, false otherwise\n */\nexport const isResult = (maybeValue: unknown): maybeValue is Result => {\n return (\n maybeValue !== null &&\n typeof maybeValue === \"object\" &&\n symbolOfResult in maybeValue\n );\n};\n"]}
@@ -0,0 +1,31 @@
1
+ import { type Result } from "../result/result.ts";
2
+ /**
3
+ * Unwrap a value from Result or nullable types, returning the underlying value or throwing an error.
4
+ *
5
+ * This function safely extracts values from wrapped types like Result,
6
+ * handling null/undefined values and providing fallback mechanisms.
7
+ *
8
+ * @param maybeValue - The value to unwrap (can be a plain value or Result)
9
+ * @param fallback - Optional fallback value to return instead of throwing
10
+ * @returns The unwrapped value or fallback
11
+ * @throws {TypeError} When the value cannot be unwrapped and no fallback is provided
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Basic unwrapping
16
+ * const value = unwrap("hello"); // "hello"
17
+ * const nullValue = unwrap(null, "default"); // "default"
18
+ *
19
+ * // With Result types
20
+ * const success = result.success({ data: "success" });
21
+ * const data = unwrap(success); // "success"
22
+ *
23
+ * const error = result.error({ error: new Error("failed") });
24
+ * const fallback = unwrap(error, "default"); // "default"
25
+ * ```
26
+ */
27
+ export declare function unwrap<Value, GivenError extends Error>(maybeValue: Value | Result<Value, GivenError>): Value;
28
+ export declare function unwrap<Value>(maybeValue: Result.Success<Value> | Result.Pending<Value>, fallback: unknown): Value;
29
+ export declare function unwrap<Value, GivenError extends Error, const Fallback>(maybeValue: Result.Error<GivenError, Value>, fallback: Fallback): Fallback;
30
+ export declare function unwrap<Value, GivenError extends Error, const Fallback>(maybeValue: Value | Result<Value, GivenError>, fallback: Fallback): NonNullable<Value> | Fallback;
31
+ //# sourceMappingURL=unwrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unwrap.d.ts","sourceRoot":"","sources":["../../src/unwrap/unwrap.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAElD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,SAAS,KAAK,EACpD,UAAU,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,GAC5C,KAAK,CAAC;AACT,wBAAgB,MAAM,CAAC,KAAK,EAC1B,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EACzD,QAAQ,EAAE,OAAO,GAChB,KAAK,CAAC;AACT,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,SAAS,KAAK,EAAE,KAAK,CAAC,QAAQ,EACpE,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,EAC3C,QAAQ,EAAE,QAAQ,GACjB,QAAQ,CAAC;AACZ,wBAAgB,MAAM,CAAC,KAAK,EAAE,UAAU,SAAS,KAAK,EAAE,KAAK,CAAC,QAAQ,EACpE,UAAU,EAAE,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,EAC7C,QAAQ,EAAE,QAAQ,GACjB,WAAW,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { stringify } from "../lib/string.js";
2
+ import { isResult } from "../result/result.lib.js";
3
+ import {} from "../result/result.js";
4
+ // eslint-disable-next-line prefer-arrow/prefer-arrow-functions
5
+ export function unwrap(maybeValue, fallback) {
6
+ const hasFallback = arguments.length > 1;
7
+ if (maybeValue === null || maybeValue === undefined) {
8
+ if (hasFallback)
9
+ return fallback;
10
+ throw new TypeError(`${errorPrefix}Value is ${stringify(maybeValue)}`);
11
+ }
12
+ if (isResult(maybeValue)) {
13
+ // Handle pending state
14
+ if ("isPending" in maybeValue && maybeValue.isPending) {
15
+ if (maybeValue.data === undefined) {
16
+ if (hasFallback)
17
+ return fallback;
18
+ throw new TypeError(typeErrorMessageForResult(maybeValue), {
19
+ cause: maybeValue,
20
+ });
21
+ }
22
+ return maybeValue.data;
23
+ }
24
+ // Handle error state
25
+ if (maybeValue.isError) {
26
+ if (hasFallback)
27
+ return fallback;
28
+ throw new TypeError(typeErrorMessageForResult(maybeValue), {
29
+ cause: maybeValue,
30
+ });
31
+ }
32
+ // Handle success state
33
+ return maybeValue.data;
34
+ }
35
+ return maybeValue;
36
+ }
37
+ const errorPrefix = "Cannot unwrap: ";
38
+ const typeErrorMessageForResult = (maybeValue) => `${errorPrefix}${String(maybeValue)} is not a success and there is no fallback`;
39
+ //# sourceMappingURL=unwrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unwrap.js","sourceRoot":"","sources":["../../src/unwrap/unwrap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAe,MAAM,qBAAqB,CAAC;AA0ClD,+DAA+D;AAC/D,MAAM,UAAU,MAAM,CACpB,UAA6C,EAC7C,QAAgB;IAEhB,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC;IAEzC,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QACpD,IAAI,WAAW;YAAE,OAAO,QAAS,CAAC;QAClC,MAAM,IAAI,SAAS,CAAC,GAAG,WAAW,YAAY,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACzE,CAAC;IAED,IAAI,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACzB,uBAAuB;QACvB,IAAI,WAAW,IAAI,UAAU,IAAI,UAAU,CAAC,SAAS,EAAE,CAAC;YACtD,IAAI,UAAU,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAClC,IAAI,WAAW;oBAAE,OAAO,QAAS,CAAC;gBAClC,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,UAAU,CAAC,EAAE;oBACzD,KAAK,EAAE,UAAU;iBAClB,CAAC,CAAC;YACL,CAAC;YACD,OAAO,UAAU,CAAC,IAAI,CAAC;QACzB,CAAC;QAED,qBAAqB;QACrB,IAAI,UAAU,CAAC,OAAO,EAAE,CAAC;YACvB,IAAI,WAAW;gBAAE,OAAO,QAAS,CAAC;YAClC,MAAM,IAAI,SAAS,CAAC,yBAAyB,CAAC,UAAU,CAAC,EAAE;gBACzD,KAAK,EAAE,UAAU;aAClB,CAAC,CAAC;QACL,CAAC;QAED,uBAAuB;QACvB,OAAO,UAAU,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,WAAW,GAAG,iBAAiB,CAAC;AACtC,MAAM,yBAAyB,GAAG,CAAC,UAAmB,EAAE,EAAE,CACxD,GAAG,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,4CAA4C,CAAC","sourcesContent":["import { stringify } from \"../lib/string.ts\";\nimport { isResult } from \"../result/result.lib.ts\";\nimport { type Result } from \"../result/result.ts\";\n\n/**\n * Unwrap a value from Result or nullable types, returning the underlying value or throwing an error.\n *\n * This function safely extracts values from wrapped types like Result,\n * handling null/undefined values and providing fallback mechanisms.\n *\n * @param maybeValue - The value to unwrap (can be a plain value or Result)\n * @param fallback - Optional fallback value to return instead of throwing\n * @returns The unwrapped value or fallback\n * @throws {TypeError} When the value cannot be unwrapped and no fallback is provided\n *\n * @example\n * ```ts\n * // Basic unwrapping\n * const value = unwrap(\"hello\"); // \"hello\"\n * const nullValue = unwrap(null, \"default\"); // \"default\"\n *\n * // With Result types\n * const success = result.success({ data: \"success\" });\n * const data = unwrap(success); // \"success\"\n *\n * const error = result.error({ error: new Error(\"failed\") });\n * const fallback = unwrap(error, \"default\"); // \"default\"\n * ```\n */\nexport function unwrap<Value, GivenError extends Error>(\n maybeValue: Value | Result<Value, GivenError>,\n): Value;\nexport function unwrap<Value>(\n maybeValue: Result.Success<Value> | Result.Pending<Value>,\n fallback: unknown,\n): Value;\nexport function unwrap<Value, GivenError extends Error, const Fallback>(\n maybeValue: Result.Error<GivenError, Value>,\n fallback: Fallback,\n): Fallback;\nexport function unwrap<Value, GivenError extends Error, const Fallback>(\n maybeValue: Value | Result<Value, GivenError>,\n fallback: Fallback,\n): NonNullable<Value> | Fallback;\n// eslint-disable-next-line prefer-arrow/prefer-arrow-functions\nexport function unwrap<Value, GivenError extends Error, Fallback>(\n maybeValue: Value | Result<Value, GivenError>,\n fallback?: Value,\n): Value | Fallback {\n const hasFallback = arguments.length > 1;\n\n if (maybeValue === null || maybeValue === undefined) {\n if (hasFallback) return fallback!;\n throw new TypeError(`${errorPrefix}Value is ${stringify(maybeValue)}`);\n }\n\n if (isResult(maybeValue)) {\n // Handle pending state\n if (\"isPending\" in maybeValue && maybeValue.isPending) {\n if (maybeValue.data === undefined) {\n if (hasFallback) return fallback!;\n throw new TypeError(typeErrorMessageForResult(maybeValue), {\n cause: maybeValue,\n });\n }\n return maybeValue.data;\n }\n\n // Handle error state\n if (maybeValue.isError) {\n if (hasFallback) return fallback!;\n throw new TypeError(typeErrorMessageForResult(maybeValue), {\n cause: maybeValue,\n });\n }\n\n // Handle success state\n return maybeValue.data;\n }\n\n return maybeValue;\n}\n\nconst errorPrefix = \"Cannot unwrap: \";\nconst typeErrorMessageForResult = (maybeValue: unknown) =>\n `${errorPrefix}${String(maybeValue)} is not a success and there is no fallback`;\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peerigon/typescript-toolkit",
3
- "version": "1.1.0",
3
+ "version": "2.1.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,13 @@
17
17
  "type": "module",
18
18
  "exports": {
19
19
  ".": "./dist/main.js",
20
- "./add": "./dist/add.js"
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",
25
+ "./result": "./dist/result/result.js",
26
+ "./unwrap": "./dist/unwrap/unwrap.js"
21
27
  },
22
28
  "main": "./dist/main.js",
23
29
  "files": [
@@ -50,6 +56,7 @@
50
56
  "@peerigon/configs": "^15.4.0",
51
57
  "@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
52
58
  "@size-limit/preset-small-lib": "^12.1.0",
59
+ "@tanstack/query-core": "^5.100.11",
53
60
  "@types/node": "^25.8.0",
54
61
  "@vitest/coverage-v8": "^4.1.6",
55
62
  "eslint": "^9.39.2",