ts-data-forge 14.1.0 → 14.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/ts-data-forge.svg)](https://www.npmjs.com/package/ts-data-forge)
5
- [![License](https://img.shields.io/npm/l/ts-data-forge.svg)](./LICENSE)
6
- [![codecov](https://codecov.io/gh/noshiro-pf/ts-data-forge/branch/main/graph/badge.svg?token=69TA40HACZ)](https://codecov.io/gh/noshiro-pf/ts-data-forge)
5
+ [![License](https://img.shields.io/npm/l/ts-data-forge.svg)](https://github.com/noshiro-pf/mono/blob/main/libs/ts-data-forge/LICENSE)
6
+ [![codecov](https://codecov.io/gh/noshiro-pf/mono/graph/badge.svg)](https://codecov.io/gh/noshiro-pf/mono)
7
7
 
8
8
  **ts-data-forge** is a TypeScript utility library that provides type-safe functional programming utilities with zero runtime dependencies. It aims to enhance development robustness, maintainability, and correctness by leveraging TypeScript's powerful type system.
9
9
 
10
10
  ## Perfect Companion to ts-type-forge
11
11
 
12
- **ts-data-forge** is designed as the ideal runtime companion to [**ts-type-forge**](https://github.com/noshiro-pf/ts-type-forge), a powerful type utility library. While ts-type-forge provides compile-time type utilities for advanced TypeScript type manipulation, ts-data-forge complements it with runtime utilities that maintain the same level of type safety.
12
+ **ts-data-forge** is designed as the ideal runtime companion to [**ts-type-forge**](https://github.com/noshiro-pf/mono/tree/main/libs/ts-type-forge), a powerful type utility library. While ts-type-forge provides compile-time type utilities for advanced TypeScript type manipulation, ts-data-forge complements it with runtime utilities that maintain the same level of type safety.
13
13
 
14
14
  Together, they form a complete TypeScript development toolkit:
15
15
 
@@ -76,7 +76,7 @@ ts-data-forge works best with strict TypeScript settings:
76
76
 
77
77
  ## Core Modules
78
78
 
79
- ### 🎯 [Functional Programming](./documents/functional.md)
79
+ ### 🎯 [Functional Programming](https://noshiro-pf.github.io/ts-data-forge/)
80
80
 
81
81
  Essential FP utilities for cleaner, more reliable code.
82
82
 
@@ -108,7 +108,7 @@ Type-safe array and tuple utilities with functional programming patterns.
108
108
  - **Array Utils** - Comprehensive array manipulation functions
109
109
  - **Tuple Utils** - Type-safe tuple operations with compile-time guarantees
110
110
 
111
- ### 📦 [Collections](./documents/collections.md)
111
+ ### 📦 [Collections](https://noshiro-pf.github.io/ts-data-forge/)
112
112
 
113
113
  Immutable data structures for safer state management.
114
114
 
@@ -677,8 +677,8 @@ export default defineConfig({
677
677
 
678
678
  ## Contributing
679
679
 
680
- Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for detailed guidelines on how to contribute to this project.
680
+ Contributions are welcome! Please see [CONTRIBUTING.md](https://github.com/noshiro-pf/mono/blob/main/libs/ts-data-forge/CONTRIBUTING.md) for detailed guidelines on how to contribute to this project.
681
681
 
682
682
  ## License
683
683
 
684
- This project is licensed under the [Apache License 2.0](./LICENSE).
684
+ This project is licensed under the [Apache License 2.0](https://github.com/noshiro-pf/mono/blob/main/libs/ts-data-forge/LICENSE).
@@ -30,5 +30,5 @@ import { type Unwrap } from './types.mjs';
30
30
  * `None` if the input was `None`.
31
31
  */
32
32
  export declare function map<O extends UnknownOptional, S2>(optional: O, mapFn: (value: Unwrap<O>) => S2): Optional<S2>;
33
- export declare function map<S, S2>(mapFn: (value: S) => S2): (optional: Optional<S>) => Optional<S2>;
33
+ export declare function map<S, S2>(mapFn: (value: S) => S2): <O extends UnknownOptional>(optional: Unwrap<O> extends S ? O : never) => Optional<S2>;
34
34
  //# sourceMappingURL=optional-map.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"optional-map.d.mts","sourceRoot":"","sources":["../../../../src/functional/optional/impl/optional-map.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAKtE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,eAAe,EAAE,EAAE,EAC/C,QAAQ,EAAE,CAAC,EACX,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,GAC9B,QAAQ,CAAC,EAAE,CAAC,CAAC;AAGhB,wBAAgB,GAAG,CAAC,CAAC,EAAE,EAAE,EACvB,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GACtB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"optional-map.d.mts","sourceRoot":"","sources":["../../../../src/functional/optional/impl/optional-map.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAKtE,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,eAAe,EAAE,EAAE,EAC/C,QAAQ,EAAE,CAAC,EACX,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,GAC9B,QAAQ,CAAC,EAAE,CAAC,CAAC;AAgBhB,wBAAgB,GAAG,CAAC,CAAC,EAAE,EAAE,EACvB,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GACtB,CAAC,CAAC,SAAS,eAAe,EAC3B,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,KACtC,QAAQ,CAAC,EAAE,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"optional-map.mjs","sources":["../../../../src/functional/optional/impl/optional-map.mts"],"sourcesContent":["import { type Optional, type UnknownOptional } from '../optional.mjs';\nimport { isSome } from './optional-is-some.mjs';\nimport { none } from './optional-none.mjs';\nimport { some } from './optional-some.mjs';\nimport { unwrap } from './optional-unwrap.mjs';\nimport { type Unwrap } from './types.mjs';\n\n/**\n * Maps an {@link Optional}<S> to {@link Optional}<S2> by applying a function to\n * a contained value. If the {@link Optional} is {@link None}, it\n * returns {@link Optional.none}. Otherwise, it applies the `mapFn` to the\n * value in `Some` and returns a new `Some` with the result.\n *\n * @example\n *\n * ```ts\n * const numberOptional = Optional.some(21);\n *\n * const mapped = Optional.map(numberOptional, (value) => value * 2);\n *\n * assert.deepStrictEqual(mapped, Optional.some(42));\n *\n * const mapToLength = Optional.map((text: string) => text.length);\n *\n * assert.deepStrictEqual(mapToLength(Optional.some('abc')), Optional.some(3));\n *\n * assert.deepStrictEqual(mapToLength(Optional.none), Optional.none);\n * ```\n *\n * @template O The input `UnknownOptional` type.\n * @template S2 The type of the value returned by the mapping function.\n * @param optional The `Optional` to map.\n * @param mapFn The function to apply to the value if it exists.\n * @returns A new `Optional<S2>` resulting from the mapping, or\n * `None` if the input was `None`.\n */\nexport function map<O extends UnknownOptional, S2>(\n optional: O,\n mapFn: (value: Unwrap<O>) => S2,\n): Optional<S2>;\n\n// Curried version\nexport function map<S, S2>(\n mapFn: (value: S) => S2,\n): (optional: Optional<S>) => Optional<S2>;\n\nexport function map<O extends UnknownOptional, S2>(\n ...args:\n | readonly [optional: O, mapFn: (value: Unwrap<O>) => S2]\n | readonly [mapFn: (value: Unwrap<O>) => S2]\n): Optional<S2> | ((optional: O) => Optional<S2>) {\n switch (args.length) {\n case 2: {\n const [optional, mapFn] = args;\n\n return mapImpl(optional, mapFn);\n }\n\n case 1: {\n // Curried version: first argument is mapping function\n const [mapFn] = args;\n\n return (optional: O) => mapImpl(optional, mapFn);\n }\n }\n}\n\nconst mapImpl = <O extends UnknownOptional, S2>(\n optional: O,\n mapFn: (value: Unwrap<O>) => S2,\n): Optional<S2> =>\n isSome(optional)\n ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n some(mapFn(unwrap(optional)!))\n : none;\n"],"names":[],"mappings":";;;;;AA8CO,SAAS,OACX,IAAA,EAG6C;AAChD,EAAA,QAAQ,KAAK,MAAA;AAAQ,IACnB,KAAK,CAAA,EAAG;AACN,MAAA,MAAM,CAAC,QAAA,EAAU,KAAK,CAAA,GAAI,IAAA;AAE1B,MAAA,OAAO,OAAA,CAAQ,UAAU,KAAK,CAAA;AAAA,IAChC;AAAA,IAEA,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,MAAA,OAAO,CAAC,QAAA,KAAgB,OAAA,CAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IACjD;AAAA;AAEJ;AAEA,MAAM,OAAA,GAAU,CACd,QAAA,EACA,KAAA,KAEA,OAAO,QAAQ,CAAA;AAAA;AAAA,EAEX,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,QAAQ,CAAE,CAAC;AAAA,IAC7B,IAAA;;;;"}
1
+ {"version":3,"file":"optional-map.mjs","sources":["../../../../src/functional/optional/impl/optional-map.mts"],"sourcesContent":["import { type Optional, type UnknownOptional } from '../optional.mjs';\nimport { isSome } from './optional-is-some.mjs';\nimport { none } from './optional-none.mjs';\nimport { some } from './optional-some.mjs';\nimport { unwrap } from './optional-unwrap.mjs';\nimport { type Unwrap } from './types.mjs';\n\n/**\n * Maps an {@link Optional}<S> to {@link Optional}<S2> by applying a function to\n * a contained value. If the {@link Optional} is {@link None}, it\n * returns {@link Optional.none}. Otherwise, it applies the `mapFn` to the\n * value in `Some` and returns a new `Some` with the result.\n *\n * @example\n *\n * ```ts\n * const numberOptional = Optional.some(21);\n *\n * const mapped = Optional.map(numberOptional, (value) => value * 2);\n *\n * assert.deepStrictEqual(mapped, Optional.some(42));\n *\n * const mapToLength = Optional.map((text: string) => text.length);\n *\n * assert.deepStrictEqual(mapToLength(Optional.some('abc')), Optional.some(3));\n *\n * assert.deepStrictEqual(mapToLength(Optional.none), Optional.none);\n * ```\n *\n * @template O The input `UnknownOptional` type.\n * @template S2 The type of the value returned by the mapping function.\n * @param optional The `Optional` to map.\n * @param mapFn The function to apply to the value if it exists.\n * @returns A new `Optional<S2>` resulting from the mapping, or\n * `None` if the input was `None`.\n */\nexport function map<O extends UnknownOptional, S2>(\n optional: O,\n mapFn: (value: Unwrap<O>) => S2,\n): Optional<S2>;\n\n// Curried version\n//\n// The returned function is generic over the *whole* optional, mirroring the\n// direct overload above, rather than fixing its parameter to `Optional<S>`.\n// Fixing it makes the curried form unusable from a caller that is itself\n// generic over the optional: with `S` bound to `Unwrap<O>` at currying time\n// the parameter becomes `Optional<Unwrap<O>>`, and TypeScript cannot show a\n// bare `O extends UnknownOptional` is assignable to that — although every\n// concrete instantiation of `O` is.\n//\n// The conditional is what keeps the check: `O` is inferred from the argument\n// and its value type still has to satisfy `S`, so mapping a `(s: string) =>\n// …` over an `Optional<number>` stays an error. The cost is the message it\n// produces — \"not assignable to `never`\" rather than naming the two optionals.\nexport function map<S, S2>(\n mapFn: (value: S) => S2,\n): <O extends UnknownOptional>(\n optional: Unwrap<O> extends S ? O : never,\n) => Optional<S2>;\n\nexport function map<O extends UnknownOptional, S2>(\n ...args:\n | readonly [optional: O, mapFn: (value: Unwrap<O>) => S2]\n | readonly [mapFn: (value: Unwrap<O>) => S2]\n): Optional<S2> | ((optional: O) => Optional<S2>) {\n switch (args.length) {\n case 2: {\n const [optional, mapFn] = args;\n\n return mapImpl(optional, mapFn);\n }\n\n case 1: {\n // Curried version: first argument is mapping function\n const [mapFn] = args;\n\n return (optional: O) => mapImpl(optional, mapFn);\n }\n }\n}\n\nconst mapImpl = <O extends UnknownOptional, S2>(\n optional: O,\n mapFn: (value: Unwrap<O>) => S2,\n): Optional<S2> =>\n isSome(optional)\n ? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n some(mapFn(unwrap(optional)!))\n : none;\n"],"names":[],"mappings":";;;;;AA6DO,SAAS,OACX,IAAA,EAG6C;AAChD,EAAA,QAAQ,KAAK,MAAA;AAAQ,IACnB,KAAK,CAAA,EAAG;AACN,MAAA,MAAM,CAAC,QAAA,EAAU,KAAK,CAAA,GAAI,IAAA;AAE1B,MAAA,OAAO,OAAA,CAAQ,UAAU,KAAK,CAAA;AAAA,IAChC;AAAA,IAEA,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,MAAA,OAAO,CAAC,QAAA,KAAgB,OAAA,CAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IACjD;AAAA;AAEJ;AAEA,MAAM,OAAA,GAAU,CACd,QAAA,EACA,KAAA,KAEA,OAAO,QAAQ,CAAA;AAAA;AAAA,EAEX,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,QAAQ,CAAE,CAAC;AAAA,IAC7B,IAAA;;;;"}
@@ -35,5 +35,5 @@ import { type UnwrapErr, type UnwrapOk } from './types.mjs';
35
35
  * @returns A new `Result<UnwrapOk<R>, E2>`.
36
36
  */
37
37
  export declare function mapErr<R extends UnknownResult, E2>(result: R, mapFn: (error: UnwrapErr<R>) => E2): Result<UnwrapOk<R>, E2>;
38
- export declare function mapErr<E, E2>(mapFn: (error: E) => E2): <S>(result: Result<S, E>) => Result<S, E2>;
38
+ export declare function mapErr<E, E2>(mapFn: (error: E) => E2): <R extends UnknownResult>(result: UnwrapErr<R> extends E ? R : never) => Result<UnwrapOk<R>, E2>;
39
39
  //# sourceMappingURL=result-map-err.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"result-map-err.d.mts","sourceRoot":"","sources":["../../../../src/functional/result/impl/result-map-err.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAGhE,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,aAAa,EAAE,EAAE,EAChD,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,GACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAG3B,wBAAgB,MAAM,CAAC,CAAC,EAAE,EAAE,EAC1B,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GACtB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC"}
1
+ {"version":3,"file":"result-map-err.d.mts","sourceRoot":"","sources":["../../../../src/functional/result/impl/result-map-err.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAGhE,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,aAAa,EAAE,EAAE,EAChD,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,EAAE,GACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAgB3B,wBAAgB,MAAM,CAAC,CAAC,EAAE,EAAE,EAC1B,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GACtB,CAAC,CAAC,SAAS,aAAa,EACzB,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,KACvC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"result-map-err.mjs","sources":["../../../../src/functional/result/impl/result-map-err.mts"],"sourcesContent":["import { type Ok } from '../../../types.mjs';\nimport { type Result, type UnknownResult } from '../result.mjs';\nimport { err } from './result-err.mjs';\nimport { isOk } from './result-is-ok.mjs';\nimport { type UnwrapErr, type UnwrapOk } from './types.mjs';\n\n/**\n * Maps a `Result<S, E>` to `Result<S, E2>` by applying a function to the\n * error value. If the `Result` is `Result.Ok`, returns the original `Ok`.\n *\n * @example\n *\n * ```ts\n * const okValue = Result.ok(3) as Result<number, string>;\n *\n * const errValue = Result.err('missing');\n *\n * const untouchedOk = Result.mapErr(okValue, (error) => error.toUpperCase());\n *\n * const uppercasedErr = Result.mapErr(errValue, (error) =>\n * error.toUpperCase(),\n * );\n *\n * assert.deepStrictEqual(untouchedOk, Result.ok(3));\n *\n * assert.deepStrictEqual(uppercasedErr, Result.err('MISSING'));\n *\n * const mapError = Result.mapErr((error: Readonly<Error>) => error.message);\n *\n * const wrapped = mapError(Result.err(new Error('boom')));\n *\n * assert.deepStrictEqual(wrapped, Result.err('boom'));\n * ```\n *\n * @template R The input `UnknownResult` type.\n * @template E2 The type of the error value returned by the mapping function.\n * @param result The `Result` to map.\n * @param mapFn The function to apply to the error value if present.\n * @returns A new `Result<UnwrapOk<R>, E2>`.\n */\nexport function mapErr<R extends UnknownResult, E2>(\n result: R,\n mapFn: (error: UnwrapErr<R>) => E2,\n): Result<UnwrapOk<R>, E2>;\n\n// Curried version\nexport function mapErr<E, E2>(\n mapFn: (error: E) => E2,\n): <S>(result: Result<S, E>) => Result<S, E2>;\n\nexport function mapErr<R extends UnknownResult, E2>(\n ...args:\n | readonly [result: R, mapFn: (error: UnwrapErr<R>) => E2]\n | readonly [mapFn: (error: UnwrapErr<R>) => E2]\n): Result<UnwrapOk<R>, E2> | ((result: R) => Result<UnwrapOk<R>, E2>) {\n switch (args.length) {\n case 2: {\n // Direct version: first argument is result\n const [result, mapFn] = args;\n\n return mapErrImpl(result, mapFn);\n }\n\n case 1: {\n // Curried version: first argument is mapping function\n const [mapFn] = args;\n\n return (result: R) => mapErrImpl(result, mapFn);\n }\n }\n}\n\nconst mapErrImpl = <R extends UnknownResult, E2>(\n result: R,\n mapFn: (error: UnwrapErr<R>) => E2,\n): Result<UnwrapOk<R>, E2> =>\n isOk(result)\n ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (result as Ok<UnwrapOk<R>>)\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n err(mapFn(result.value as UnwrapErr<R>));\n"],"names":[],"mappings":";;;AAkDO,SAAS,UACX,IAAA,EAGiE;AACpE,EAAA,QAAQ,KAAK,MAAA;AAAQ,IACnB,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA;AAExB,MAAA,OAAO,UAAA,CAAW,QAAQ,KAAK,CAAA;AAAA,IACjC;AAAA,IAEA,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,MAAA,OAAO,CAAC,MAAA,KAAc,UAAA,CAAW,MAAA,EAAQ,KAAK,CAAA;AAAA,IAChD;AAAA;AAEJ;AAEA,MAAM,UAAA,GAAa,CACjB,MAAA,EACA,KAAA,KAEA,KAAK,MAAM,CAAA;AAAA;AAAA,EAEN;AAAA;AAAA;AAAA,EAED,GAAA,CAAI,KAAA,CAAM,MAAA,CAAO,KAAqB,CAAC;AAAA,CAAA;;;;"}
1
+ {"version":3,"file":"result-map-err.mjs","sources":["../../../../src/functional/result/impl/result-map-err.mts"],"sourcesContent":["import { type Ok } from '../../../types.mjs';\nimport { type Result, type UnknownResult } from '../result.mjs';\nimport { err } from './result-err.mjs';\nimport { isOk } from './result-is-ok.mjs';\nimport { type UnwrapErr, type UnwrapOk } from './types.mjs';\n\n/**\n * Maps a `Result<S, E>` to `Result<S, E2>` by applying a function to the\n * error value. If the `Result` is `Result.Ok`, returns the original `Ok`.\n *\n * @example\n *\n * ```ts\n * const okValue = Result.ok(3) as Result<number, string>;\n *\n * const errValue = Result.err('missing');\n *\n * const untouchedOk = Result.mapErr(okValue, (error) => error.toUpperCase());\n *\n * const uppercasedErr = Result.mapErr(errValue, (error) =>\n * error.toUpperCase(),\n * );\n *\n * assert.deepStrictEqual(untouchedOk, Result.ok(3));\n *\n * assert.deepStrictEqual(uppercasedErr, Result.err('MISSING'));\n *\n * const mapError = Result.mapErr((error: Readonly<Error>) => error.message);\n *\n * const wrapped = mapError(Result.err(new Error('boom')));\n *\n * assert.deepStrictEqual(wrapped, Result.err('boom'));\n * ```\n *\n * @template R The input `UnknownResult` type.\n * @template E2 The type of the error value returned by the mapping function.\n * @param result The `Result` to map.\n * @param mapFn The function to apply to the error value if present.\n * @returns A new `Result<UnwrapOk<R>, E2>`.\n */\nexport function mapErr<R extends UnknownResult, E2>(\n result: R,\n mapFn: (error: UnwrapErr<R>) => E2,\n): Result<UnwrapOk<R>, E2>;\n\n// Curried version\n//\n// The returned function is generic over the *whole* result, mirroring the\n// direct overload above, rather than fixing its parameter to `Result<S, E>`.\n// Fixing it makes the curried form unusable from a caller that is itself\n// generic over the result: with `E` bound to `UnwrapErr<R>` at currying time the\n// parameter becomes `Result<...>` in terms of `R`, and TypeScript cannot show\n// a bare `R extends UnknownResult` is assignable to that — although every\n// concrete instantiation of `R` is.\n//\n// The conditional is what keeps the check: `R` is inferred from the argument\n// and its error type still has to satisfy `E`, so the mismatched case stays an\n// error. The cost is the message it produces — \"not assignable to `never`\"\n// rather than naming the two results.\nexport function mapErr<E, E2>(\n mapFn: (error: E) => E2,\n): <R extends UnknownResult>(\n result: UnwrapErr<R> extends E ? R : never,\n) => Result<UnwrapOk<R>, E2>;\n\nexport function mapErr<R extends UnknownResult, E2>(\n ...args:\n | readonly [result: R, mapFn: (error: UnwrapErr<R>) => E2]\n | readonly [mapFn: (error: UnwrapErr<R>) => E2]\n): Result<UnwrapOk<R>, E2> | ((result: R) => Result<UnwrapOk<R>, E2>) {\n switch (args.length) {\n case 2: {\n // Direct version: first argument is result\n const [result, mapFn] = args;\n\n return mapErrImpl(result, mapFn);\n }\n\n case 1: {\n // Curried version: first argument is mapping function\n const [mapFn] = args;\n\n return (result: R) => mapErrImpl(result, mapFn);\n }\n }\n}\n\nconst mapErrImpl = <R extends UnknownResult, E2>(\n result: R,\n mapFn: (error: UnwrapErr<R>) => E2,\n): Result<UnwrapOk<R>, E2> =>\n isOk(result)\n ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (result as Ok<UnwrapOk<R>>)\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n err(mapFn(result.value as UnwrapErr<R>));\n"],"names":[],"mappings":";;;AAiEO,SAAS,UACX,IAAA,EAGiE;AACpE,EAAA,QAAQ,KAAK,MAAA;AAAQ,IACnB,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA;AAExB,MAAA,OAAO,UAAA,CAAW,QAAQ,KAAK,CAAA;AAAA,IACjC;AAAA,IAEA,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,MAAA,OAAO,CAAC,MAAA,KAAc,UAAA,CAAW,MAAA,EAAQ,KAAK,CAAA;AAAA,IAChD;AAAA;AAEJ;AAEA,MAAM,UAAA,GAAa,CACjB,MAAA,EACA,KAAA,KAEA,KAAK,MAAM,CAAA;AAAA;AAAA,EAEN;AAAA;AAAA;AAAA,EAED,GAAA,CAAI,KAAA,CAAM,MAAA,CAAO,KAAqB,CAAC;AAAA,CAAA;;;;"}
@@ -35,5 +35,5 @@ import { type UnwrapErr, type UnwrapOk } from './types.mjs';
35
35
  * @returns A new `Result<S2, UnwrapErr<R>>`.
36
36
  */
37
37
  export declare function map<R extends UnknownResult, S2>(result: R, mapFn: (value: UnwrapOk<R>) => S2): Result<S2, UnwrapErr<R>>;
38
- export declare function map<S, S2>(mapFn: (value: S) => S2): <E>(result: Result<S, E>) => Result<S2, E>;
38
+ export declare function map<S, S2>(mapFn: (value: S) => S2): <R extends UnknownResult>(result: UnwrapOk<R> extends S ? R : never) => Result<S2, UnwrapErr<R>>;
39
39
  //# sourceMappingURL=result-map.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"result-map.d.mts","sourceRoot":"","sources":["../../../../src/functional/result/impl/result-map.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAGhE,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,aAAa,EAAE,EAAE,EAC7C,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAE,GAChC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAG5B,wBAAgB,GAAG,CAAC,CAAC,EAAE,EAAE,EACvB,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GACtB,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"result-map.d.mts","sourceRoot":"","sources":["../../../../src/functional/result/impl/result-map.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAGhE,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,GAAG,CAAC,CAAC,SAAS,aAAa,EAAE,EAAE,EAC7C,MAAM,EAAE,CAAC,EACT,KAAK,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,EAAE,GAChC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAgB5B,wBAAgB,GAAG,CAAC,CAAC,EAAE,EAAE,EACvB,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,GACtB,CAAC,CAAC,SAAS,aAAa,EACzB,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,KACtC,MAAM,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"result-map.mjs","sources":["../../../../src/functional/result/impl/result-map.mts"],"sourcesContent":["import { type Err } from '../../../types.mjs';\nimport { type Result, type UnknownResult } from '../result.mjs';\nimport { isErr } from './result-is-err.mjs';\nimport { ok } from './result-ok.mjs';\nimport { type UnwrapErr, type UnwrapOk } from './types.mjs';\n\n/**\n * Maps a `Result<S, E>` to `Result<S2, E>` by applying a function to the\n * success value. If the `Result` is `Result.Err`, returns the original\n * `Err`.\n *\n * @example\n *\n * ```ts\n * const okNumber = Result.ok(5);\n *\n * const errMessage = Result.err('error');\n *\n * const doubled = Result.map(okNumber, (value) => value * 2);\n *\n * const untouchedError = Result.map(errMessage, (value: number) => value * 2);\n *\n * assert.deepStrictEqual(doubled, Result.ok(10));\n *\n * assert.deepStrictEqual(untouchedError, errMessage);\n *\n * const mapToLength = Result.map((text: string) => text.length);\n *\n * assert.deepStrictEqual(mapToLength(Result.ok('abc')), Result.ok(3));\n *\n * assert.deepStrictEqual(mapToLength(Result.err('bad')), Result.err('bad'));\n * ```\n *\n * @template R The input `UnknownResult` type.\n * @template S2 The type of the success value returned by the mapping\n * function.\n * @param result The `Result` to map.\n * @param mapFn The function to apply to the success value if present.\n * @returns A new `Result<S2, UnwrapErr<R>>`.\n */\nexport function map<R extends UnknownResult, S2>(\n result: R,\n mapFn: (value: UnwrapOk<R>) => S2,\n): Result<S2, UnwrapErr<R>>;\n\n// Curried version\nexport function map<S, S2>(\n mapFn: (value: S) => S2,\n): <E>(result: Result<S, E>) => Result<S2, E>;\n\nexport function map<R extends UnknownResult, S2>(\n ...args:\n | readonly [result: R, mapFn: (value: UnwrapOk<R>) => S2]\n | readonly [mapFn: (value: UnwrapOk<R>) => S2]\n): Result<S2, UnwrapErr<R>> | ((result: R) => Result<S2, UnwrapErr<R>>) {\n switch (args.length) {\n case 2: {\n // Direct version: first argument is result\n const [result, mapFn] = args;\n\n return mapImpl(result, mapFn);\n }\n\n case 1: {\n // Curried version\n const [mapFn] = args;\n\n return (result: R) => mapImpl(result, mapFn);\n }\n }\n}\n\nconst mapImpl = <R extends UnknownResult, S2>(\n result: R,\n mapFn: (value: UnwrapOk<R>) => S2,\n): Result<S2, UnwrapErr<R>> =>\n isErr(result)\n ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (result as Err<UnwrapErr<R>>)\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n ok(mapFn(result.value as UnwrapOk<R>));\n"],"names":[],"mappings":";;;AAkDO,SAAS,OACX,IAAA,EAGmE;AACtE,EAAA,QAAQ,KAAK,MAAA;AAAQ,IACnB,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA;AAExB,MAAA,OAAO,OAAA,CAAQ,QAAQ,KAAK,CAAA;AAAA,IAC9B;AAAA,IAEA,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,MAAA,OAAO,CAAC,MAAA,KAAc,OAAA,CAAQ,MAAA,EAAQ,KAAK,CAAA;AAAA,IAC7C;AAAA;AAEJ;AAEA,MAAM,OAAA,GAAU,CACd,MAAA,EACA,KAAA,KAEA,MAAM,MAAM,CAAA;AAAA;AAAA,EAEP;AAAA;AAAA;AAAA,EAED,EAAA,CAAG,KAAA,CAAM,MAAA,CAAO,KAAoB,CAAC;AAAA,CAAA;;;;"}
1
+ {"version":3,"file":"result-map.mjs","sources":["../../../../src/functional/result/impl/result-map.mts"],"sourcesContent":["import { type Err } from '../../../types.mjs';\nimport { type Result, type UnknownResult } from '../result.mjs';\nimport { isErr } from './result-is-err.mjs';\nimport { ok } from './result-ok.mjs';\nimport { type UnwrapErr, type UnwrapOk } from './types.mjs';\n\n/**\n * Maps a `Result<S, E>` to `Result<S2, E>` by applying a function to the\n * success value. If the `Result` is `Result.Err`, returns the original\n * `Err`.\n *\n * @example\n *\n * ```ts\n * const okNumber = Result.ok(5);\n *\n * const errMessage = Result.err('error');\n *\n * const doubled = Result.map(okNumber, (value) => value * 2);\n *\n * const untouchedError = Result.map(errMessage, (value: number) => value * 2);\n *\n * assert.deepStrictEqual(doubled, Result.ok(10));\n *\n * assert.deepStrictEqual(untouchedError, errMessage);\n *\n * const mapToLength = Result.map((text: string) => text.length);\n *\n * assert.deepStrictEqual(mapToLength(Result.ok('abc')), Result.ok(3));\n *\n * assert.deepStrictEqual(mapToLength(Result.err('bad')), Result.err('bad'));\n * ```\n *\n * @template R The input `UnknownResult` type.\n * @template S2 The type of the success value returned by the mapping\n * function.\n * @param result The `Result` to map.\n * @param mapFn The function to apply to the success value if present.\n * @returns A new `Result<S2, UnwrapErr<R>>`.\n */\nexport function map<R extends UnknownResult, S2>(\n result: R,\n mapFn: (value: UnwrapOk<R>) => S2,\n): Result<S2, UnwrapErr<R>>;\n\n// Curried version\n//\n// The returned function is generic over the *whole* result, mirroring the\n// direct overload above, rather than fixing its parameter to `Result<S, E>`.\n// Fixing it makes the curried form unusable from a caller that is itself\n// generic over the result: with `S` bound to `UnwrapOk<R>` at currying time the\n// parameter becomes `Result<...>` in terms of `R`, and TypeScript cannot show\n// a bare `R extends UnknownResult` is assignable to that — although every\n// concrete instantiation of `R` is.\n//\n// The conditional is what keeps the check: `R` is inferred from the argument\n// and its success type still has to satisfy `S`, so the mismatched case stays an\n// error. The cost is the message it produces — \"not assignable to `never`\"\n// rather than naming the two results.\nexport function map<S, S2>(\n mapFn: (value: S) => S2,\n): <R extends UnknownResult>(\n result: UnwrapOk<R> extends S ? R : never,\n) => Result<S2, UnwrapErr<R>>;\n\nexport function map<R extends UnknownResult, S2>(\n ...args:\n | readonly [result: R, mapFn: (value: UnwrapOk<R>) => S2]\n | readonly [mapFn: (value: UnwrapOk<R>) => S2]\n): Result<S2, UnwrapErr<R>> | ((result: R) => Result<S2, UnwrapErr<R>>) {\n switch (args.length) {\n case 2: {\n // Direct version: first argument is result\n const [result, mapFn] = args;\n\n return mapImpl(result, mapFn);\n }\n\n case 1: {\n // Curried version\n const [mapFn] = args;\n\n return (result: R) => mapImpl(result, mapFn);\n }\n }\n}\n\nconst mapImpl = <R extends UnknownResult, S2>(\n result: R,\n mapFn: (value: UnwrapOk<R>) => S2,\n): Result<S2, UnwrapErr<R>> =>\n isErr(result)\n ? // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n (result as Err<UnwrapErr<R>>)\n : // eslint-disable-next-line total-functions/no-unsafe-type-assertion\n ok(mapFn(result.value as UnwrapOk<R>));\n"],"names":[],"mappings":";;;AAiEO,SAAS,OACX,IAAA,EAGmE;AACtE,EAAA,QAAQ,KAAK,MAAA;AAAQ,IACnB,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,MAAA,EAAQ,KAAK,CAAA,GAAI,IAAA;AAExB,MAAA,OAAO,OAAA,CAAQ,QAAQ,KAAK,CAAA;AAAA,IAC9B;AAAA,IAEA,KAAK,CAAA,EAAG;AAEN,MAAA,MAAM,CAAC,KAAK,CAAA,GAAI,IAAA;AAEhB,MAAA,OAAO,CAAC,MAAA,KAAc,OAAA,CAAQ,MAAA,EAAQ,KAAK,CAAA;AAAA,IAC7C;AAAA;AAEJ;AAEA,MAAM,OAAA,GAAU,CACd,MAAA,EACA,KAAA,KAEA,MAAM,MAAM,CAAA;AAAA;AAAA,EAEP;AAAA;AAAA;AAAA,EAED,EAAA,CAAG,KAAA,CAAM,MAAA,CAAO,KAAoB,CAAC;AAAA,CAAA;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-data-forge",
3
- "version": "14.1.0",
3
+ "version": "14.2.1",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript",
@@ -12,7 +12,8 @@
12
12
  ],
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git+https://github.com/noshiro-pf/ts-data-forge.git"
15
+ "url": "git+https://github.com/noshiro-pf/mono.git",
16
+ "directory": "libs/ts-data-forge"
16
17
  },
17
18
  "license": "Apache-2.0",
18
19
  "author": "noshiro-pf <noshiro.pf@gmail.com>",
@@ -36,17 +37,31 @@
36
37
  ],
37
38
  "dependencies": {
38
39
  "@sindresorhus/is": "^8.1.0",
39
- "ts-type-forge": "~9.1.3"
40
+ "ts-type-forge": "~9.2.1"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@emotion/react": "11.14.0",
43
44
  "@emotion/styled": "11.14.1",
44
45
  "@mui/material": "9.2.0",
46
+ "@rollup/plugin-replace": "6.0.3",
47
+ "@rollup/plugin-strip": "3.0.4",
45
48
  "@types/react": "19.2.18",
49
+ "@vitest/browser-playwright": "4.1.10",
50
+ "dedent": "1.7.2",
46
51
  "immer": "11.1.15",
52
+ "prettier": "3.9.6",
53
+ "prettier-plugin-organize-imports": "4.3.0",
54
+ "prettier-plugin-packagejson": "3.0.2",
47
55
  "react": "19.2.8",
56
+ "rollup": "4.62.4",
57
+ "rollup-plugin-esbuild": "6.2.1",
48
58
  "typedoc": "0.28.20",
49
- "typedoc-github-theme": "0.4.0"
59
+ "typedoc-github-theme": "0.4.0",
60
+ "vite": "8.2.0",
61
+ "eslint-config-typed": "5.8.5",
62
+ "eslint-plugin-ts-fortress": "1.0.1",
63
+ "eslint-plugin-ts-type-forge": "0.6.3",
64
+ "ts-repo-utils": "10.2.0"
50
65
  },
51
66
  "peerDependencies": {
52
67
  "typescript": ">=4.8"
@@ -60,20 +75,20 @@
60
75
  "provenance": true
61
76
  },
62
77
  "scripts": {
63
- "build": "tsx ./scripts/cmd/build.mts",
64
- "build:min": "tsx ./scripts/cmd/build.mts --skip-check",
65
- "check:ext": "tsx ./scripts/cmd/check-ext.mts",
66
- "doc": "tsx ./scripts/cmd/gen-docs.mts",
67
- "doc:embed": "tsx ./scripts/cmd/embed-examples.mts",
68
- "doc:embed:jsdoc": "tsx ./scripts/cmd/embed-examples-in-jsdoc.mts",
78
+ "build": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts",
79
+ "build:min": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/build.mts --skip-check",
80
+ "check:ext": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/check-ext.mts",
81
+ "doc": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/gen-docs.mts",
82
+ "doc:embed": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples.mts",
83
+ "doc:embed:jsdoc": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/cmd/embed-examples-in-jsdoc.mts",
69
84
  "doc:preview": "vite preview --config ./configs/vite.doc.config.mts",
70
85
  "doc:watch": "typedoc --options ./configs/typedoc.config.mjs --watch",
71
- "fmt": "format-uncommitted --cwd .",
72
- "fmt:diff": "format-diff-from origin/main --cwd .",
86
+ "fmt": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-uncommitted.mts --cwd .",
87
+ "fmt:diff": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ../../libs/ts-repo-utils/src/cmd/format-diff-from.mts origin/main --cwd .",
73
88
  "fmt:full": "pnpm run z:prettier .",
74
- "gen:branded-number": "tsx ./scripts/gen-branded-number/generate.mts && pnpm run doc:embed:jsdoc && pnpm run z:prettier 'src/number/branded-types/*.mts' 'src/number/enum/*.mts'",
89
+ "gen:branded-number": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json ./scripts/gen-branded-number/generate.mts && pnpm run doc:embed:jsdoc && pnpm run z:prettier 'src/number/branded-types/*.mts' 'src/number/enum/*.mts'",
75
90
  "gi": "run-s gi:src fmt",
76
- "gi:src": "tsx scripts/cmd/gen-index.mts",
91
+ "gi:src": "tsx --tsconfig ../../tools/configs/tsconfig.tsx.json scripts/cmd/gen-index.mts",
77
92
  "lint": "eslint .",
78
93
  "lint:fix": "eslint . --fix",
79
94
  "test": "pnpm run z:vitest:node run",
@@ -40,9 +40,24 @@ export function map<O extends UnknownOptional, S2>(
40
40
  ): Optional<S2>;
41
41
 
42
42
  // Curried version
43
+ //
44
+ // The returned function is generic over the *whole* optional, mirroring the
45
+ // direct overload above, rather than fixing its parameter to `Optional<S>`.
46
+ // Fixing it makes the curried form unusable from a caller that is itself
47
+ // generic over the optional: with `S` bound to `Unwrap<O>` at currying time
48
+ // the parameter becomes `Optional<Unwrap<O>>`, and TypeScript cannot show a
49
+ // bare `O extends UnknownOptional` is assignable to that — although every
50
+ // concrete instantiation of `O` is.
51
+ //
52
+ // The conditional is what keeps the check: `O` is inferred from the argument
53
+ // and its value type still has to satisfy `S`, so mapping a `(s: string) =>
54
+ // …` over an `Optional<number>` stays an error. The cost is the message it
55
+ // produces — "not assignable to `never`" rather than naming the two optionals.
43
56
  export function map<S, S2>(
44
57
  mapFn: (value: S) => S2,
45
- ): (optional: Optional<S>) => Optional<S2>;
58
+ ): <O extends UnknownOptional>(
59
+ optional: Unwrap<O> extends S ? O : never,
60
+ ) => Optional<S2>;
46
61
 
47
62
  export function map<O extends UnknownOptional, S2>(
48
63
  ...args:
@@ -1,7 +1,7 @@
1
1
  import { expectType } from '../expect-type.mjs';
2
2
  import { Num } from '../number/index.mjs';
3
3
  import { type None, type Some } from '../types.mjs';
4
- import { Optional } from './optional/index.mjs';
4
+ import { Optional, type UnknownOptional } from './optional/index.mjs';
5
5
  import { pipe } from './pipe.mjs';
6
6
  import { Result } from './result/index.mjs';
7
7
 
@@ -144,6 +144,53 @@ describe('Optional test', () => {
144
144
  }
145
145
  });
146
146
 
147
+ test('curried form is usable from a caller generic over the optional', () => {
148
+ // The motivating case: with the curried parameter fixed to
149
+ // `Optional<S>`, `S` binds to `Optional.Unwrap<O>` here and a bare
150
+ // `O extends UnknownOptional` is not assignable to
151
+ // `Optional<Optional.Unwrap<O>>`, so this only compiles because the
152
+ // returned function is generic over the whole optional.
153
+ const liftMap =
154
+ <O extends UnknownOptional, B>(mapFn: (x: Optional.Unwrap<O>) => B) =>
155
+ (optional: O): Optional<B> =>
156
+ Optional.map(mapFn)(optional);
157
+
158
+ const lengthOf = liftMap<Optional<string>, number>((s) => s.length);
159
+
160
+ const mapped = lengthOf(Optional.some('abc'));
161
+
162
+ expectType<typeof mapped, Optional<number>>('=');
163
+
164
+ assert.deepStrictEqual(mapped, Optional.some(3));
165
+
166
+ assert.deepStrictEqual(lengthOf(Optional.none), Optional.none);
167
+ });
168
+
169
+ test('curried form still infers the result precisely', () => {
170
+ const mapToLength = Optional.map((text: string) => text.length);
171
+
172
+ const mapped = mapToLength(Optional.some('abc'));
173
+
174
+ expectType<typeof mapped, Optional<number>>('=');
175
+
176
+ assert.deepStrictEqual(mapped, Optional.some(3));
177
+
178
+ const empty = mapToLength(Optional.none);
179
+
180
+ expectType<typeof empty, Optional<number>>('=');
181
+
182
+ assert.deepStrictEqual(empty, Optional.none);
183
+ });
184
+
185
+ test('curried form still rejects a mismatched value type', () => {
186
+ const mapToLength = Optional.map((text: string) => text.length);
187
+
188
+ // @ts-expect-error an Optional<number> is not an Optional<string>
189
+ const rejected = (): unknown => mapToLength(Optional.some(42));
190
+
191
+ expect(rejected).toBeTypeOf('function');
192
+ });
193
+
147
194
  test('should preserve types correctly', () => {
148
195
  const some = Optional.some(42);
149
196
 
@@ -44,9 +44,24 @@ export function mapErr<R extends UnknownResult, E2>(
44
44
  ): Result<UnwrapOk<R>, E2>;
45
45
 
46
46
  // Curried version
47
+ //
48
+ // The returned function is generic over the *whole* result, mirroring the
49
+ // direct overload above, rather than fixing its parameter to `Result<S, E>`.
50
+ // Fixing it makes the curried form unusable from a caller that is itself
51
+ // generic over the result: with `E` bound to `UnwrapErr<R>` at currying time the
52
+ // parameter becomes `Result<...>` in terms of `R`, and TypeScript cannot show
53
+ // a bare `R extends UnknownResult` is assignable to that — although every
54
+ // concrete instantiation of `R` is.
55
+ //
56
+ // The conditional is what keeps the check: `R` is inferred from the argument
57
+ // and its error type still has to satisfy `E`, so the mismatched case stays an
58
+ // error. The cost is the message it produces — "not assignable to `never`"
59
+ // rather than naming the two results.
47
60
  export function mapErr<E, E2>(
48
61
  mapFn: (error: E) => E2,
49
- ): <S>(result: Result<S, E>) => Result<S, E2>;
62
+ ): <R extends UnknownResult>(
63
+ result: UnwrapErr<R> extends E ? R : never,
64
+ ) => Result<UnwrapOk<R>, E2>;
50
65
 
51
66
  export function mapErr<R extends UnknownResult, E2>(
52
67
  ...args:
@@ -44,9 +44,24 @@ export function map<R extends UnknownResult, S2>(
44
44
  ): Result<S2, UnwrapErr<R>>;
45
45
 
46
46
  // Curried version
47
+ //
48
+ // The returned function is generic over the *whole* result, mirroring the
49
+ // direct overload above, rather than fixing its parameter to `Result<S, E>`.
50
+ // Fixing it makes the curried form unusable from a caller that is itself
51
+ // generic over the result: with `S` bound to `UnwrapOk<R>` at currying time the
52
+ // parameter becomes `Result<...>` in terms of `R`, and TypeScript cannot show
53
+ // a bare `R extends UnknownResult` is assignable to that — although every
54
+ // concrete instantiation of `R` is.
55
+ //
56
+ // The conditional is what keeps the check: `R` is inferred from the argument
57
+ // and its success type still has to satisfy `S`, so the mismatched case stays an
58
+ // error. The cost is the message it produces — "not assignable to `never`"
59
+ // rather than naming the two results.
47
60
  export function map<S, S2>(
48
61
  mapFn: (value: S) => S2,
49
- ): <E>(result: Result<S, E>) => Result<S2, E>;
62
+ ): <R extends UnknownResult>(
63
+ result: UnwrapOk<R> extends S ? R : never,
64
+ ) => Result<S2, UnwrapErr<R>>;
50
65
 
51
66
  export function map<R extends UnknownResult, S2>(
52
67
  ...args:
@@ -3,7 +3,7 @@ import { Num } from '../number/index.mjs';
3
3
  import { type Err, type Ok } from '../types.mjs';
4
4
  import { Optional } from './optional/index.mjs';
5
5
  import { pipe } from './pipe.mjs';
6
- import { Result } from './result/index.mjs';
6
+ import { Result, type UnknownResult } from './result/index.mjs';
7
7
 
8
8
  describe('Result test', () => {
9
9
  describe('ok', () => {
@@ -174,6 +174,49 @@ describe('Result test', () => {
174
174
 
175
175
  expect(result.value).toBe('10');
176
176
  });
177
+
178
+ test('curried form is usable from a caller generic over the result', () => {
179
+ // The motivating case: with the curried parameter fixed to
180
+ // `Result<S, E>`, `S` binds to `Result.UnwrapOk<R>` here and a bare
181
+ // `R extends UnknownResult` is not assignable to that, so this only
182
+ // compiles because the returned function is generic over the whole
183
+ // result.
184
+ const liftMap =
185
+ <R extends UnknownResult, S2>(mapFn: (x: Result.UnwrapOk<R>) => S2) =>
186
+ (result: R): Result<S2, Result.UnwrapErr<R>> =>
187
+ Result.map(mapFn)(result);
188
+
189
+ const lengthOf = liftMap<Result<string, 'boom'>, number>((s) => s.length);
190
+
191
+ const mapped = lengthOf(Result.ok('abc'));
192
+
193
+ expectType<typeof mapped, Result<number, 'boom'>>('=');
194
+
195
+ assert.deepStrictEqual(mapped, Result.ok(3));
196
+
197
+ assert.deepStrictEqual(lengthOf(Result.err('boom')), Result.err('boom'));
198
+ });
199
+
200
+ test('curried form keeps the error type and rejects a mismatch', () => {
201
+ const mapToLength = Result.map((text: string) => text.length);
202
+
203
+ const mapped = mapToLength(Result.ok('abc'));
204
+
205
+ expectType<typeof mapped, Result<number, never>>('=');
206
+
207
+ assert.deepStrictEqual(mapped, Result.ok(3));
208
+
209
+ const failed = mapToLength(Result.err('boom' as const));
210
+
211
+ expectType<typeof failed, Result<number, 'boom'>>('=');
212
+
213
+ assert.deepStrictEqual(failed, Result.err('boom'));
214
+
215
+ // @ts-expect-error a Result<number, …> is not a Result<string, …>
216
+ const rejected = (): unknown => mapToLength(Result.ok(42));
217
+
218
+ expect(rejected).toBeTypeOf('function');
219
+ });
177
220
  });
178
221
 
179
222
  describe('mapErr', () => {
@@ -234,6 +277,40 @@ describe('Result test', () => {
234
277
 
235
278
  expect(result.value).toBe('ERROR: FAILED');
236
279
  });
280
+
281
+ test('curried form is usable from a caller generic over the result', () => {
282
+ const liftMapErr =
283
+ <R extends UnknownResult, E2>(mapFn: (x: Result.UnwrapErr<R>) => E2) =>
284
+ (result: R): Result<Result.UnwrapOk<R>, E2> =>
285
+ Result.mapErr(mapFn)(result);
286
+
287
+ const shout = liftMapErr<Result<number, string>, string>((e) =>
288
+ e.toUpperCase(),
289
+ );
290
+
291
+ const mapped = shout(Result.err('failed'));
292
+
293
+ expectType<typeof mapped, Result<number, string>>('=');
294
+
295
+ assert.deepStrictEqual(mapped, Result.err('FAILED'));
296
+
297
+ assert.deepStrictEqual(shout(Result.ok(1)), Result.ok(1));
298
+ });
299
+
300
+ test('curried form keeps the ok type and rejects a mismatch', () => {
301
+ const shout = Result.mapErr((e: string) => e.toUpperCase());
302
+
303
+ const mapped = shout(Result.err('failed'));
304
+
305
+ expectType<typeof mapped, Result<never, string>>('=');
306
+
307
+ assert.deepStrictEqual(mapped, Result.err('FAILED'));
308
+
309
+ // @ts-expect-error a Result<…, number> is not a Result<…, string>
310
+ const rejected = (): unknown => shout(Result.err(42));
311
+
312
+ expect(rejected).toBeTypeOf('function');
313
+ });
237
314
  });
238
315
 
239
316
  describe('unwrapThrow', () => {