ts-data-forge 14.4.0 → 14.6.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 (123) hide show
  1. package/README.md +6 -0
  2. package/dist/array/impl/array-utils-element-access.mjs +1 -1
  3. package/dist/array/impl/array-utils-reducing-value.mjs +3 -3
  4. package/dist/array/impl/array-utils-search.d.mts.map +1 -1
  5. package/dist/array/impl/array-utils-search.mjs +1 -1
  6. package/dist/array/impl/array-utils-search.mjs.map +1 -1
  7. package/dist/collections/imap-mapped.mjs +1 -1
  8. package/dist/collections/imap.mjs +1 -1
  9. package/dist/entry-point.mjs +8 -6
  10. package/dist/entry-point.mjs.map +1 -1
  11. package/dist/functional/async-result/async-result.d.mts +16 -0
  12. package/dist/functional/async-result/async-result.d.mts.map +1 -0
  13. package/dist/functional/async-result/async-result.mjs +3 -0
  14. package/dist/functional/async-result/async-result.mjs.map +1 -0
  15. package/dist/functional/async-result/impl/async-result-flat-map.d.mts +39 -0
  16. package/dist/functional/async-result/impl/async-result-flat-map.d.mts.map +1 -0
  17. package/dist/functional/async-result/impl/async-result-flat-map.mjs +25 -0
  18. package/dist/functional/async-result/impl/async-result-flat-map.mjs.map +1 -0
  19. package/dist/functional/async-result/impl/async-result-from-promise.d.mts +47 -0
  20. package/dist/functional/async-result/impl/async-result-from-promise.d.mts.map +1 -0
  21. package/dist/functional/async-result/impl/async-result-from-promise.mjs +11 -0
  22. package/dist/functional/async-result/impl/async-result-from-promise.mjs.map +1 -0
  23. package/dist/functional/async-result/impl/async-result-from-throwable.d.mts +53 -0
  24. package/dist/functional/async-result/impl/async-result-from-throwable.d.mts.map +1 -0
  25. package/dist/functional/async-result/impl/async-result-from-throwable.mjs +17 -0
  26. package/dist/functional/async-result/impl/async-result-from-throwable.mjs.map +1 -0
  27. package/dist/functional/async-result/impl/async-result-map-err.d.mts +35 -0
  28. package/dist/functional/async-result/impl/async-result-map-err.d.mts.map +1 -0
  29. package/dist/functional/async-result/impl/async-result-map-err.mjs +23 -0
  30. package/dist/functional/async-result/impl/async-result-map-err.mjs.map +1 -0
  31. package/dist/functional/async-result/impl/async-result-map.d.mts +45 -0
  32. package/dist/functional/async-result/impl/async-result-map.d.mts.map +1 -0
  33. package/dist/functional/async-result/impl/async-result-map.mjs +23 -0
  34. package/dist/functional/async-result/impl/async-result-map.mjs.map +1 -0
  35. package/dist/functional/async-result/impl/async-result-unwrap-or.d.mts +34 -0
  36. package/dist/functional/async-result/impl/async-result-unwrap-or.d.mts.map +1 -0
  37. package/dist/functional/async-result/impl/async-result-unwrap-or.mjs +10 -0
  38. package/dist/functional/async-result/impl/async-result-unwrap-or.mjs.map +1 -0
  39. package/dist/functional/async-result/impl/index.d.mts +8 -0
  40. package/dist/functional/async-result/impl/index.d.mts.map +1 -0
  41. package/dist/functional/async-result/impl/index.mjs +7 -0
  42. package/dist/functional/async-result/impl/index.mjs.map +1 -0
  43. package/dist/functional/async-result/impl/types.d.mts +45 -0
  44. package/dist/functional/async-result/impl/types.d.mts.map +1 -0
  45. package/dist/functional/async-result/impl/types.mjs +2 -0
  46. package/dist/functional/async-result/impl/types.mjs.map +1 -0
  47. package/dist/functional/async-result/index.d.mts +2 -0
  48. package/dist/functional/async-result/index.d.mts.map +1 -0
  49. package/dist/functional/async-result/index.mjs +3 -0
  50. package/dist/functional/async-result/index.mjs.map +1 -0
  51. package/dist/functional/index.d.mts +1 -0
  52. package/dist/functional/index.d.mts.map +1 -1
  53. package/dist/functional/index.mjs +8 -6
  54. package/dist/functional/index.mjs.map +1 -1
  55. package/dist/functional/optional/impl/index.d.mts +1 -0
  56. package/dist/functional/optional/impl/index.d.mts.map +1 -1
  57. package/dist/functional/optional/impl/index.mjs +1 -0
  58. package/dist/functional/optional/impl/index.mjs.map +1 -1
  59. package/dist/functional/optional/impl/optional-match.d.mts +48 -0
  60. package/dist/functional/optional/impl/optional-match.d.mts.map +1 -0
  61. package/dist/functional/optional/impl/optional-match.mjs +22 -0
  62. package/dist/functional/optional/impl/optional-match.mjs.map +1 -0
  63. package/dist/functional/result/impl/index.d.mts +4 -0
  64. package/dist/functional/result/impl/index.d.mts.map +1 -1
  65. package/dist/functional/result/impl/index.mjs +4 -0
  66. package/dist/functional/result/impl/index.mjs.map +1 -1
  67. package/dist/functional/result/impl/result-from-optional.d.mts +41 -0
  68. package/dist/functional/result/impl/result-from-optional.d.mts.map +1 -0
  69. package/dist/functional/result/impl/result-from-optional.mjs +24 -0
  70. package/dist/functional/result/impl/result-from-optional.mjs.map +1 -0
  71. package/dist/functional/result/impl/result-from-promise.d.mts +5 -0
  72. package/dist/functional/result/impl/result-from-promise.d.mts.map +1 -1
  73. package/dist/functional/result/impl/result-from-promise.mjs.map +1 -1
  74. package/dist/functional/result/impl/result-match.d.mts +47 -0
  75. package/dist/functional/result/impl/result-match.d.mts.map +1 -0
  76. package/dist/functional/result/impl/result-match.mjs +24 -0
  77. package/dist/functional/result/impl/result-match.mjs.map +1 -0
  78. package/dist/functional/result/impl/result-safe-try.d.mts +51 -0
  79. package/dist/functional/result/impl/result-safe-try.d.mts.map +1 -0
  80. package/dist/functional/result/impl/result-safe-try.mjs +24 -0
  81. package/dist/functional/result/impl/result-safe-try.mjs.map +1 -0
  82. package/dist/functional/result/impl/result-safe-unwrap.d.mts +36 -0
  83. package/dist/functional/result/impl/result-safe-unwrap.d.mts.map +1 -0
  84. package/dist/functional/result/impl/result-safe-unwrap.mjs +14 -0
  85. package/dist/functional/result/impl/result-safe-unwrap.mjs.map +1 -0
  86. package/dist/json/json.mjs +2 -2
  87. package/dist/number/refined-number-utils.mjs +1 -1
  88. package/dist/object/object.mjs +1 -1
  89. package/dist/others/debounce-function.d.mts.map +1 -1
  90. package/dist/others/debounce-function.mjs.map +1 -1
  91. package/dist/others/fast-deep-equal.d.mts.map +1 -1
  92. package/dist/others/fast-deep-equal.mjs +1 -1
  93. package/dist/others/fast-deep-equal.mjs.map +1 -1
  94. package/package.json +10 -10
  95. package/src/array/impl/array-utils-modification.test.mts +3 -3
  96. package/src/array/impl/array-utils-search.mts +22 -8
  97. package/src/array/impl/array-utils-shape-invariants.test.mts +3 -3
  98. package/src/array/impl/array-utils-transformation.test.mts +1 -1
  99. package/src/functional/async-result/async-result.mts +17 -0
  100. package/src/functional/async-result/impl/async-result-flat-map.mts +99 -0
  101. package/src/functional/async-result/impl/async-result-from-promise.mts +69 -0
  102. package/src/functional/async-result/impl/async-result-from-throwable.mts +78 -0
  103. package/src/functional/async-result/impl/async-result-map-err.mts +84 -0
  104. package/src/functional/async-result/impl/async-result-map.mts +94 -0
  105. package/src/functional/async-result/impl/async-result-unwrap-or.mts +42 -0
  106. package/src/functional/async-result/impl/index.mts +7 -0
  107. package/src/functional/async-result/impl/types.mts +51 -0
  108. package/src/functional/async-result/index.mts +1 -0
  109. package/src/functional/async-result.test.mts +290 -0
  110. package/src/functional/index.mts +1 -0
  111. package/src/functional/optional/impl/index.mts +1 -0
  112. package/src/functional/optional/impl/optional-match.mts +108 -0
  113. package/src/functional/optional.test.mts +79 -0
  114. package/src/functional/result/impl/index.mts +4 -0
  115. package/src/functional/result/impl/result-from-optional.mts +80 -0
  116. package/src/functional/result/impl/result-from-promise.mts +5 -0
  117. package/src/functional/result/impl/result-match.mts +108 -0
  118. package/src/functional/result/impl/result-safe-try.mts +110 -0
  119. package/src/functional/result/impl/result-safe-unwrap.mts +50 -0
  120. package/src/functional/result.test.mts +331 -0
  121. package/src/guard/is-error.test.mts +7 -7
  122. package/src/others/debounce-function.mts +5 -1
  123. package/src/others/fast-deep-equal.mts +9 -2
@@ -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';
@@ -0,0 +1,290 @@
1
+ import { isError } from '@sindresorhus/is';
2
+ import { AsyncResult as AsyncResultFromEntryPoint } from '../entry-point.mjs';
3
+ import { expectType } from '../expect-type.mjs';
4
+ import { AsyncResult } from './async-result/index.mjs';
5
+ import { Result } from './result/index.mjs';
6
+
7
+ // Fixtures are created through functions so that the success/error types stay
8
+ // unions instead of being narrowed to literal types by const inference.
9
+ const okFixture = (value: number): AsyncResult<number, string> =>
10
+ Promise.resolve(Result.ok(value));
11
+
12
+ const errFixture = (message: string): AsyncResult<number, string> =>
13
+ Promise.resolve(Result.err(message));
14
+
15
+ describe('AsyncResult test', () => {
16
+ describe('fromPromise', () => {
17
+ test('resolved promise becomes Ok', async () => {
18
+ const result = await AsyncResult.fromPromise(
19
+ Promise.resolve(42),
20
+ (error) => `mapped: ${String(error)}`,
21
+ );
22
+
23
+ assert.isTrue(Result.isOk(result));
24
+
25
+ assert.deepStrictEqual(result, Result.ok(42));
26
+ });
27
+
28
+ test('rejected promise becomes Err with mapError applied', async () => {
29
+ const result = await AsyncResult.fromPromise(
30
+ Promise.reject(new Error('boom')),
31
+ (error) => (isError(error) ? error.message : 'unknown'),
32
+ );
33
+
34
+ assert.isTrue(Result.isErr(result));
35
+
36
+ assert.deepStrictEqual(result, Result.err('boom'));
37
+ });
38
+
39
+ test('resolved promise becomes Ok when mapError is omitted', async () => {
40
+ const result = await AsyncResult.fromPromise(Promise.resolve(42));
41
+
42
+ assert.deepStrictEqual(result, Result.ok(42));
43
+
44
+ expectType<typeof result, Result<number, unknown>>('=');
45
+ });
46
+
47
+ test('rejection reason is carried as-is when mapError is omitted', async () => {
48
+ const error = new Error('boom');
49
+
50
+ const result = await AsyncResult.fromPromise(
51
+ Promise.reject(error).then(() => 42),
52
+ );
53
+
54
+ assert.isTrue(Result.isErr(result));
55
+
56
+ assert.deepStrictEqual(result, Result.err(error));
57
+ });
58
+ });
59
+
60
+ describe('fromThrowable', () => {
61
+ test('resolved async function becomes Ok', async () => {
62
+ const result = await AsyncResult.fromThrowable(
63
+ () => Promise.resolve('data'),
64
+ (error) => `mapped: ${String(error)}`,
65
+ );
66
+
67
+ assert.isTrue(Result.isOk(result));
68
+
69
+ assert.deepStrictEqual(result, Result.ok('data'));
70
+ });
71
+
72
+ test('synchronous throw becomes Err with mapError applied', async () => {
73
+ const result = await AsyncResult.fromThrowable(
74
+ (): Promise<number> => {
75
+ throw new Error('sync failure');
76
+ },
77
+ (error) => (isError(error) ? error.message : 'unknown'),
78
+ );
79
+
80
+ assert.isTrue(Result.isErr(result));
81
+
82
+ assert.deepStrictEqual(result, Result.err('sync failure'));
83
+ });
84
+
85
+ test('async rejection becomes Err with mapError applied', async () => {
86
+ const result = await AsyncResult.fromThrowable(
87
+ () => Promise.reject(new Error('async failure')),
88
+ (error) => (isError(error) ? error.message : 'unknown'),
89
+ );
90
+
91
+ assert.isTrue(Result.isErr(result));
92
+
93
+ assert.deepStrictEqual(result, Result.err('async failure'));
94
+ });
95
+
96
+ test('synchronous throw is carried as-is when mapError is omitted', async () => {
97
+ const error = new Error('sync failure');
98
+
99
+ const result = await AsyncResult.fromThrowable((): Promise<number> => {
100
+ throw error;
101
+ });
102
+
103
+ assert.deepStrictEqual(result, Result.err(error));
104
+
105
+ expectType<typeof result, Result<number, unknown>>('=');
106
+ });
107
+
108
+ test('async rejection is carried as-is when mapError is omitted', async () => {
109
+ const error = new Error('async failure');
110
+
111
+ const result = await AsyncResult.fromThrowable(() =>
112
+ Promise.reject(error).then(() => 42),
113
+ );
114
+
115
+ assert.deepStrictEqual(result, Result.err(error));
116
+ });
117
+ });
118
+
119
+ describe('map', () => {
120
+ test('maps Ok value with a synchronous function', async () => {
121
+ const result = await AsyncResult.map(okFixture(5), (value) => value * 2);
122
+
123
+ assert.deepStrictEqual(result, Result.ok(10));
124
+ });
125
+
126
+ test('maps Ok value with an asynchronous function', async () => {
127
+ const result = await AsyncResult.map(okFixture(5), (value) =>
128
+ Promise.resolve(value * 3),
129
+ );
130
+
131
+ assert.deepStrictEqual(result, Result.ok(15));
132
+ });
133
+
134
+ test('passes Err through unchanged', async () => {
135
+ let mut_called = false;
136
+
137
+ const result = await AsyncResult.map(errFixture('failure'), (value) => {
138
+ mut_called = true;
139
+
140
+ return value * 2;
141
+ });
142
+
143
+ assert.deepStrictEqual(result, Result.err('failure'));
144
+
145
+ assert.isFalse(mut_called);
146
+ });
147
+
148
+ test('curried version maps Ok value', async () => {
149
+ const double = AsyncResult.map((value: number) => value * 2);
150
+
151
+ const result = await double(okFixture(21));
152
+
153
+ assert.deepStrictEqual(result, Result.ok(42));
154
+ });
155
+
156
+ test('curried version passes Err through unchanged', async () => {
157
+ const double = AsyncResult.map((value: number) => value * 2);
158
+
159
+ const result = await double(errFixture('failure'));
160
+
161
+ assert.deepStrictEqual(result, Result.err('failure'));
162
+ });
163
+ });
164
+
165
+ describe('mapErr', () => {
166
+ test('maps Err value with a synchronous function', async () => {
167
+ const result = await AsyncResult.mapErr(errFixture('failure'), (error) =>
168
+ error.toUpperCase(),
169
+ );
170
+
171
+ assert.deepStrictEqual(result, Result.err('FAILURE'));
172
+ });
173
+
174
+ test('maps Err value with an asynchronous function', async () => {
175
+ const result = await AsyncResult.mapErr(
176
+ errFixture('failure'),
177
+ (message) => Promise.resolve(message.length),
178
+ );
179
+
180
+ assert.deepStrictEqual(result, Result.err(7));
181
+ });
182
+
183
+ test('passes Ok through unchanged', async () => {
184
+ let mut_called = false;
185
+
186
+ const result = await AsyncResult.mapErr(okFixture(1), (error) => {
187
+ mut_called = true;
188
+
189
+ return error.toUpperCase();
190
+ });
191
+
192
+ assert.deepStrictEqual(result, Result.ok(1));
193
+
194
+ assert.isFalse(mut_called);
195
+ });
196
+
197
+ test('curried version maps Err value', async () => {
198
+ const upper = AsyncResult.mapErr((error: string) => error.toUpperCase());
199
+
200
+ const result = await upper(errFixture('failure'));
201
+
202
+ assert.deepStrictEqual(result, Result.err('FAILURE'));
203
+ });
204
+ });
205
+
206
+ describe('flatMap', () => {
207
+ test('chains with a function returning a Result', async () => {
208
+ const result = await AsyncResult.flatMap(okFixture(4), (value) =>
209
+ value > 0 ? Result.ok(value * 10) : Result.err('non-positive'),
210
+ );
211
+
212
+ assert.deepStrictEqual(result, Result.ok(40));
213
+ });
214
+
215
+ test('chains with a function returning an AsyncResult', async () => {
216
+ const result = await AsyncResult.flatMap(okFixture(4), (value) =>
217
+ AsyncResult.fromPromise(Promise.resolve(value + 1), () => 'failed'),
218
+ );
219
+
220
+ assert.deepStrictEqual(result, Result.ok(5));
221
+ });
222
+
223
+ test('chains into Err returned by the function', async () => {
224
+ const result = await AsyncResult.flatMap(
225
+ okFixture(-1),
226
+ (value): Result<number, string> =>
227
+ value > 0 ? Result.ok(value * 10) : Result.err('non-positive'),
228
+ );
229
+
230
+ assert.deepStrictEqual(result, Result.err('non-positive'));
231
+ });
232
+
233
+ test('short-circuits on Err without calling the function', async () => {
234
+ let mut_called = false;
235
+
236
+ const result = await AsyncResult.flatMap(
237
+ errFixture('failure'),
238
+ (value) => {
239
+ mut_called = true;
240
+
241
+ return Result.ok(value * 2);
242
+ },
243
+ );
244
+
245
+ assert.deepStrictEqual(result, Result.err('failure'));
246
+
247
+ assert.isFalse(mut_called);
248
+ });
249
+
250
+ test('curried version chains with a function returning a Result', async () => {
251
+ const validate = AsyncResult.flatMap(
252
+ (value: number): Result<number, string> =>
253
+ value > 0 ? Result.ok(value) : Result.err('non-positive'),
254
+ );
255
+
256
+ const passed = await validate(okFixture(7));
257
+
258
+ const failed = await validate(okFixture(-7));
259
+
260
+ assert.deepStrictEqual(passed, Result.ok(7));
261
+
262
+ assert.deepStrictEqual(failed, Result.err('non-positive'));
263
+ });
264
+ });
265
+
266
+ describe('unwrapOr', () => {
267
+ test('returns the success value for Ok', async () => {
268
+ const value = await AsyncResult.unwrapOr(okFixture(10), 0);
269
+
270
+ expect(value).toBe(10);
271
+ });
272
+
273
+ test('returns the default value for Err', async () => {
274
+ const value = await AsyncResult.unwrapOr(errFixture('failure'), 0);
275
+
276
+ expect(value).toBe(0);
277
+ });
278
+ });
279
+
280
+ describe('entry point', () => {
281
+ test('AsyncResult is exported from the package entry point', async () => {
282
+ const result = await AsyncResultFromEntryPoint.fromPromise(
283
+ Promise.resolve(1),
284
+ () => 'failed',
285
+ );
286
+
287
+ assert.deepStrictEqual(result, Result.ok(1));
288
+ });
289
+ });
290
+ });
@@ -1,3 +1,4 @@
1
+ export * from './async-result/index.mjs';
1
2
  export * from './match.mjs';
2
3
  export * from './optional/index.mjs';
3
4
  export * from './pipe.mjs';
@@ -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';