ts-data-forge 14.5.0 → 14.6.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/dist/array/impl/array-utils-element-access.mjs +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs +3 -3
- package/dist/array/impl/array-utils-search.mjs +1 -1
- package/dist/collections/imap-mapped.mjs +5 -4
- package/dist/collections/imap-mapped.mjs.map +1 -1
- package/dist/collections/imap.mjs +1 -1
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.d.mts.map +1 -1
- package/dist/collections/iset-mapped.mjs +3 -1
- package/dist/collections/iset-mapped.mjs.map +1 -1
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/entry-point.mjs +8 -6
- package/dist/entry-point.mjs.map +1 -1
- package/dist/functional/async-result/async-result.d.mts +16 -0
- package/dist/functional/async-result/async-result.d.mts.map +1 -0
- package/dist/functional/async-result/async-result.mjs +3 -0
- package/dist/functional/async-result/async-result.mjs.map +1 -0
- package/dist/functional/async-result/impl/async-result-flat-map.d.mts +39 -0
- package/dist/functional/async-result/impl/async-result-flat-map.d.mts.map +1 -0
- package/dist/functional/async-result/impl/async-result-flat-map.mjs +25 -0
- package/dist/functional/async-result/impl/async-result-flat-map.mjs.map +1 -0
- package/dist/functional/async-result/impl/async-result-from-promise.d.mts +47 -0
- package/dist/functional/async-result/impl/async-result-from-promise.d.mts.map +1 -0
- package/dist/functional/async-result/impl/async-result-from-promise.mjs +11 -0
- package/dist/functional/async-result/impl/async-result-from-promise.mjs.map +1 -0
- package/dist/functional/async-result/impl/async-result-from-throwable.d.mts +53 -0
- package/dist/functional/async-result/impl/async-result-from-throwable.d.mts.map +1 -0
- package/dist/functional/async-result/impl/async-result-from-throwable.mjs +17 -0
- package/dist/functional/async-result/impl/async-result-from-throwable.mjs.map +1 -0
- package/dist/functional/async-result/impl/async-result-map-err.d.mts +35 -0
- package/dist/functional/async-result/impl/async-result-map-err.d.mts.map +1 -0
- package/dist/functional/async-result/impl/async-result-map-err.mjs +23 -0
- package/dist/functional/async-result/impl/async-result-map-err.mjs.map +1 -0
- package/dist/functional/async-result/impl/async-result-map.d.mts +45 -0
- package/dist/functional/async-result/impl/async-result-map.d.mts.map +1 -0
- package/dist/functional/async-result/impl/async-result-map.mjs +23 -0
- package/dist/functional/async-result/impl/async-result-map.mjs.map +1 -0
- package/dist/functional/async-result/impl/async-result-unwrap-or.d.mts +34 -0
- package/dist/functional/async-result/impl/async-result-unwrap-or.d.mts.map +1 -0
- package/dist/functional/async-result/impl/async-result-unwrap-or.mjs +10 -0
- package/dist/functional/async-result/impl/async-result-unwrap-or.mjs.map +1 -0
- package/dist/functional/async-result/impl/index.d.mts +8 -0
- package/dist/functional/async-result/impl/index.d.mts.map +1 -0
- package/dist/functional/async-result/impl/index.mjs +7 -0
- package/dist/functional/async-result/impl/index.mjs.map +1 -0
- package/dist/functional/async-result/impl/types.d.mts +45 -0
- package/dist/functional/async-result/impl/types.d.mts.map +1 -0
- package/dist/functional/async-result/impl/types.mjs +2 -0
- package/dist/functional/async-result/impl/types.mjs.map +1 -0
- package/dist/functional/async-result/index.d.mts +2 -0
- package/dist/functional/async-result/index.d.mts.map +1 -0
- package/dist/functional/async-result/index.mjs +3 -0
- package/dist/functional/async-result/index.mjs.map +1 -0
- package/dist/functional/index.d.mts +1 -0
- package/dist/functional/index.d.mts.map +1 -1
- package/dist/functional/index.mjs +8 -6
- package/dist/functional/index.mjs.map +1 -1
- package/dist/functional/result/impl/result-from-promise.d.mts +5 -0
- package/dist/functional/result/impl/result-from-promise.d.mts.map +1 -1
- package/dist/functional/result/impl/result-from-promise.mjs.map +1 -1
- package/dist/json/json.mjs +2 -2
- package/dist/number/enum/int8.mjs +1 -1
- package/dist/number/enum/int8.mjs.map +1 -1
- package/dist/number/refined-number-utils.mjs +1 -1
- package/dist/object/object.mjs +1 -1
- package/package.json +7 -7
- package/src/array/impl/array-utils-length-constraint-preservation.test.mts +2 -2
- package/src/collections/imap-mapped.mts +4 -4
- package/src/collections/imap.mts +1 -1
- package/src/collections/imap.test.mts +1 -1
- package/src/collections/iset-mapped.mts +2 -1
- package/src/collections/iset.mts +1 -1
- package/src/functional/async-result/async-result.mts +17 -0
- package/src/functional/async-result/impl/async-result-flat-map.mts +99 -0
- package/src/functional/async-result/impl/async-result-from-promise.mts +69 -0
- package/src/functional/async-result/impl/async-result-from-throwable.mts +78 -0
- package/src/functional/async-result/impl/async-result-map-err.mts +84 -0
- package/src/functional/async-result/impl/async-result-map.mts +94 -0
- package/src/functional/async-result/impl/async-result-unwrap-or.mts +42 -0
- package/src/functional/async-result/impl/index.mts +7 -0
- package/src/functional/async-result/impl/types.mts +51 -0
- package/src/functional/async-result/index.mts +1 -0
- package/src/functional/async-result.test.mts +291 -0
- package/src/functional/index.mts +1 -0
- package/src/functional/optional.test.mts +2 -1
- package/src/functional/result/impl/result-from-promise.mts +5 -0
- package/src/functional/result.test.mts +0 -2
- package/src/guard/is-non-empty-string.test.mts +2 -2
- package/src/guard/is-non-null-object.test.mts +3 -3
- package/src/guard/is-primitive.test.mts +3 -3
- package/src/guard/is-type.test.mts +3 -3
- package/src/json/json.test.mts +1 -1
- package/src/number/enum/int8.mts +1 -1
|
@@ -32,7 +32,7 @@ describe('map preserves length constraints', () => {
|
|
|
32
32
|
test('a branded input keeps its constraint', () => {
|
|
33
33
|
const input: MinLengthArray<3, number> = asMinLengthArray(3, [0, 1, 2, 3]);
|
|
34
34
|
|
|
35
|
-
const result = map(input, (value) => `#${
|
|
35
|
+
const result = map(input, (value) => `#${value.toString()}`);
|
|
36
36
|
|
|
37
37
|
expectType<typeof result, MinLengthArray<3, string>>('~=');
|
|
38
38
|
|
|
@@ -162,7 +162,7 @@ describe('other length-preserving utilities preserve length constraints', () =>
|
|
|
162
162
|
test('toUpdated keeps the brand and widens only the element type', () => {
|
|
163
163
|
const input: MinLengthArray<3, number> = asMinLengthArray(3, [0, 1, 2]);
|
|
164
164
|
|
|
165
|
-
const result = toUpdated(input, 1, (prev) => `#${
|
|
165
|
+
const result = toUpdated(input, 1, (prev) => `#${prev.toString()}`);
|
|
166
166
|
|
|
167
167
|
expectType<typeof result, MinLengthArray<3, number | `#${string}`>>('~=');
|
|
168
168
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { type WidenLiteral } from 'ts-type-forge';
|
|
3
3
|
import { Optional, pipe } from '../functional/index.mjs';
|
|
4
4
|
import { asUint32 } from '../number/index.mjs';
|
|
5
|
-
import { tp } from '../others/index.mjs';
|
|
5
|
+
import { tp, unknownToString } from '../others/index.mjs';
|
|
6
6
|
import { type MapSetKeyType, type SizeType } from '../types.mjs';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -1051,7 +1051,7 @@ class IMapMappedClass<K, V, KM extends MapSetKeyType>
|
|
|
1051
1051
|
if (!this.has(key)) {
|
|
1052
1052
|
if (this.#showNotFoundMessage) {
|
|
1053
1053
|
console.warn(
|
|
1054
|
-
`IMapMapped.delete: key not found: ${
|
|
1054
|
+
`IMapMapped.delete: key not found: ${unknownToString(this.#toKey(key))}`,
|
|
1055
1055
|
);
|
|
1056
1056
|
}
|
|
1057
1057
|
|
|
@@ -1103,7 +1103,7 @@ class IMapMappedClass<K, V, KM extends MapSetKeyType>
|
|
|
1103
1103
|
if (Optional.isNone(curr)) {
|
|
1104
1104
|
if (this.#showNotFoundMessage) {
|
|
1105
1105
|
console.warn(
|
|
1106
|
-
`IMapMapped.update: key not found: ${
|
|
1106
|
+
`IMapMapped.update: key not found: ${unknownToString(this.#toKey(key))}`,
|
|
1107
1107
|
);
|
|
1108
1108
|
}
|
|
1109
1109
|
|
|
@@ -1160,7 +1160,7 @@ class IMapMappedClass<K, V, KM extends MapSetKeyType>
|
|
|
1160
1160
|
if (curr === undefined || !mut_result.has(key)) {
|
|
1161
1161
|
if (this.#showNotFoundMessage) {
|
|
1162
1162
|
console.warn(
|
|
1163
|
-
`IMapMapped.withMutations::update: key not found: ${
|
|
1163
|
+
`IMapMapped.withMutations::update: key not found: ${unknownToString(key)}`,
|
|
1164
1164
|
);
|
|
1165
1165
|
}
|
|
1166
1166
|
|
package/src/collections/imap.mts
CHANGED
|
@@ -753,7 +753,7 @@ class IMapClass<K extends MapSetKeyType, V>
|
|
|
753
753
|
|
|
754
754
|
/** @inheritdoc */
|
|
755
755
|
has(key: K | (WidenLiteral<K> & {})): boolean {
|
|
756
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
756
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, @typescript-eslint/no-unnecessary-type-assertion -- unnecessary only under the strict standard library; the stock one types `has` as taking exactly `K`
|
|
757
757
|
return this.#map.has(key as K);
|
|
758
758
|
}
|
|
759
759
|
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type WidenLiteral,
|
|
6
6
|
} from 'ts-type-forge';
|
|
7
7
|
import { asUint32 } from '../number/index.mjs';
|
|
8
|
+
import { unknownToString } from '../others/index.mjs';
|
|
8
9
|
import { type MapSetKeyType, type SizeType } from '../types.mjs';
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -1270,7 +1271,7 @@ class ISetMappedClass<K, KM extends MapSetKeyType>
|
|
|
1270
1271
|
if (!this.has(key)) {
|
|
1271
1272
|
if (this.#showNotFoundMessage) {
|
|
1272
1273
|
console.warn(
|
|
1273
|
-
`ISetMapped.delete: key not found: ${
|
|
1274
|
+
`ISetMapped.delete: key not found: ${unknownToString(this.#toKey(key))}`,
|
|
1274
1275
|
);
|
|
1275
1276
|
}
|
|
1276
1277
|
|
package/src/collections/iset.mts
CHANGED
|
@@ -753,7 +753,7 @@ class ISetClass<K extends MapSetKeyType> implements ISet<K>, Iterable<K> {
|
|
|
753
753
|
|
|
754
754
|
/** @inheritdoc */
|
|
755
755
|
has(key: K | (WidenLiteral<K> & {})): boolean {
|
|
756
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
756
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, @typescript-eslint/no-unnecessary-type-assertion -- unnecessary only under the strict standard library; the stock one types `has` as taking exactly `K`
|
|
757
757
|
return this.#set.has(key as K);
|
|
758
758
|
}
|
|
759
759
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Result } from '../result/index.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents an asynchronous computation that can either succeed (`Ok`) or
|
|
5
|
+
* fail (`Err`) — a `Promise` that resolves to a `Result<S, E>`.
|
|
6
|
+
*
|
|
7
|
+
* Unlike a raw `Promise`, an `AsyncResult` never rejects for expected
|
|
8
|
+
* failures: errors are carried in the `Err` channel with a concrete type `E`,
|
|
9
|
+
* so they can be handled with the same combinators as a synchronous
|
|
10
|
+
* `Result`.
|
|
11
|
+
*
|
|
12
|
+
* @template S The type of the success value.
|
|
13
|
+
* @template E The type of the error value.
|
|
14
|
+
*/
|
|
15
|
+
export type AsyncResult<S, E> = Promise<Result<S, E>>;
|
|
16
|
+
|
|
17
|
+
export * as AsyncResult from './impl/index.mjs';
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Result, type UnknownResult } from '../../result/index.mjs';
|
|
2
|
+
import { type AsyncResult } from '../async-result.mjs';
|
|
3
|
+
import {
|
|
4
|
+
type UnknownAsyncResult,
|
|
5
|
+
type UnwrapErr,
|
|
6
|
+
type UnwrapOk,
|
|
7
|
+
} from './types.mjs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Applies a function that returns an `AsyncResult` or a `Result` to the
|
|
11
|
+
* success value of an `AsyncResult`. If the input resolves to `Err`, the
|
|
12
|
+
* original `Err` is passed through and the function is not called. This is
|
|
13
|
+
* the monadic bind operation for `AsyncResult`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* const parseNumber = (input: string): Result<number, string> => {
|
|
19
|
+
* const num = Number.parseInt(input, 10);
|
|
20
|
+
*
|
|
21
|
+
* return Number.isNaN(num) ? Result.err('not a number') : Result.ok(num);
|
|
22
|
+
* };
|
|
23
|
+
*
|
|
24
|
+
* const input = AsyncResult.fromPromise(
|
|
25
|
+
* Promise.resolve('42'),
|
|
26
|
+
* () => 'failed',
|
|
27
|
+
* );
|
|
28
|
+
*
|
|
29
|
+
* const parsed = await AsyncResult.flatMap(input, parseNumber);
|
|
30
|
+
*
|
|
31
|
+
* assert.deepStrictEqual(parsed, Result.ok(42));
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @template S The type of the success value of the input.
|
|
35
|
+
* @template E The type of the error value of the input.
|
|
36
|
+
* @template R2 The `AsyncResult` or `Result` type returned by the function.
|
|
37
|
+
* @param asyncResult The `AsyncResult` to flat map.
|
|
38
|
+
* @param flatMapFn The function to apply that returns an `AsyncResult` or a
|
|
39
|
+
* `Result`.
|
|
40
|
+
* @returns The result of applying the function, or the original `Err`.
|
|
41
|
+
*/
|
|
42
|
+
export function flatMap<S, E, R2 extends UnknownAsyncResult | UnknownResult>(
|
|
43
|
+
asyncResult: AsyncResult<S, E>,
|
|
44
|
+
flatMapFn: (value: S) => R2,
|
|
45
|
+
): AsyncResult<UnwrapOk<R2>, E | UnwrapErr<R2>>;
|
|
46
|
+
|
|
47
|
+
// Curried version
|
|
48
|
+
//
|
|
49
|
+
// The function's return type is captured as a whole (`R2`) and unwrapped with
|
|
50
|
+
// `UnwrapOk` / `UnwrapErr`, instead of being written as
|
|
51
|
+
// `AsyncResult<S2, E2> | Result<S2, E2>` with separate `S2` / `E2` type
|
|
52
|
+
// parameters. With the union form, inference for `E2` collects a candidate
|
|
53
|
+
// from matching `Ok<S2>` against `Err<E2>` (both have a `value` property), so
|
|
54
|
+
// `E2` ends up as the success type and the call fails to type-check even for
|
|
55
|
+
// a function annotated as returning `Result<S2, E2>`.
|
|
56
|
+
export function flatMap<S, R2 extends UnknownAsyncResult | UnknownResult>(
|
|
57
|
+
flatMapFn: (value: S) => R2,
|
|
58
|
+
): <E>(
|
|
59
|
+
asyncResult: AsyncResult<S, E>,
|
|
60
|
+
) => AsyncResult<UnwrapOk<R2>, E | UnwrapErr<R2>>;
|
|
61
|
+
|
|
62
|
+
export function flatMap<S, E, R2 extends UnknownAsyncResult | UnknownResult>(
|
|
63
|
+
...args:
|
|
64
|
+
| readonly [asyncResult: AsyncResult<S, E>, flatMapFn: (value: S) => R2]
|
|
65
|
+
| readonly [flatMapFn: (value: S) => R2]
|
|
66
|
+
):
|
|
67
|
+
| AsyncResult<UnwrapOk<R2>, E | UnwrapErr<R2>>
|
|
68
|
+
| ((
|
|
69
|
+
asyncResult: AsyncResult<S, E>,
|
|
70
|
+
) => AsyncResult<UnwrapOk<R2>, E | UnwrapErr<R2>>) {
|
|
71
|
+
switch (args.length) {
|
|
72
|
+
case 2: {
|
|
73
|
+
// Direct version: first argument is asyncResult
|
|
74
|
+
const [asyncResult, flatMapFn] = args;
|
|
75
|
+
|
|
76
|
+
return flatMapImpl(asyncResult, flatMapFn);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 1: {
|
|
80
|
+
// Curried version
|
|
81
|
+
const [flatMapFn] = args;
|
|
82
|
+
|
|
83
|
+
return (asyncResult: AsyncResult<S, E>) =>
|
|
84
|
+
flatMapImpl(asyncResult, flatMapFn);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const flatMapImpl = async <S, E, R2 extends UnknownAsyncResult | UnknownResult>(
|
|
90
|
+
asyncResult: AsyncResult<S, E>,
|
|
91
|
+
flatMapFn: (value: S) => R2,
|
|
92
|
+
): AsyncResult<UnwrapOk<R2>, E | UnwrapErr<R2>> => {
|
|
93
|
+
const result = await asyncResult;
|
|
94
|
+
|
|
95
|
+
return Result.isErr(result)
|
|
96
|
+
? result
|
|
97
|
+
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
98
|
+
((await flatMapFn(result.value)) as Result<UnwrapOk<R2>, UnwrapErr<R2>>);
|
|
99
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Result } from '../../result/index.mjs';
|
|
2
|
+
import { type AsyncResult } from '../async-result.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Converts a `Promise` into an `AsyncResult`. If the input Promise resolves,
|
|
6
|
+
* the `AsyncResult` resolves to `Ok` with the resolved value. If the input
|
|
7
|
+
* Promise rejects, the rejection reason is passed through `mapError` and the
|
|
8
|
+
* `AsyncResult` resolves to `Err` with the mapped error.
|
|
9
|
+
*
|
|
10
|
+
* Passing `mapError` is what gives the error channel a concrete type `E`.
|
|
11
|
+
* Omitting it carries the rejection reason through untouched, leaving the
|
|
12
|
+
* error channel as `unknown` — the same contract as `Result.fromPromise`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const resolved = await AsyncResult.fromPromise(
|
|
18
|
+
* Promise.resolve('ok'),
|
|
19
|
+
* (error) => `failed: ${String(error)}`,
|
|
20
|
+
* );
|
|
21
|
+
*
|
|
22
|
+
* const rejected = await AsyncResult.fromPromise(
|
|
23
|
+
* Promise.reject(new Error('boom')),
|
|
24
|
+
* (error) => (isError(error) ? error.message : 'unknown'),
|
|
25
|
+
* );
|
|
26
|
+
*
|
|
27
|
+
* assert.deepStrictEqual(resolved, Result.ok('ok'));
|
|
28
|
+
*
|
|
29
|
+
* assert.deepStrictEqual(rejected, Result.err('boom'));
|
|
30
|
+
*
|
|
31
|
+
* // Without `mapError`, the rejection reason is carried as-is.
|
|
32
|
+
* const untyped = await AsyncResult.fromPromise(
|
|
33
|
+
* Promise.reject(new Error('boom')),
|
|
34
|
+
* );
|
|
35
|
+
*
|
|
36
|
+
* assert.isTrue(Result.isErr(untyped));
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @template S The type of the success value.
|
|
40
|
+
* @template E The type of the error value.
|
|
41
|
+
* @param promise The Promise to convert.
|
|
42
|
+
* @param mapError The function applied to the rejection reason to produce the
|
|
43
|
+
* error value. Omit it to carry the rejection reason as `unknown`.
|
|
44
|
+
* @returns An `AsyncResult<S, E>` that never rejects for the input Promise's
|
|
45
|
+
* failure.
|
|
46
|
+
*/
|
|
47
|
+
export function fromPromise<S>(promise: Promise<S>): AsyncResult<S, unknown>;
|
|
48
|
+
|
|
49
|
+
export function fromPromise<S, E>(
|
|
50
|
+
promise: Promise<S>,
|
|
51
|
+
mapError: (error: unknown) => E,
|
|
52
|
+
): AsyncResult<S, E>;
|
|
53
|
+
|
|
54
|
+
export function fromPromise<S, E>(
|
|
55
|
+
promise: Promise<S>,
|
|
56
|
+
mapError?: (error: unknown) => E,
|
|
57
|
+
): AsyncResult<S, E> | AsyncResult<S, unknown> {
|
|
58
|
+
return mapError === undefined
|
|
59
|
+
? fromPromiseImpl(promise, (error: unknown) => error)
|
|
60
|
+
: fromPromiseImpl(promise, mapError);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const fromPromiseImpl = <S, E>(
|
|
64
|
+
promise: Promise<S>,
|
|
65
|
+
mapError: (error: unknown) => E,
|
|
66
|
+
): AsyncResult<S, E> =>
|
|
67
|
+
promise
|
|
68
|
+
.then((value) => Result.ok(value))
|
|
69
|
+
.catch((error: unknown) => Result.err(mapError(error)));
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Result } from '../../result/index.mjs';
|
|
2
|
+
import { type AsyncResult } from '../async-result.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a function returning a `Promise` in an `AsyncResult`. Both a
|
|
6
|
+
* synchronous `throw` while calling `fn` and an asynchronous rejection of the
|
|
7
|
+
* returned Promise are caught, passed through `mapError`, and carried as
|
|
8
|
+
* `Err`.
|
|
9
|
+
*
|
|
10
|
+
* Passing `mapError` is what gives the error channel a concrete type `E`.
|
|
11
|
+
* Omitting it carries the thrown value through untouched, leaving the error
|
|
12
|
+
* channel as `unknown`.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* const success = await AsyncResult.fromThrowable(
|
|
18
|
+
* () => Promise.resolve(2),
|
|
19
|
+
* (error) => `failed: ${String(error)}`,
|
|
20
|
+
* );
|
|
21
|
+
*
|
|
22
|
+
* const failure = await AsyncResult.fromThrowable(
|
|
23
|
+
* (): Promise<number> => {
|
|
24
|
+
* throw new Error('boom');
|
|
25
|
+
* },
|
|
26
|
+
* (error) => (isError(error) ? error.message : 'unknown'),
|
|
27
|
+
* );
|
|
28
|
+
*
|
|
29
|
+
* assert.deepStrictEqual(success, Result.ok(2));
|
|
30
|
+
*
|
|
31
|
+
* assert.deepStrictEqual(failure, Result.err('boom'));
|
|
32
|
+
*
|
|
33
|
+
* // Without `mapError`, the thrown value is carried as-is.
|
|
34
|
+
* const untyped = await AsyncResult.fromThrowable(
|
|
35
|
+
* (): Promise<number> => {
|
|
36
|
+
* throw new Error('boom');
|
|
37
|
+
* },
|
|
38
|
+
* );
|
|
39
|
+
*
|
|
40
|
+
* assert.isTrue(Result.isErr(untyped));
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @template S The type of the success value.
|
|
44
|
+
* @template E The type of the error value.
|
|
45
|
+
* @param fn The function to execute; it may throw synchronously or return a
|
|
46
|
+
* rejecting Promise.
|
|
47
|
+
* @param mapError The function applied to the thrown value or rejection
|
|
48
|
+
* reason to produce the error value. Omit it to carry the thrown value as
|
|
49
|
+
* `unknown`.
|
|
50
|
+
* @returns An `AsyncResult<S, E>` containing either the resolved value or the
|
|
51
|
+
* mapped error.
|
|
52
|
+
*/
|
|
53
|
+
export function fromThrowable<S>(fn: () => Promise<S>): AsyncResult<S, unknown>;
|
|
54
|
+
|
|
55
|
+
export function fromThrowable<S, E>(
|
|
56
|
+
fn: () => Promise<S>,
|
|
57
|
+
mapError: (error: unknown) => E,
|
|
58
|
+
): AsyncResult<S, E>;
|
|
59
|
+
|
|
60
|
+
export function fromThrowable<S, E>(
|
|
61
|
+
fn: () => Promise<S>,
|
|
62
|
+
mapError?: (error: unknown) => E,
|
|
63
|
+
): AsyncResult<S, E> | AsyncResult<S, unknown> {
|
|
64
|
+
return mapError === undefined
|
|
65
|
+
? fromThrowableImpl(fn, (error: unknown) => error)
|
|
66
|
+
: fromThrowableImpl(fn, mapError);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const fromThrowableImpl = async <S, E>(
|
|
70
|
+
fn: () => Promise<S>,
|
|
71
|
+
mapError: (error: unknown) => E,
|
|
72
|
+
): AsyncResult<S, E> => {
|
|
73
|
+
try {
|
|
74
|
+
return Result.ok(await fn());
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return Result.err(mapError(error));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Result } from '../../result/index.mjs';
|
|
2
|
+
import { type AsyncResult } from '../async-result.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Maps an `AsyncResult<S, E>` to `AsyncResult<S, E2>` by applying a function
|
|
6
|
+
* to the error value. The mapping function may be synchronous or return a
|
|
7
|
+
* `Promise`; both are handled transparently. If the `AsyncResult` resolves to
|
|
8
|
+
* `Ok`, the original `Ok` is passed through and the function is not called.
|
|
9
|
+
*
|
|
10
|
+
* If `mapFn` throws or returns a rejecting Promise, the rejection propagates
|
|
11
|
+
* as-is — it is **not** caught and converted into an `Err`.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const errValue = AsyncResult.fromPromise(
|
|
17
|
+
* Promise.reject(new Error('boom')),
|
|
18
|
+
* () => 'failure',
|
|
19
|
+
* );
|
|
20
|
+
*
|
|
21
|
+
* const uppercased = await AsyncResult.mapErr(errValue, (error) =>
|
|
22
|
+
* error.toUpperCase(),
|
|
23
|
+
* );
|
|
24
|
+
*
|
|
25
|
+
* assert.deepStrictEqual(uppercased, Result.err('FAILURE'));
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @template S The type of the success value.
|
|
29
|
+
* @template E The type of the error value of the input.
|
|
30
|
+
* @template E2 The type of the error value returned by the mapping function.
|
|
31
|
+
* @param asyncResult The `AsyncResult` to map.
|
|
32
|
+
* @param mapFn The function to apply to the error value if present.
|
|
33
|
+
* @returns A new `AsyncResult<S, E2>`.
|
|
34
|
+
*/
|
|
35
|
+
export function mapErr<S, E, E2>(
|
|
36
|
+
asyncResult: AsyncResult<S, E>,
|
|
37
|
+
mapFn: (error: E) => E2 | Promise<E2>,
|
|
38
|
+
): AsyncResult<S, E2>;
|
|
39
|
+
|
|
40
|
+
// Curried version
|
|
41
|
+
//
|
|
42
|
+
// Unlike `Result.mapErr`, the returned function can simply be generic over
|
|
43
|
+
// `S`: an `AsyncResult` is a `Promise`, not a discriminated union, so there
|
|
44
|
+
// is no literal-narrowing problem that would require the whole-result
|
|
45
|
+
// generic trick used by the synchronous combinators.
|
|
46
|
+
export function mapErr<E, E2>(
|
|
47
|
+
mapFn: (error: E) => E2 | Promise<E2>,
|
|
48
|
+
): <S>(asyncResult: AsyncResult<S, E>) => AsyncResult<S, E2>;
|
|
49
|
+
|
|
50
|
+
export function mapErr<S, E, E2>(
|
|
51
|
+
...args:
|
|
52
|
+
| readonly [
|
|
53
|
+
asyncResult: AsyncResult<S, E>,
|
|
54
|
+
mapFn: (error: E) => E2 | Promise<E2>,
|
|
55
|
+
]
|
|
56
|
+
| readonly [mapFn: (error: E) => E2 | Promise<E2>]
|
|
57
|
+
):
|
|
58
|
+
| AsyncResult<S, E2>
|
|
59
|
+
| ((asyncResult: AsyncResult<S, E>) => AsyncResult<S, E2>) {
|
|
60
|
+
switch (args.length) {
|
|
61
|
+
case 2: {
|
|
62
|
+
// Direct version: first argument is asyncResult
|
|
63
|
+
const [asyncResult, mapFn] = args;
|
|
64
|
+
|
|
65
|
+
return mapErrImpl(asyncResult, mapFn);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case 1: {
|
|
69
|
+
// Curried version: first argument is mapping function
|
|
70
|
+
const [mapFn] = args;
|
|
71
|
+
|
|
72
|
+
return (asyncResult: AsyncResult<S, E>) => mapErrImpl(asyncResult, mapFn);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const mapErrImpl = async <S, E, E2>(
|
|
78
|
+
asyncResult: AsyncResult<S, E>,
|
|
79
|
+
mapFn: (error: E) => E2 | Promise<E2>,
|
|
80
|
+
): AsyncResult<S, E2> => {
|
|
81
|
+
const result = await asyncResult;
|
|
82
|
+
|
|
83
|
+
return Result.isOk(result) ? result : Result.err(await mapFn(result.value));
|
|
84
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { Result } from '../../result/index.mjs';
|
|
2
|
+
import { type AsyncResult } from '../async-result.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Maps an `AsyncResult<S, E>` to `AsyncResult<S2, E>` by applying a function
|
|
6
|
+
* to the success value. The mapping function may be synchronous or return a
|
|
7
|
+
* `Promise`; both are handled transparently. If the `AsyncResult` resolves to
|
|
8
|
+
* `Err`, the original `Err` is passed through and the function is not called.
|
|
9
|
+
*
|
|
10
|
+
* If `mapFn` throws or returns a rejecting Promise, the rejection propagates
|
|
11
|
+
* as-is — it is **not** caught and converted into an `Err`. `map` performs no
|
|
12
|
+
* implicit error conversion; write a fallible step as
|
|
13
|
+
* `AsyncResult.flatMap` combined with `AsyncResult.fromThrowable` instead, so
|
|
14
|
+
* that the error type stays explicit.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
*
|
|
18
|
+
* ```ts
|
|
19
|
+
* const okValue = AsyncResult.fromPromise(
|
|
20
|
+
* Promise.resolve(5),
|
|
21
|
+
* () => 'failed',
|
|
22
|
+
* );
|
|
23
|
+
*
|
|
24
|
+
* const doubled = await AsyncResult.map(okValue, (value) => value * 2);
|
|
25
|
+
*
|
|
26
|
+
* assert.deepStrictEqual(doubled, Result.ok(10));
|
|
27
|
+
*
|
|
28
|
+
* const double = AsyncResult.map((value: number) => value * 2);
|
|
29
|
+
*
|
|
30
|
+
* const curried = await double(
|
|
31
|
+
* AsyncResult.fromPromise(Promise.resolve(21), () => 'failed'),
|
|
32
|
+
* );
|
|
33
|
+
*
|
|
34
|
+
* assert.deepStrictEqual(curried, Result.ok(42));
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* @template S The type of the success value of the input.
|
|
38
|
+
* @template S2 The type of the success value returned by the mapping
|
|
39
|
+
* function.
|
|
40
|
+
* @template E The type of the error value.
|
|
41
|
+
* @param asyncResult The `AsyncResult` to map.
|
|
42
|
+
* @param mapFn The function to apply to the success value if present.
|
|
43
|
+
* @returns A new `AsyncResult<S2, E>`.
|
|
44
|
+
*/
|
|
45
|
+
export function map<S, S2, E>(
|
|
46
|
+
asyncResult: AsyncResult<S, E>,
|
|
47
|
+
mapFn: (value: S) => Promise<S2> | S2,
|
|
48
|
+
): AsyncResult<S2, E>;
|
|
49
|
+
|
|
50
|
+
// Curried version
|
|
51
|
+
//
|
|
52
|
+
// Unlike `Result.map`, the returned function can simply be generic over `E`:
|
|
53
|
+
// an `AsyncResult` is a `Promise`, not a discriminated union, so there is no
|
|
54
|
+
// literal-narrowing problem that would require the whole-result generic
|
|
55
|
+
// trick used by the synchronous combinators.
|
|
56
|
+
export function map<S, S2>(
|
|
57
|
+
mapFn: (value: S) => Promise<S2> | S2,
|
|
58
|
+
): <E>(asyncResult: AsyncResult<S, E>) => AsyncResult<S2, E>;
|
|
59
|
+
|
|
60
|
+
export function map<S, S2, E>(
|
|
61
|
+
...args:
|
|
62
|
+
| readonly [
|
|
63
|
+
asyncResult: AsyncResult<S, E>,
|
|
64
|
+
mapFn: (value: S) => Promise<S2> | S2,
|
|
65
|
+
]
|
|
66
|
+
| readonly [mapFn: (value: S) => Promise<S2> | S2]
|
|
67
|
+
):
|
|
68
|
+
| AsyncResult<S2, E>
|
|
69
|
+
| ((asyncResult: AsyncResult<S, E>) => AsyncResult<S2, E>) {
|
|
70
|
+
switch (args.length) {
|
|
71
|
+
case 2: {
|
|
72
|
+
// Direct version: first argument is asyncResult
|
|
73
|
+
const [asyncResult, mapFn] = args;
|
|
74
|
+
|
|
75
|
+
return mapImpl(asyncResult, mapFn);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case 1: {
|
|
79
|
+
// Curried version
|
|
80
|
+
const [mapFn] = args;
|
|
81
|
+
|
|
82
|
+
return (asyncResult: AsyncResult<S, E>) => mapImpl(asyncResult, mapFn);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const mapImpl = async <S, S2, E>(
|
|
88
|
+
asyncResult: AsyncResult<S, E>,
|
|
89
|
+
mapFn: (value: S) => Promise<S2> | S2,
|
|
90
|
+
): AsyncResult<S2, E> => {
|
|
91
|
+
const result = await asyncResult;
|
|
92
|
+
|
|
93
|
+
return Result.isErr(result) ? result : Result.ok(await mapFn(result.value));
|
|
94
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Result } from '../../result/index.mjs';
|
|
2
|
+
import { type AsyncResult } from '../async-result.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Unwraps an `AsyncResult`, resolving to the success value or a default value
|
|
6
|
+
* if it resolves to `Err`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
*
|
|
10
|
+
* ```ts
|
|
11
|
+
* const okValue = AsyncResult.fromPromise(
|
|
12
|
+
* Promise.resolve(10),
|
|
13
|
+
* () => 'failed',
|
|
14
|
+
* );
|
|
15
|
+
*
|
|
16
|
+
* const errValue = AsyncResult.fromPromise(
|
|
17
|
+
* Promise.reject(new Error('boom')),
|
|
18
|
+
* () => 'failed',
|
|
19
|
+
* );
|
|
20
|
+
*
|
|
21
|
+
* assert.isTrue((await AsyncResult.unwrapOr(okValue, 0)) === 10);
|
|
22
|
+
*
|
|
23
|
+
* assert.isTrue((await AsyncResult.unwrapOr(errValue, 0)) === 0);
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* @template S The type of the success value.
|
|
27
|
+
* @template E The type of the error value.
|
|
28
|
+
* @template D The type of the default value.
|
|
29
|
+
* @param asyncResult The `AsyncResult` to unwrap.
|
|
30
|
+
* @param defaultValue The value to resolve to if `asyncResult` resolves to
|
|
31
|
+
* `Err`.
|
|
32
|
+
* @returns A Promise of the success value if `Ok`, otherwise of
|
|
33
|
+
* `defaultValue`.
|
|
34
|
+
*/
|
|
35
|
+
export const unwrapOr = async <S, E, D>(
|
|
36
|
+
asyncResult: AsyncResult<S, E>,
|
|
37
|
+
defaultValue: D,
|
|
38
|
+
): Promise<D | S> => {
|
|
39
|
+
const result = await asyncResult;
|
|
40
|
+
|
|
41
|
+
return Result.isOk(result) ? result.value : defaultValue;
|
|
42
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from './async-result-flat-map.mjs';
|
|
2
|
+
export * from './async-result-from-promise.mjs';
|
|
3
|
+
export * from './async-result-from-throwable.mjs';
|
|
4
|
+
export * from './async-result-map-err.mjs';
|
|
5
|
+
export * from './async-result-map.mjs';
|
|
6
|
+
export * from './async-result-unwrap-or.mjs';
|
|
7
|
+
export * from './types.mjs';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type Err, type Ok } from '../../../types.mjs';
|
|
2
|
+
import { type UnknownResult } from '../../result/index.mjs';
|
|
3
|
+
import { type AsyncResult } from '../async-result.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base type for any `AsyncResult`, used for generic constraints. Represents
|
|
7
|
+
* an `AsyncResult` with unknown success and error types.
|
|
8
|
+
*/
|
|
9
|
+
export type UnknownAsyncResult = AsyncResult<unknown, unknown>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extracts the success value type `S` from a `Result.Ok<S>`, or from an
|
|
13
|
+
* `AsyncResult` resolving to one. Distributes over unions, so an
|
|
14
|
+
* `Ok<S> | Err<E>` union resolves to `S`.
|
|
15
|
+
*
|
|
16
|
+
* @template R The `Result` or `AsyncResult` type to unwrap.
|
|
17
|
+
*/
|
|
18
|
+
export type UnwrapOk<R extends UnknownAsyncResult | UnknownResult> =
|
|
19
|
+
UnwrapOkImpl<SyncResultOf<R>>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extracts the error value type `E` from a `Result.Err<E>`, or from an
|
|
23
|
+
* `AsyncResult` resolving to one. Distributes over unions, so an
|
|
24
|
+
* `Ok<S> | Err<E>` union resolves to `E`.
|
|
25
|
+
*
|
|
26
|
+
* @template R The `Result` or `AsyncResult` type to unwrap.
|
|
27
|
+
*/
|
|
28
|
+
export type UnwrapErr<R extends UnknownAsyncResult | UnknownResult> =
|
|
29
|
+
UnwrapErrImpl<SyncResultOf<R>>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @template R The `Result` or `AsyncResult` type.
|
|
33
|
+
* @internal
|
|
34
|
+
* Resolves an `AsyncResult` to its `Result`, leaving a plain `Result`
|
|
35
|
+
* unchanged. Distributive.
|
|
36
|
+
*/
|
|
37
|
+
type SyncResultOf<R> = R extends Promise<infer P> ? P : R;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @template R The `Result` type to unwrap.
|
|
41
|
+
* @internal
|
|
42
|
+
* Distributive extraction of the `Ok` value type.
|
|
43
|
+
*/
|
|
44
|
+
type UnwrapOkImpl<R> = R extends Ok<infer S> ? S : never;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @template R The `Result` type to unwrap.
|
|
48
|
+
* @internal
|
|
49
|
+
* Distributive extraction of the `Err` value type.
|
|
50
|
+
*/
|
|
51
|
+
type UnwrapErrImpl<R> = R extends Err<infer E> ? E : never;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './async-result.mjs';
|