ts-data-forge 14.4.0 → 14.5.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 (53) hide show
  1. package/README.md +6 -0
  2. package/dist/array/impl/array-utils-search.d.mts.map +1 -1
  3. package/dist/array/impl/array-utils-search.mjs.map +1 -1
  4. package/dist/functional/optional/impl/index.d.mts +1 -0
  5. package/dist/functional/optional/impl/index.d.mts.map +1 -1
  6. package/dist/functional/optional/impl/index.mjs +1 -0
  7. package/dist/functional/optional/impl/index.mjs.map +1 -1
  8. package/dist/functional/optional/impl/optional-match.d.mts +48 -0
  9. package/dist/functional/optional/impl/optional-match.d.mts.map +1 -0
  10. package/dist/functional/optional/impl/optional-match.mjs +22 -0
  11. package/dist/functional/optional/impl/optional-match.mjs.map +1 -0
  12. package/dist/functional/result/impl/index.d.mts +4 -0
  13. package/dist/functional/result/impl/index.d.mts.map +1 -1
  14. package/dist/functional/result/impl/index.mjs +4 -0
  15. package/dist/functional/result/impl/index.mjs.map +1 -1
  16. package/dist/functional/result/impl/result-from-optional.d.mts +41 -0
  17. package/dist/functional/result/impl/result-from-optional.d.mts.map +1 -0
  18. package/dist/functional/result/impl/result-from-optional.mjs +24 -0
  19. package/dist/functional/result/impl/result-from-optional.mjs.map +1 -0
  20. package/dist/functional/result/impl/result-match.d.mts +47 -0
  21. package/dist/functional/result/impl/result-match.d.mts.map +1 -0
  22. package/dist/functional/result/impl/result-match.mjs +24 -0
  23. package/dist/functional/result/impl/result-match.mjs.map +1 -0
  24. package/dist/functional/result/impl/result-safe-try.d.mts +51 -0
  25. package/dist/functional/result/impl/result-safe-try.d.mts.map +1 -0
  26. package/dist/functional/result/impl/result-safe-try.mjs +24 -0
  27. package/dist/functional/result/impl/result-safe-try.mjs.map +1 -0
  28. package/dist/functional/result/impl/result-safe-unwrap.d.mts +36 -0
  29. package/dist/functional/result/impl/result-safe-unwrap.d.mts.map +1 -0
  30. package/dist/functional/result/impl/result-safe-unwrap.mjs +14 -0
  31. package/dist/functional/result/impl/result-safe-unwrap.mjs.map +1 -0
  32. package/dist/others/debounce-function.d.mts.map +1 -1
  33. package/dist/others/debounce-function.mjs.map +1 -1
  34. package/dist/others/fast-deep-equal.d.mts.map +1 -1
  35. package/dist/others/fast-deep-equal.mjs +1 -1
  36. package/dist/others/fast-deep-equal.mjs.map +1 -1
  37. package/package.json +9 -9
  38. package/src/array/impl/array-utils-modification.test.mts +3 -3
  39. package/src/array/impl/array-utils-search.mts +22 -8
  40. package/src/array/impl/array-utils-shape-invariants.test.mts +3 -3
  41. package/src/array/impl/array-utils-transformation.test.mts +1 -1
  42. package/src/functional/optional/impl/index.mts +1 -0
  43. package/src/functional/optional/impl/optional-match.mts +108 -0
  44. package/src/functional/optional.test.mts +79 -0
  45. package/src/functional/result/impl/index.mts +4 -0
  46. package/src/functional/result/impl/result-from-optional.mts +80 -0
  47. package/src/functional/result/impl/result-match.mts +108 -0
  48. package/src/functional/result/impl/result-safe-try.mts +110 -0
  49. package/src/functional/result/impl/result-safe-unwrap.mts +50 -0
  50. package/src/functional/result.test.mts +331 -0
  51. package/src/guard/is-error.test.mts +7 -7
  52. package/src/others/debounce-function.mts +5 -1
  53. package/src/others/fast-deep-equal.mts +9 -2
@@ -0,0 +1,14 @@
1
+ import { isErr } from './result-is-err.mjs';
2
+
3
+ function* safeUnwrap(result) {
4
+ if (isErr(result)) {
5
+ yield result;
6
+ throw new Error(
7
+ "`safeUnwrap()` generator was resumed after yielding an `Err`; it must only be used via `yield*` inside `safeTry()`"
8
+ );
9
+ }
10
+ return result.value;
11
+ }
12
+
13
+ export { safeUnwrap };
14
+ //# sourceMappingURL=result-safe-unwrap.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result-safe-unwrap.mjs","sources":["../../../../src/functional/result/impl/result-safe-unwrap.mts"],"sourcesContent":["import { type UnknownResult } from '../result.mjs';\nimport { isErr } from './result-is-err.mjs';\nimport { type NarrowToErr, type UnwrapOk } from './types.mjs';\n\n/**\n * Unwraps a `Result` inside a {@link Result.safeTry} generator body via\n * `yield*`, emulating Rust's `?` operator.\n *\n * If the `Result` is `Ok`, the `yield*` expression evaluates to the contained\n * value and execution continues. If it is `Err`, the error is yielded to\n * {@link Result.safeTry}, which returns it immediately — the rest of the\n * generator body never runs.\n *\n * This generator must only be delegated to (`yield*`) inside a\n * {@link Result.safeTry} body; resuming it after it has yielded an `Err`\n * throws.\n *\n * @example\n *\n * ```ts\n * const result = Result.safeTry(function* () {\n * const x = yield* Result.safeUnwrap(Result.ok(2));\n *\n * const y = yield* Result.safeUnwrap(Result.ok(3));\n *\n * return Result.ok(x + y);\n * });\n *\n * assert.deepStrictEqual(result, Result.ok(5));\n * ```\n *\n * @template R The input `UnknownResult` type.\n * @param result The `Result` to unwrap.\n * @returns A generator that yields the `Err` (if any) and returns the `Ok`\n * value.\n */\nexport function* safeUnwrap<R extends UnknownResult>(\n result: R,\n): Generator<NarrowToErr<R>, UnwrapOk<R>, unknown> {\n if (isErr(result)) {\n yield result;\n\n throw new Error(\n '`safeUnwrap()` generator was resumed after yielding an `Err`; it must only be used via `yield*` inside `safeTry()`',\n );\n }\n\n // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n return result.value as UnwrapOk<R>;\n}\n"],"names":[],"mappings":";;AAoCO,UAAU,WACf,MAAA,EACiD;AACjD,EAAA,IAAI,KAAA,CAAM,MAAM,CAAA,EAAG;AACjB,IAAA,MAAM,MAAA;AAEN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AAGA,EAAA,OAAO,MAAA,CAAO,KAAA;AAChB;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"debounce-function.d.mts","sourceRoot":"","sources":["../../src/others/debounce-function.mts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ,GAAI,IAAI,SAAS,SAAS,OAAO,EAAE,QAChD,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,oBACX,MAAM,KACvB,CAAC,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,CAU1B,CAAC"}
1
+ {"version":3,"file":"debounce-function.d.mts","sourceRoot":"","sources":["../../src/others/debounce-function.mts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,QAAQ,GAAI,IAAI,SAAS,SAAS,OAAO,EAAE,QAChD,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,oBACX,MAAM,KACvB,CAAC,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,IAAI,CAc1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"debounce-function.mjs","sources":["../../src/others/debounce-function.mts"],"sourcesContent":["/**\n * Creates a debounced function that delays invoking `func` until after `waitMilliseconds`\n * have elapsed since the last time the debounced function was invoked.\n *\n * @template F The type of the function to debounce.\n * @param func The function to debounce.\n * @param waitMilliseconds The number of milliseconds to delay.\n * @returns The new debounced function.\n */\nexport const debounce = <Args extends readonly unknown[]>(\n func: (...args: Args) => void,\n waitMilliseconds: number,\n): ((...args: Args) => void) => {\n let mut_timeoutId: ReturnType<typeof setTimeout> | undefined;\n\n return (...args: Args): void => {\n clearTimeout(mut_timeoutId);\n\n mut_timeoutId = setTimeout(() => {\n func(...args);\n }, waitMilliseconds);\n };\n};\n"],"names":[],"mappings":"AASO,MAAM,QAAA,GAAW,CACtB,IAAA,EACA,gBAAA,KAC8B;AAC9B,EAAA,IAAI,aAAA;AAEJ,EAAA,OAAO,IAAI,IAAA,KAAqB;AAC9B,IAAA,YAAA,CAAa,aAAa,CAAA;AAE1B,IAAA,aAAA,GAAgB,WAAW,MAAM;AAC/B,MAAA,IAAA,CAAK,GAAG,IAAI,CAAA;AAAA,IACd,GAAG,gBAAgB,CAAA;AAAA,EACrB,CAAA;AACF;;;;"}
1
+ {"version":3,"file":"debounce-function.mjs","sources":["../../src/others/debounce-function.mts"],"sourcesContent":["/**\n * Creates a debounced function that delays invoking `func` until after `waitMilliseconds`\n * have elapsed since the last time the debounced function was invoked.\n *\n * @template F The type of the function to debounce.\n * @param func The function to debounce.\n * @param waitMilliseconds The number of milliseconds to delay.\n * @returns The new debounced function.\n */\nexport const debounce = <Args extends readonly unknown[]>(\n func: (...args: Args) => void,\n waitMilliseconds: number,\n): ((...args: Args) => void) => {\n // Not `ReturnType<typeof setTimeout>`: with both the DOM and Node\n // declarations in scope that resolves to `unknown`, which `clearTimeout`\n // will not take. Naming what `clearTimeout` accepts says the same thing and\n // stays true in either environment.\n let mut_timeoutId: Parameters<typeof clearTimeout>[0];\n\n return (...args: Args): void => {\n clearTimeout(mut_timeoutId);\n\n mut_timeoutId = setTimeout(() => {\n func(...args);\n }, waitMilliseconds);\n };\n};\n"],"names":[],"mappings":"AASO,MAAM,QAAA,GAAW,CACtB,IAAA,EACA,gBAAA,KAC8B;AAK9B,EAAA,IAAI,aAAA;AAEJ,EAAA,OAAO,IAAI,IAAA,KAAqB;AAC9B,IAAA,YAAA,CAAa,aAAa,CAAA;AAE1B,IAAA,aAAA,GAAgB,WAAW,MAAM;AAC/B,MAAA,IAAA,CAAK,GAAG,IAAI,CAAA;AAAA,IACd,GAAG,gBAAgB,CAAA;AAAA,EACrB,CAAA;AACF;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"fast-deep-equal.d.mts","sourceRoot":"","sources":["../../src/others/fast-deep-equal.mts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,KAAM,CAAC,KAAK,CAAC,KAAG,OAyF9C,CAAC"}
1
+ {"version":3,"file":"fast-deep-equal.d.mts","sourceRoot":"","sources":["../../src/others/fast-deep-equal.mts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,KAAM,CAAC,KAAK,CAAC,KAAG,OAgG9C,CAAC"}
@@ -49,7 +49,7 @@ const fastDeepEqual = (a, b) => {
49
49
  !(key === "_owner" && a["$$typeof"]) && fastDeepEqual(a[key], b[key])
50
50
  );
51
51
  }
52
- return Number.isNaN(a) && Number.isNaN(b);
52
+ return typeof a === "number" && typeof b === "number" && Number.isNaN(a) && Number.isNaN(b);
53
53
  };
54
54
 
55
55
  export { fastDeepEqual };
@@ -1 +1 @@
1
- {"version":3,"file":"fast-deep-equal.mjs","sources":["../../src/others/fast-deep-equal.mts"],"sourcesContent":["import { isMap, isRegExp, isSet, isTypedArray } from '@sindresorhus/is';\nimport { hasKey, isRecord } from '../guard/index.mjs';\n\n/**\n * The fastest deep equal with ES6 Map, Set and Typed arrays support.\n * Checks equality of Date and RegExp objects by value.\n *\n * Forked from https://github.com/epoberezkin/fast-deep-equal/blob/v3.1.3/src/index.jst\n */\nexport const fastDeepEqual = <T,>(a: T, b: T): boolean => {\n if (a === b) {\n return true;\n }\n\n if (typeof a !== typeof b) {\n return false;\n }\n\n // Arrays are handled before `isRecord`, which excludes them.\n if (Array.isArray(a)) {\n return (\n Array.isArray(b) &&\n a.constructor === b.constructor &&\n a.length === b.length &&\n a.every((ai, index) => fastDeepEqual(ai, b[index]))\n );\n }\n\n if (isRecord(a)) {\n if (!isRecord(b)) {\n return false;\n }\n\n if (a.constructor !== b.constructor) {\n return false;\n }\n\n if (isMap(a)) {\n return (\n isMap(b) &&\n a.size === b.size &&\n a\n .entries()\n .every(\n ([key, value]) => b.has(key) && fastDeepEqual(value, b.get(key)),\n )\n );\n }\n\n if (isSet(a)) {\n return (\n isSet(b) &&\n a.size === b.size &&\n a.entries().every(([value]) => b.has(value))\n );\n }\n\n if (isTypedArray(a)) {\n return (\n isTypedArray(b) &&\n a.length === b.length &&\n a.every((value, index) => value === b[index])\n );\n }\n\n if (isRegExp(a)) {\n return isRegExp(b) && a.source === b.source && a.flags === b.flags;\n }\n\n if (a.valueOf !== Object.prototype.valueOf) {\n return a.valueOf() === b.valueOf();\n }\n\n if (a.toString !== Object.prototype.toString) {\n return a.toString() === b.toString();\n }\n\n const aKeys: readonly string[] = Object.keys(a);\n\n const bKeys: readonly string[] = Object.keys(b);\n\n return (\n aKeys.length === bKeys.length &&\n aKeys.every(\n (key) =>\n hasKey(b, key) &&\n // React-specific: avoid traversing React elements' _owner.\n // _owner contains circular references\n // and is not needed when comparing the actual elements (and not their owners)\n // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions\n !(key === '_owner' && a['$$typeof']) &&\n fastDeepEqual(a[key], b[key]),\n )\n );\n }\n\n // true if both NaN, false otherwise\n return Number.isNaN(a) && Number.isNaN(b);\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fast-deep-equal.mjs","sources":["../../src/others/fast-deep-equal.mts"],"sourcesContent":["import { isMap, isRegExp, isSet, isTypedArray } from '@sindresorhus/is';\nimport { hasKey, isRecord } from '../guard/index.mjs';\n\n/**\n * The fastest deep equal with ES6 Map, Set and Typed arrays support.\n * Checks equality of Date and RegExp objects by value.\n *\n * Forked from https://github.com/epoberezkin/fast-deep-equal/blob/v3.1.3/src/index.jst\n */\nexport const fastDeepEqual = <T,>(a: T, b: T): boolean => {\n if (a === b) {\n return true;\n }\n\n if (typeof a !== typeof b) {\n return false;\n }\n\n // Arrays are handled before `isRecord`, which excludes them.\n if (Array.isArray(a)) {\n return (\n Array.isArray(b) &&\n a.constructor === b.constructor &&\n a.length === b.length &&\n a.every((ai, index) => fastDeepEqual(ai, b[index]))\n );\n }\n\n if (isRecord(a)) {\n if (!isRecord(b)) {\n return false;\n }\n\n if (a.constructor !== b.constructor) {\n return false;\n }\n\n if (isMap(a)) {\n return (\n isMap(b) &&\n a.size === b.size &&\n a\n .entries()\n .every(\n ([key, value]) => b.has(key) && fastDeepEqual(value, b.get(key)),\n )\n );\n }\n\n if (isSet(a)) {\n return (\n isSet(b) &&\n a.size === b.size &&\n a.entries().every(([value]) => b.has(value))\n );\n }\n\n if (isTypedArray(a)) {\n return (\n isTypedArray(b) &&\n a.length === b.length &&\n a.every((value, index) => value === b[index])\n );\n }\n\n if (isRegExp(a)) {\n return isRegExp(b) && a.source === b.source && a.flags === b.flags;\n }\n\n if (a.valueOf !== Object.prototype.valueOf) {\n return a.valueOf() === b.valueOf();\n }\n\n if (a.toString !== Object.prototype.toString) {\n return a.toString() === b.toString();\n }\n\n const aKeys: readonly string[] = Object.keys(a);\n\n const bKeys: readonly string[] = Object.keys(b);\n\n return (\n aKeys.length === bKeys.length &&\n aKeys.every(\n (key) =>\n hasKey(b, key) &&\n // React-specific: avoid traversing React elements' _owner.\n // _owner contains circular references\n // and is not needed when comparing the actual elements (and not their owners)\n // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions\n !(key === '_owner' && a['$$typeof']) &&\n fastDeepEqual(a[key], b[key]),\n )\n );\n }\n\n // true if both NaN, false otherwise. `Number.isNaN` takes a number: called\n // with anything else it is always false, so the check belongs behind a\n // `typeof` guard rather than relying on that.\n return (\n typeof a === 'number' &&\n typeof b === 'number' &&\n Number.isNaN(a) &&\n Number.isNaN(b)\n );\n};\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-data-forge",
3
- "version": "14.4.0",
3
+ "version": "14.5.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -37,29 +37,29 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "@sindresorhus/is": "^8.1.0",
40
- "ts-type-forge": "~9.2.1"
40
+ "ts-type-forge": "~9.2.2"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@emotion/react": "11.14.0",
44
44
  "@emotion/styled": "11.14.1",
45
45
  "@mui/material": "9.3.1",
46
46
  "@types/react": "19.2.18",
47
- "@vitest/browser-playwright": "4.1.10",
47
+ "@vitest/browser-playwright": "4.1.11",
48
48
  "dedent": "1.7.2",
49
- "immer": "11.1.16",
49
+ "eslint-config-typed": "5.10.0",
50
+ "eslint-plugin-ts-fortress": "1.0.5",
51
+ "eslint-plugin-ts-type-forge": "0.7.1",
52
+ "immer": "11.1.18",
50
53
  "playwright": "1.62.1",
51
54
  "prettier": "3.9.6",
52
55
  "prettier-plugin-organize-imports": "4.3.0",
53
56
  "prettier-plugin-packagejson": "3.0.2",
54
57
  "react": "19.2.8",
55
58
  "rollup": "4.62.4",
59
+ "ts-repo-utils": "10.3.2",
56
60
  "typedoc": "0.28.20",
57
61
  "typedoc-github-theme": "0.4.0",
58
- "vite": "8.2.1",
59
- "eslint-plugin-ts-fortress": "1.0.4",
60
- "eslint-config-typed": "5.9.3",
61
- "ts-repo-utils": "10.2.0",
62
- "eslint-plugin-ts-type-forge": "0.6.6"
62
+ "vite": "8.2.1"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "typescript": ">=4.8"
@@ -257,7 +257,7 @@ describe('Arr modifications', () => {
257
257
  xs: T,
258
258
  ): Readonly<{ [K in keyof T]: 0 }> => toFilled(xs, 0);
259
259
 
260
- const result = blank([1, 2, 3] as const);
260
+ const result = blank([1, 2, 3]);
261
261
 
262
262
  expectType<typeof result, FixedLengthTuple<3, 0>>('=');
263
263
 
@@ -269,7 +269,7 @@ describe('Arr modifications', () => {
269
269
  xs: T,
270
270
  ): Readonly<{ [K in keyof T]: 0 }> => toFilled(0)(xs);
271
271
 
272
- const result = blank([1, 2] as const);
272
+ const result = blank([1, 2]);
273
273
 
274
274
  expectType<typeof result, FixedLengthTuple<2, 0>>('=');
275
275
 
@@ -319,7 +319,7 @@ describe('Arr modifications', () => {
319
319
  xs: T,
320
320
  ): Readonly<{ [K in keyof T]: number }> => toRangeFilled(xs, 0, [1, 2]);
321
321
 
322
- const result = blankMiddle([1, 2, 3] as const);
322
+ const result = blankMiddle([1, 2, 3]);
323
323
 
324
324
  expectType<typeof result, FixedLengthTuple<3, number>>('=');
325
325
 
@@ -79,10 +79,17 @@ const findImpl = <E,>(
79
79
  predicate as () => boolean,
80
80
  );
81
81
 
82
- expectType<
83
- Parameters<typeof array.findIndex>[0],
84
- (value: E, index: number, arr: readonly E[]) => unknown
85
- >('=');
82
+ // Left as a comment rather than asserted. This pinned the shape of the
83
+ // standard library's `findIndex`, and the shape differs between the stock
84
+ // library and the strict one — `(value, index, obj: this) => boolean` there
85
+ // against `(value, index, arr: readonly E[]) => unknown` here. `src` ships
86
+ // to consumers so that "Go to Definition" reaches it, so an assertion that
87
+ // only holds under one of them shows up as an error in their editor.
88
+ //
89
+ // expectType<
90
+ // Parameters<typeof array.findIndex>[0],
91
+ // (value: E, index: number, arr: readonly E[]) => unknown
92
+ // >('=');
86
93
 
87
94
  expectType<
88
95
  typeof predicate,
@@ -162,10 +169,17 @@ const findLastImpl = <E,>(
162
169
  predicate as () => boolean,
163
170
  );
164
171
 
165
- expectType<
166
- Parameters<typeof array.findIndex>[0],
167
- (value: E, index: number, arr: readonly E[]) => unknown
168
- >('=');
172
+ // Left as a comment rather than asserted. This pinned the shape of the
173
+ // standard library's `findIndex`, and the shape differs between the stock
174
+ // library and the strict one — `(value, index, obj: this) => boolean` there
175
+ // against `(value, index, arr: readonly E[]) => unknown` here. `src` ships
176
+ // to consumers so that "Go to Definition" reaches it, so an assertion that
177
+ // only holds under one of them shows up as an error in their editor.
178
+ //
179
+ // expectType<
180
+ // Parameters<typeof array.findIndex>[0],
181
+ // (value: E, index: number, arr: readonly E[]) => unknown
182
+ // >('=');
169
183
 
170
184
  expectType<
171
185
  typeof predicate,
@@ -267,7 +267,7 @@ describe('Arr shape invariants', () => {
267
267
  );
268
268
 
269
269
  assert.deepStrictEqual<readonly string[]>(
270
- toSortedNonNumberInGenericContext(['c', 'a', 'b'] as const),
270
+ toSortedNonNumberInGenericContext(['c', 'a', 'b']),
271
271
  ['a', 'b', 'c'],
272
272
  );
273
273
 
@@ -313,12 +313,12 @@ describe('Arr shape invariants', () => {
313
313
  );
314
314
 
315
315
  assert.deepStrictEqual<readonly number[]>(
316
- uniqInGenericContext([1, 1, 2] as const),
316
+ uniqInGenericContext([1, 1, 2]),
317
317
  [1, 2],
318
318
  );
319
319
 
320
320
  assert.deepStrictEqual<readonly number[]>(
321
- uniqByInGenericContext([1, 1, 2] as const),
321
+ uniqByInGenericContext([1, 1, 2]),
322
322
  [1, 2],
323
323
  );
324
324
  });
@@ -77,7 +77,7 @@ describe('Arr transformations', () => {
77
77
  boxes: T,
78
78
  ): Readonly<{ [K in keyof T]: unknown }> => map(boxes, (b) => b.v);
79
79
 
80
- const result = mapValues([{ v: 1 }, { v: 'a' }] as const);
80
+ const result = mapValues([{ v: 1 }, { v: 'a' }]);
81
81
 
82
82
  expectType<typeof result, FixedLengthTuple<2, unknown>>('=');
83
83
 
@@ -6,6 +6,7 @@ export * from './optional-is-none.mjs';
6
6
  export * from './optional-is-optional.mjs';
7
7
  export * from './optional-is-some.mjs';
8
8
  export * from './optional-map.mjs';
9
+ export * from './optional-match.mjs';
9
10
  export * from './optional-none.mjs';
10
11
  export * from './optional-or-else.mjs';
11
12
  export * from './optional-some.mjs';
@@ -0,0 +1,108 @@
1
+ import { type UnknownOptional } from '../optional.mjs';
2
+ import { isSome } from './optional-is-some.mjs';
3
+ import { unwrap } from './optional-unwrap.mjs';
4
+ import { type Unwrap } from './types.mjs';
5
+
6
+ /**
7
+ * Folds an {@link Optional} into a plain value by applying one of two case
8
+ * handlers: `some` if the {@link Optional} contains a value, `none`
9
+ * otherwise. This is the function form of pattern matching on an
10
+ * {@link Optional}.
11
+ *
12
+ * Unlike {@link Optional.map}, the result is not wrapped in an
13
+ * {@link Optional} — both branches produce the final value directly.
14
+ *
15
+ * @example
16
+ *
17
+ * ```ts
18
+ * const doubledOrZero = Optional.match(Optional.some(21), {
19
+ * some: (value) => value * 2,
20
+ * none: () => 0,
21
+ * });
22
+ *
23
+ * assert.deepStrictEqual(doubledOrZero, 42);
24
+ *
25
+ * const matcher = Optional.match({
26
+ * some: (value: number) => `value: ${value}`,
27
+ * none: () => 'none',
28
+ * });
29
+ *
30
+ * assert.deepStrictEqual(matcher(Optional.some(3)), 'value: 3');
31
+ *
32
+ * assert.deepStrictEqual(matcher(Optional.none), 'none');
33
+ * ```
34
+ *
35
+ * @template O The input `UnknownOptional` type.
36
+ * @template S2 The type returned by the `some` case.
37
+ * @template N2 The type returned by the `none` case.
38
+ * @param optional The `Optional` to match on.
39
+ * @param cases An object with a `some` handler receiving the contained value
40
+ * and a `none` handler receiving nothing.
41
+ * @returns The value produced by whichever case handler was applied.
42
+ */
43
+ export function match<O extends UnknownOptional, S2, N2>(
44
+ optional: O,
45
+ cases: Readonly<{
46
+ some: (value: Unwrap<O>) => S2;
47
+ none: () => N2;
48
+ }>,
49
+ ): N2 | S2;
50
+
51
+ // Curried version
52
+ //
53
+ // The returned function is generic over the *whole* optional, mirroring the
54
+ // direct overload above; see the comment on the curried overload of
55
+ // `Optional.map` for why the parameter is a conditional type instead of a
56
+ // plain `Optional<S>`.
57
+ export function match<S, S2, N2>(
58
+ cases: Readonly<{
59
+ some: (value: S) => S2;
60
+ none: () => N2;
61
+ }>,
62
+ ): <O extends UnknownOptional>(
63
+ optional: Unwrap<O> extends S ? O : never,
64
+ ) => N2 | S2;
65
+
66
+ export function match<O extends UnknownOptional, S2, N2>(
67
+ ...args:
68
+ | readonly [
69
+ optional: O,
70
+ cases: Readonly<{
71
+ some: (value: Unwrap<O>) => S2;
72
+ none: () => N2;
73
+ }>,
74
+ ]
75
+ | readonly [
76
+ cases: Readonly<{
77
+ some: (value: Unwrap<O>) => S2;
78
+ none: () => N2;
79
+ }>,
80
+ ]
81
+ ): N2 | S2 | ((optional: O) => N2 | S2) {
82
+ switch (args.length) {
83
+ case 2: {
84
+ const [optional, cases] = args;
85
+
86
+ return matchImpl(optional, cases);
87
+ }
88
+
89
+ case 1: {
90
+ // Curried version: the only argument is the case handlers
91
+ const [cases] = args;
92
+
93
+ return (optional: O) => matchImpl(optional, cases);
94
+ }
95
+ }
96
+ }
97
+
98
+ const matchImpl = <O extends UnknownOptional, S2, N2>(
99
+ optional: O,
100
+ cases: Readonly<{
101
+ some: (value: Unwrap<O>) => S2;
102
+ none: () => N2;
103
+ }>,
104
+ ): N2 | S2 =>
105
+ isSome(optional)
106
+ ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
107
+ cases.some(unwrap(optional)!)
108
+ : cases.none();
@@ -800,4 +800,83 @@ describe('Optional test', () => {
800
800
  expect(Optional.unwrap(inner)).toBe(42);
801
801
  });
802
802
  });
803
+
804
+ describe('match', () => {
805
+ test('should apply the some case for Some', () => {
806
+ const result = Optional.match(Optional.some(21), {
807
+ some: (value) => value * 2,
808
+ none: () => 0,
809
+ });
810
+
811
+ expectType<typeof result, number>('=');
812
+
813
+ assert.deepStrictEqual(result, 42);
814
+ });
815
+
816
+ test('should apply the none case for None', () => {
817
+ const result = Optional.match(Optional.none, {
818
+ some: () => 'some',
819
+ none: () => 'none',
820
+ });
821
+
822
+ assert.deepStrictEqual(result, 'none');
823
+ });
824
+
825
+ test('should support Optional union inputs', () => {
826
+ const optional: Optional<number> = Optional.some(3);
827
+
828
+ const result = Optional.match(optional, {
829
+ some: (value) => `value: ${value}`,
830
+ none: () => 'none',
831
+ });
832
+
833
+ expectType<typeof result, string>('=');
834
+
835
+ assert.deepStrictEqual(result, 'value: 3');
836
+ });
837
+
838
+ test('curried version should work', () => {
839
+ const matcher = Optional.match({
840
+ some: (value: number) => `value: ${value}`,
841
+ none: () => 'none',
842
+ });
843
+
844
+ const result = matcher(Optional.some(3));
845
+
846
+ expectType<typeof result, string>('=');
847
+
848
+ assert.deepStrictEqual(result, 'value: 3');
849
+
850
+ assert.deepStrictEqual(matcher(Optional.none), 'none');
851
+ });
852
+
853
+ test('curried version should compose with Array.map and pipe', () => {
854
+ const matcher = Optional.match({
855
+ some: (value: number) => `some:${value}`,
856
+ none: () => 'none',
857
+ });
858
+
859
+ const optionals: readonly Optional<number>[] = [
860
+ Optional.some(1),
861
+ Optional.none,
862
+ ] as const;
863
+
864
+ assert.deepStrictEqual(optionals.map(matcher), ['some:1', 'none']);
865
+
866
+ const single: Optional<number> = Optional.some(3);
867
+
868
+ assert.deepStrictEqual(pipe(single).map(matcher).value, 'some:3');
869
+ });
870
+
871
+ test('should apply the some case for Some(undefined)', () => {
872
+ const makeOptional = (): Optional<undefined> => Optional.some(undefined);
873
+
874
+ const result = Optional.match(makeOptional(), {
875
+ some: (value) => `some: ${String(value)}`,
876
+ none: () => 'none',
877
+ });
878
+
879
+ assert.deepStrictEqual(result, 'some: undefined');
880
+ });
881
+ });
803
882
  });
@@ -2,6 +2,7 @@ export * from './result-err.mjs';
2
2
  export * from './result-expect-to-be.mjs';
3
3
  export * from './result-flat-map.mjs';
4
4
  export * from './result-fold.mjs';
5
+ export * from './result-from-optional.mjs';
5
6
  export * from './result-from-promise.mjs';
6
7
  export * from './result-from-throwable.mjs';
7
8
  export * from './result-is-err.mjs';
@@ -9,8 +10,11 @@ export * from './result-is-ok.mjs';
9
10
  export * from './result-is-result.mjs';
10
11
  export * from './result-map-err.mjs';
11
12
  export * from './result-map.mjs';
13
+ export * from './result-match.mjs';
12
14
  export * from './result-ok.mjs';
13
15
  export * from './result-or-else.mjs';
16
+ export * from './result-safe-try.mjs';
17
+ export * from './result-safe-unwrap.mjs';
14
18
  export * from './result-swap.mjs';
15
19
  export * from './result-to-optional.mjs';
16
20
  export * from './result-unwrap-err-or.mjs';
@@ -0,0 +1,80 @@
1
+ import { Optional, type UnknownOptional } from '../../optional/index.mjs';
2
+ import { type Result } from '../result.mjs';
3
+ import { err } from './result-err.mjs';
4
+ import { ok } from './result-ok.mjs';
5
+
6
+ /**
7
+ * Converts an {@link Optional} to a `Result` (the inverse of
8
+ * {@link Result.toOptional}, and the equivalent of Rust's `Option::ok_or`).
9
+ *
10
+ * If the {@link Optional} is `Some`, returns `Ok` with the contained value.
11
+ * If it is `None`, returns `Err` with the provided error value.
12
+ *
13
+ * @example
14
+ *
15
+ * ```ts
16
+ * assert.deepStrictEqual(
17
+ * Result.fromOptional(Optional.some(42), 'missing'),
18
+ * Result.ok(42),
19
+ * );
20
+ *
21
+ * assert.deepStrictEqual(
22
+ * Result.fromOptional(Optional.none, 'missing'),
23
+ * Result.err('missing'),
24
+ * );
25
+ *
26
+ * const withMissingError = Result.fromOptional('missing');
27
+ *
28
+ * assert.deepStrictEqual(withMissingError(Optional.some(1)), Result.ok(1));
29
+ *
30
+ * assert.deepStrictEqual(
31
+ * withMissingError(Optional.none),
32
+ * Result.err('missing'),
33
+ * );
34
+ * ```
35
+ *
36
+ * @template O The input `UnknownOptional` type.
37
+ * @template E The type of the error value used for the `None` case.
38
+ * @param optional The `Optional` to convert.
39
+ * @param error The error value to use if the `Optional` is `None`.
40
+ * @returns `Ok` with the contained value, or `Err` with `error`.
41
+ */
42
+ export function fromOptional<O extends UnknownOptional, const E>(
43
+ optional: O,
44
+ error: E,
45
+ ): Result<Optional.Unwrap<O>, E>;
46
+
47
+ // Curried version
48
+ export function fromOptional<const E>(
49
+ error: E,
50
+ ): <O extends UnknownOptional>(optional: O) => Result<Optional.Unwrap<O>, E>;
51
+
52
+ export function fromOptional<O extends UnknownOptional, const E>(
53
+ ...args: readonly [optional: O, error: E] | readonly [error: E]
54
+ ):
55
+ | Result<Optional.Unwrap<O>, E>
56
+ | ((optional: O) => Result<Optional.Unwrap<O>, E>) {
57
+ switch (args.length) {
58
+ case 2: {
59
+ const [optional, error] = args;
60
+
61
+ return fromOptionalImpl(optional, error);
62
+ }
63
+
64
+ case 1: {
65
+ // Curried version: the only argument is the error value
66
+ const [error] = args;
67
+
68
+ return (optional: O) => fromOptionalImpl(optional, error);
69
+ }
70
+ }
71
+ }
72
+
73
+ const fromOptionalImpl = <O extends UnknownOptional, E>(
74
+ optional: O,
75
+ error: E,
76
+ ): Result<Optional.Unwrap<O>, E> =>
77
+ Optional.isSome(optional)
78
+ ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
79
+ ok(Optional.unwrap(optional)!)
80
+ : err(error);
@@ -0,0 +1,108 @@
1
+ import { type UnknownResult } from '../result.mjs';
2
+ import { isOk } from './result-is-ok.mjs';
3
+ import { type UnwrapErr, type UnwrapOk } from './types.mjs';
4
+
5
+ /**
6
+ * Folds a `Result` into a plain value by applying one of two case handlers:
7
+ * `ok` if the `Result` is `Ok`, `err` otherwise. This is the function form of
8
+ * pattern matching on a `Result`.
9
+ *
10
+ * Unlike {@link Result.fold}, which maps both sides and returns another
11
+ * `Result`, `match` unwraps: both branches produce the final value directly.
12
+ *
13
+ * @example
14
+ *
15
+ * ```ts
16
+ * const doubledOrZero = Result.match(Result.ok(21), {
17
+ * ok: (value) => value * 2,
18
+ * err: () => 0,
19
+ * });
20
+ *
21
+ * assert.deepStrictEqual(doubledOrZero, 42);
22
+ *
23
+ * const matcher = Result.match({
24
+ * ok: (value: number) => value + 1,
25
+ * err: (error: string) => error.length,
26
+ * });
27
+ *
28
+ * assert.deepStrictEqual(matcher(Result.ok(1)), 2);
29
+ *
30
+ * assert.deepStrictEqual(matcher(Result.err('oops')), 4);
31
+ * ```
32
+ *
33
+ * @template R The input `UnknownResult` type.
34
+ * @template S2 The type returned by the `ok` case.
35
+ * @template E2 The type returned by the `err` case.
36
+ * @param result The `Result` to match on.
37
+ * @param cases An object with an `ok` handler receiving the success value and
38
+ * an `err` handler receiving the error value.
39
+ * @returns The value produced by whichever case handler was applied.
40
+ */
41
+ export function match<R extends UnknownResult, S2, E2>(
42
+ result: R,
43
+ cases: Readonly<{
44
+ ok: (value: UnwrapOk<R>) => S2;
45
+ err: (error: UnwrapErr<R>) => E2;
46
+ }>,
47
+ ): E2 | S2;
48
+
49
+ // Curried version
50
+ //
51
+ // The returned function is generic over the *whole* result, mirroring the
52
+ // direct overload above; see the comment on the curried overload of
53
+ // `Result.map` for why the parameter is a conditional type instead of a plain
54
+ // `Result<S, E>`. Both sides have to be checked here, hence the nested
55
+ // conditional.
56
+ export function match<S, E, S2, E2>(
57
+ cases: Readonly<{
58
+ ok: (value: S) => S2;
59
+ err: (error: E) => E2;
60
+ }>,
61
+ ): <R extends UnknownResult>(
62
+ result: UnwrapOk<R> extends S ? (UnwrapErr<R> extends E ? R : never) : never,
63
+ ) => E2 | S2;
64
+
65
+ export function match<R extends UnknownResult, S2, E2>(
66
+ ...args:
67
+ | readonly [
68
+ result: R,
69
+ cases: Readonly<{
70
+ ok: (value: UnwrapOk<R>) => S2;
71
+ err: (error: UnwrapErr<R>) => E2;
72
+ }>,
73
+ ]
74
+ | readonly [
75
+ cases: Readonly<{
76
+ ok: (value: UnwrapOk<R>) => S2;
77
+ err: (error: UnwrapErr<R>) => E2;
78
+ }>,
79
+ ]
80
+ ): E2 | S2 | ((result: R) => E2 | S2) {
81
+ switch (args.length) {
82
+ case 2: {
83
+ const [result, cases] = args;
84
+
85
+ return matchImpl(result, cases);
86
+ }
87
+
88
+ case 1: {
89
+ // Curried version: the only argument is the case handlers
90
+ const [cases] = args;
91
+
92
+ return (result: R) => matchImpl(result, cases);
93
+ }
94
+ }
95
+ }
96
+
97
+ const matchImpl = <R extends UnknownResult, S2, E2>(
98
+ result: R,
99
+ cases: Readonly<{
100
+ ok: (value: UnwrapOk<R>) => S2;
101
+ err: (error: UnwrapErr<R>) => E2;
102
+ }>,
103
+ ): E2 | S2 =>
104
+ isOk(result)
105
+ ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion
106
+ cases.ok(result.value as UnwrapOk<R>)
107
+ : // eslint-disable-next-line total-functions/no-unsafe-type-assertion
108
+ cases.err(result.value as UnwrapErr<R>);