@nlozgachev/pipelined 0.6.4
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 +85 -0
- package/esm/mod.js +3 -0
- package/esm/package.json +3 -0
- package/esm/src/Composition/compose.js +3 -0
- package/esm/src/Composition/converge.js +3 -0
- package/esm/src/Composition/curry.js +42 -0
- package/esm/src/Composition/flip.js +20 -0
- package/esm/src/Composition/flow.js +8 -0
- package/esm/src/Composition/fn.js +85 -0
- package/esm/src/Composition/index.js +13 -0
- package/esm/src/Composition/juxt.js +3 -0
- package/esm/src/Composition/memoize.js +66 -0
- package/esm/src/Composition/not.js +25 -0
- package/esm/src/Composition/on.js +12 -0
- package/esm/src/Composition/pipe.js +3 -0
- package/esm/src/Composition/tap.js +33 -0
- package/esm/src/Composition/uncurry.js +32 -0
- package/esm/src/Core/Arr.js +463 -0
- package/esm/src/Core/Deferred.js +26 -0
- package/esm/src/Core/InternalTypes.js +1 -0
- package/esm/src/Core/Lens.js +98 -0
- package/esm/src/Core/Option.js +186 -0
- package/esm/src/Core/Optional.js +160 -0
- package/esm/src/Core/Reader.js +134 -0
- package/esm/src/Core/Rec.js +167 -0
- package/esm/src/Core/RemoteData.js +206 -0
- package/esm/src/Core/Result.js +164 -0
- package/esm/src/Core/Task.js +187 -0
- package/esm/src/Core/TaskOption.js +105 -0
- package/esm/src/Core/TaskResult.js +125 -0
- package/esm/src/Core/TaskValidation.js +101 -0
- package/esm/src/Core/These.js +241 -0
- package/esm/src/Core/Validation.js +214 -0
- package/esm/src/Core/index.js +15 -0
- package/esm/src/Types/Brand.js +28 -0
- package/esm/src/Types/NonEmptyList.js +14 -0
- package/esm/src/Types/index.js +2 -0
- package/package.json +61 -0
- package/script/mod.js +19 -0
- package/script/package.json +3 -0
- package/script/src/Composition/compose.js +6 -0
- package/script/src/Composition/converge.js +6 -0
- package/script/src/Composition/curry.js +48 -0
- package/script/src/Composition/flip.js +24 -0
- package/script/src/Composition/flow.js +11 -0
- package/script/src/Composition/fn.js +98 -0
- package/script/src/Composition/index.js +29 -0
- package/script/src/Composition/juxt.js +6 -0
- package/script/src/Composition/memoize.js +71 -0
- package/script/src/Composition/not.js +29 -0
- package/script/src/Composition/on.js +16 -0
- package/script/src/Composition/pipe.js +6 -0
- package/script/src/Composition/tap.js +37 -0
- package/script/src/Composition/uncurry.js +38 -0
- package/script/src/Core/Arr.js +466 -0
- package/script/src/Core/Deferred.js +29 -0
- package/script/src/Core/InternalTypes.js +2 -0
- package/script/src/Core/Lens.js +101 -0
- package/script/src/Core/Option.js +189 -0
- package/script/src/Core/Optional.js +163 -0
- package/script/src/Core/Reader.js +137 -0
- package/script/src/Core/Rec.js +170 -0
- package/script/src/Core/RemoteData.js +209 -0
- package/script/src/Core/Result.js +167 -0
- package/script/src/Core/Task.js +190 -0
- package/script/src/Core/TaskOption.js +108 -0
- package/script/src/Core/TaskResult.js +128 -0
- package/script/src/Core/TaskValidation.js +104 -0
- package/script/src/Core/These.js +244 -0
- package/script/src/Core/Validation.js +217 -0
- package/script/src/Core/index.js +31 -0
- package/script/src/Types/Brand.js +31 -0
- package/script/src/Types/NonEmptyList.js +18 -0
- package/script/src/Types/index.js +18 -0
- package/types/mod.d.ts +4 -0
- package/types/mod.d.ts.map +1 -0
- package/types/src/Composition/compose.d.ts +33 -0
- package/types/src/Composition/compose.d.ts.map +1 -0
- package/types/src/Composition/converge.d.ts +21 -0
- package/types/src/Composition/converge.d.ts.map +1 -0
- package/types/src/Composition/curry.d.ts +43 -0
- package/types/src/Composition/curry.d.ts.map +1 -0
- package/types/src/Composition/flip.d.ts +21 -0
- package/types/src/Composition/flip.d.ts.map +1 -0
- package/types/src/Composition/flow.d.ts +56 -0
- package/types/src/Composition/flow.d.ts.map +1 -0
- package/types/src/Composition/fn.d.ts +76 -0
- package/types/src/Composition/fn.d.ts.map +1 -0
- package/types/src/Composition/index.d.ts +14 -0
- package/types/src/Composition/index.d.ts.map +1 -0
- package/types/src/Composition/juxt.d.ts +18 -0
- package/types/src/Composition/juxt.d.ts.map +1 -0
- package/types/src/Composition/memoize.d.ts +46 -0
- package/types/src/Composition/memoize.d.ts.map +1 -0
- package/types/src/Composition/not.d.ts +26 -0
- package/types/src/Composition/not.d.ts.map +1 -0
- package/types/src/Composition/on.d.ts +13 -0
- package/types/src/Composition/on.d.ts.map +1 -0
- package/types/src/Composition/pipe.d.ts +56 -0
- package/types/src/Composition/pipe.d.ts.map +1 -0
- package/types/src/Composition/tap.d.ts +31 -0
- package/types/src/Composition/tap.d.ts.map +1 -0
- package/types/src/Composition/uncurry.d.ts +54 -0
- package/types/src/Composition/uncurry.d.ts.map +1 -0
- package/types/src/Core/Arr.d.ts +355 -0
- package/types/src/Core/Arr.d.ts.map +1 -0
- package/types/src/Core/Deferred.d.ts +49 -0
- package/types/src/Core/Deferred.d.ts.map +1 -0
- package/types/src/Core/InternalTypes.d.ts +20 -0
- package/types/src/Core/InternalTypes.d.ts.map +1 -0
- package/types/src/Core/Lens.d.ts +118 -0
- package/types/src/Core/Lens.d.ts.map +1 -0
- package/types/src/Core/Option.d.ts +205 -0
- package/types/src/Core/Option.d.ts.map +1 -0
- package/types/src/Core/Optional.d.ts +158 -0
- package/types/src/Core/Optional.d.ts.map +1 -0
- package/types/src/Core/Reader.d.ts +156 -0
- package/types/src/Core/Reader.d.ts.map +1 -0
- package/types/src/Core/Rec.d.ts +121 -0
- package/types/src/Core/Rec.d.ts.map +1 -0
- package/types/src/Core/RemoteData.d.ts +192 -0
- package/types/src/Core/RemoteData.d.ts.map +1 -0
- package/types/src/Core/Result.d.ts +176 -0
- package/types/src/Core/Result.d.ts.map +1 -0
- package/types/src/Core/Task.d.ts +189 -0
- package/types/src/Core/Task.d.ts.map +1 -0
- package/types/src/Core/TaskOption.d.ts +120 -0
- package/types/src/Core/TaskOption.d.ts.map +1 -0
- package/types/src/Core/TaskResult.d.ts +117 -0
- package/types/src/Core/TaskResult.d.ts.map +1 -0
- package/types/src/Core/TaskValidation.d.ts +119 -0
- package/types/src/Core/TaskValidation.d.ts.map +1 -0
- package/types/src/Core/These.d.ts +221 -0
- package/types/src/Core/These.d.ts.map +1 -0
- package/types/src/Core/Validation.d.ts +213 -0
- package/types/src/Core/Validation.d.ts.map +1 -0
- package/types/src/Core/index.d.ts +16 -0
- package/types/src/Core/index.d.ts.map +1 -0
- package/types/src/Types/Brand.d.ts +52 -0
- package/types/src/Types/Brand.d.ts.map +1 -0
- package/types/src/Types/NonEmptyList.d.ts +29 -0
- package/types/src/Types/NonEmptyList.d.ts.map +1 -0
- package/types/src/Types/index.d.ts +3 -0
- package/types/src/Types/index.d.ts.map +1 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TaskResult = void 0;
|
|
4
|
+
const Deferred_js_1 = require("./Deferred.js");
|
|
5
|
+
const Result_js_1 = require("./Result.js");
|
|
6
|
+
const Task_js_1 = require("./Task.js");
|
|
7
|
+
var TaskResult;
|
|
8
|
+
(function (TaskResult) {
|
|
9
|
+
/**
|
|
10
|
+
* Wraps a value in a successful TaskResult.
|
|
11
|
+
*/
|
|
12
|
+
TaskResult.ok = (value) => Task_js_1.Task.resolve(Result_js_1.Result.ok(value));
|
|
13
|
+
/**
|
|
14
|
+
* Creates a failed TaskResult with the given error.
|
|
15
|
+
*/
|
|
16
|
+
TaskResult.err = (error) => Task_js_1.Task.resolve(Result_js_1.Result.err(error));
|
|
17
|
+
/**
|
|
18
|
+
* Creates a TaskResult from a function that may throw.
|
|
19
|
+
* Catches any errors and transforms them using the onError function.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const parseJson = (s: string): TaskResult<string, unknown> =>
|
|
24
|
+
* TaskResult.tryCatch(
|
|
25
|
+
* async () => JSON.parse(s),
|
|
26
|
+
* (e) => `Parse error: ${e}`
|
|
27
|
+
* );
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
TaskResult.tryCatch = (f, onError) => Task_js_1.Task.from(() => f()
|
|
31
|
+
.then(Result_js_1.Result.ok)
|
|
32
|
+
.catch((e) => Result_js_1.Result.err(onError(e))));
|
|
33
|
+
/**
|
|
34
|
+
* Transforms the success value inside a TaskResult.
|
|
35
|
+
*/
|
|
36
|
+
TaskResult.map = (f) => (data) => Task_js_1.Task.map(Result_js_1.Result.map(f))(data);
|
|
37
|
+
/**
|
|
38
|
+
* Transforms the error value inside a TaskResult.
|
|
39
|
+
*/
|
|
40
|
+
TaskResult.mapError = (f) => (data) => Task_js_1.Task.map(Result_js_1.Result.mapError(f))(data);
|
|
41
|
+
/**
|
|
42
|
+
* Chains TaskResult computations. If the first succeeds, passes the value to f.
|
|
43
|
+
* If the first fails, propagates the error.
|
|
44
|
+
*/
|
|
45
|
+
TaskResult.chain = (f) => (data) => Task_js_1.Task.chain((result) => Result_js_1.Result.isOk(result) ? f(result.value) : Task_js_1.Task.resolve(Result_js_1.Result.err(result.error)))(data);
|
|
46
|
+
/**
|
|
47
|
+
* Extracts the value from a TaskResult by providing handlers for both cases.
|
|
48
|
+
*/
|
|
49
|
+
TaskResult.fold = (onErr, onOk) => (data) => Task_js_1.Task.map(Result_js_1.Result.fold(onErr, onOk))(data);
|
|
50
|
+
/**
|
|
51
|
+
* Pattern matches on a TaskResult, returning a Task of the result.
|
|
52
|
+
*/
|
|
53
|
+
TaskResult.match = (cases) => (data) => Task_js_1.Task.map(Result_js_1.Result.match(cases))(data);
|
|
54
|
+
/**
|
|
55
|
+
* Recovers from an error by providing a fallback TaskResult.
|
|
56
|
+
*/
|
|
57
|
+
TaskResult.recover = (fallback) => (data) => Task_js_1.Task.chain((result) => Result_js_1.Result.isErr(result) ? fallback(result.error) : Task_js_1.Task.resolve(result))(data);
|
|
58
|
+
/**
|
|
59
|
+
* Returns the success value or a default value if the TaskResult is an error.
|
|
60
|
+
*/
|
|
61
|
+
TaskResult.getOrElse = (defaultValue) => (data) => Task_js_1.Task.map(Result_js_1.Result.getOrElse(defaultValue))(data);
|
|
62
|
+
/**
|
|
63
|
+
* Executes a side effect on the success value without changing the TaskResult.
|
|
64
|
+
* Useful for logging or debugging.
|
|
65
|
+
*/
|
|
66
|
+
TaskResult.tap = (f) => (data) => Task_js_1.Task.map(Result_js_1.Result.tap(f))(data);
|
|
67
|
+
/**
|
|
68
|
+
* Re-runs a TaskResult on `Err` with configurable attempts, backoff, and retry condition.
|
|
69
|
+
*
|
|
70
|
+
* @param options.attempts - Total number of attempts (1 = no retry, 3 = up to 3 tries)
|
|
71
|
+
* @param options.backoff - Fixed delay in ms, or a function `(attempt) => ms` for computed delay
|
|
72
|
+
* @param options.when - Only retry when this returns true; defaults to always retry on Err
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* // Retry up to 3 times with exponential backoff
|
|
77
|
+
* pipe(
|
|
78
|
+
* fetchUser,
|
|
79
|
+
* TaskResult.retry({ attempts: 3, backoff: n => n * 1000 })
|
|
80
|
+
* );
|
|
81
|
+
*
|
|
82
|
+
* // Only retry on network errors, not auth errors
|
|
83
|
+
* pipe(
|
|
84
|
+
* fetchUser,
|
|
85
|
+
* TaskResult.retry({ attempts: 3, when: e => e instanceof NetworkError })
|
|
86
|
+
* );
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
TaskResult.retry = (options) => (data) => Task_js_1.Task.from(() => {
|
|
90
|
+
const { attempts, backoff, when: shouldRetry } = options;
|
|
91
|
+
const getDelay = (n) => backoff === undefined ? 0 : typeof backoff === "function" ? backoff(n) : backoff;
|
|
92
|
+
const run = (left) => Deferred_js_1.Deferred.toPromise(data()).then((result) => {
|
|
93
|
+
if (Result_js_1.Result.isOk(result))
|
|
94
|
+
return result;
|
|
95
|
+
if (left <= 1)
|
|
96
|
+
return result;
|
|
97
|
+
if (shouldRetry !== undefined && !shouldRetry(result.error)) {
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
const ms = getDelay(attempts - left + 1);
|
|
101
|
+
return (ms > 0 ? new Promise((r) => setTimeout(r, ms)) : Promise.resolve()).then(() => run(left - 1));
|
|
102
|
+
});
|
|
103
|
+
return run(attempts);
|
|
104
|
+
});
|
|
105
|
+
/**
|
|
106
|
+
* Fails a TaskResult with a typed error if it does not resolve within the given time.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* ```ts
|
|
110
|
+
* pipe(
|
|
111
|
+
* fetchUser,
|
|
112
|
+
* TaskResult.timeout(5000, () => new TimeoutError("fetch user timed out"))
|
|
113
|
+
* );
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
TaskResult.timeout = (ms, onTimeout) => (data) => Task_js_1.Task.from(() => {
|
|
117
|
+
let timerId;
|
|
118
|
+
return Promise.race([
|
|
119
|
+
Deferred_js_1.Deferred.toPromise(data()).then((result) => {
|
|
120
|
+
clearTimeout(timerId);
|
|
121
|
+
return result;
|
|
122
|
+
}),
|
|
123
|
+
new Promise((resolve) => {
|
|
124
|
+
timerId = setTimeout(() => resolve(Result_js_1.Result.err(onTimeout())), ms);
|
|
125
|
+
}),
|
|
126
|
+
]);
|
|
127
|
+
});
|
|
128
|
+
})(TaskResult || (exports.TaskResult = TaskResult = {}));
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TaskValidation = void 0;
|
|
4
|
+
const Deferred_js_1 = require("./Deferred.js");
|
|
5
|
+
const Task_js_1 = require("./Task.js");
|
|
6
|
+
const Validation_js_1 = require("./Validation.js");
|
|
7
|
+
var TaskValidation;
|
|
8
|
+
(function (TaskValidation) {
|
|
9
|
+
/**
|
|
10
|
+
* Wraps a value in a valid TaskValidation.
|
|
11
|
+
*/
|
|
12
|
+
TaskValidation.valid = (value) => Task_js_1.Task.resolve(Validation_js_1.Validation.valid(value));
|
|
13
|
+
/**
|
|
14
|
+
* Creates a failed TaskValidation with a single error.
|
|
15
|
+
*/
|
|
16
|
+
TaskValidation.invalid = (error) => Task_js_1.Task.resolve(Validation_js_1.Validation.invalid(error));
|
|
17
|
+
/**
|
|
18
|
+
* Creates an invalid TaskValidation from multiple errors.
|
|
19
|
+
*/
|
|
20
|
+
TaskValidation.invalidAll = (errors) => Task_js_1.Task.resolve(Validation_js_1.Validation.invalidAll(errors));
|
|
21
|
+
/**
|
|
22
|
+
* Lifts a Validation into a TaskValidation.
|
|
23
|
+
*/
|
|
24
|
+
TaskValidation.fromValidation = (validation) => Task_js_1.Task.resolve(validation);
|
|
25
|
+
/**
|
|
26
|
+
* Creates a TaskValidation from a Promise-returning function.
|
|
27
|
+
* Catches any errors and transforms them using the onError function.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const fetchUser = (id: string): TaskValidation<string, User> =>
|
|
32
|
+
* TaskValidation.tryCatch(
|
|
33
|
+
* () => fetch(`/users/${id}`).then(r => r.json()),
|
|
34
|
+
* e => `Failed to fetch user: ${e}`
|
|
35
|
+
* );
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
TaskValidation.tryCatch = (f, onError) => Task_js_1.Task.from(() => f()
|
|
39
|
+
.then((Validation_js_1.Validation.valid))
|
|
40
|
+
.catch((e) => Validation_js_1.Validation.invalid(onError(e))));
|
|
41
|
+
/**
|
|
42
|
+
* Transforms the success value inside a TaskValidation.
|
|
43
|
+
*/
|
|
44
|
+
TaskValidation.map = (f) => (data) => Task_js_1.Task.map(Validation_js_1.Validation.map(f))(data);
|
|
45
|
+
/**
|
|
46
|
+
* Chains TaskValidation computations. If the first is Valid, passes the value
|
|
47
|
+
* to f. If the first is Invalid, propagates the errors.
|
|
48
|
+
*
|
|
49
|
+
* Note: chain short-circuits on first error. Use ap to accumulate errors.
|
|
50
|
+
*/
|
|
51
|
+
TaskValidation.chain = (f) => (data) => Task_js_1.Task.chain((validation) => Validation_js_1.Validation.isValid(validation)
|
|
52
|
+
? f(validation.value)
|
|
53
|
+
: Task_js_1.Task.resolve(Validation_js_1.Validation.invalidAll(validation.errors)))(data);
|
|
54
|
+
/**
|
|
55
|
+
* Applies a function wrapped in a TaskValidation to a value wrapped in a
|
|
56
|
+
* TaskValidation. Both Tasks run in parallel and errors from both sides
|
|
57
|
+
* are accumulated.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* pipe(
|
|
62
|
+
* TaskValidation.valid((name: string) => (age: number) => ({ name, age })),
|
|
63
|
+
* TaskValidation.ap(validateName(name)),
|
|
64
|
+
* TaskValidation.ap(validateAge(age))
|
|
65
|
+
* )();
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
TaskValidation.ap = (arg) => (data) => Task_js_1.Task.from(() => Promise.all([
|
|
69
|
+
Deferred_js_1.Deferred.toPromise(data()),
|
|
70
|
+
Deferred_js_1.Deferred.toPromise(arg()),
|
|
71
|
+
]).then(([vf, va]) => Validation_js_1.Validation.ap(va)(vf)));
|
|
72
|
+
/**
|
|
73
|
+
* Extracts a value from a TaskValidation by providing handlers for both cases.
|
|
74
|
+
*/
|
|
75
|
+
TaskValidation.fold = (onInvalid, onValid) => (data) => Task_js_1.Task.map(Validation_js_1.Validation.fold(onInvalid, onValid))(data);
|
|
76
|
+
/**
|
|
77
|
+
* Pattern matches on a TaskValidation, returning a Task of the result.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* pipe(
|
|
82
|
+
* validateForm(input),
|
|
83
|
+
* TaskValidation.match({
|
|
84
|
+
* valid: data => save(data),
|
|
85
|
+
* invalid: errors => showErrors(errors)
|
|
86
|
+
* })
|
|
87
|
+
* )();
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
TaskValidation.match = (cases) => (data) => Task_js_1.Task.map(Validation_js_1.Validation.match(cases))(data);
|
|
91
|
+
/**
|
|
92
|
+
* Returns the success value or a default value if the TaskValidation is invalid.
|
|
93
|
+
*/
|
|
94
|
+
TaskValidation.getOrElse = (defaultValue) => (data) => Task_js_1.Task.map(Validation_js_1.Validation.getOrElse(defaultValue))(data);
|
|
95
|
+
/**
|
|
96
|
+
* Executes a side effect on the success value without changing the TaskValidation.
|
|
97
|
+
* Useful for logging or debugging.
|
|
98
|
+
*/
|
|
99
|
+
TaskValidation.tap = (f) => (data) => Task_js_1.Task.map(Validation_js_1.Validation.tap(f))(data);
|
|
100
|
+
/**
|
|
101
|
+
* Recovers from an Invalid state by providing a fallback TaskValidation.
|
|
102
|
+
*/
|
|
103
|
+
TaskValidation.recover = (fallback) => (data) => Task_js_1.Task.chain((validation) => Validation_js_1.Validation.isValid(validation) ? Task_js_1.Task.resolve(validation) : fallback())(data);
|
|
104
|
+
})(TaskValidation || (exports.TaskValidation = TaskValidation = {}));
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.These = void 0;
|
|
4
|
+
var These;
|
|
5
|
+
(function (These) {
|
|
6
|
+
/**
|
|
7
|
+
* Creates a These holding only a first value.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* These.first(42); // { kind: "First", first: 42 }
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
These.first = (value) => ({ kind: "First", first: value });
|
|
15
|
+
/**
|
|
16
|
+
* Creates a These holding only a second value.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* These.second("warning"); // { kind: "Second", second: "warning" }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
These.second = (value) => ({ kind: "Second", second: value });
|
|
24
|
+
/**
|
|
25
|
+
* Creates a These holding both a first and a second value simultaneously.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```ts
|
|
29
|
+
* These.both(42, "Deprecated API used"); // { kind: "Both", first: 42, second: "Deprecated API used" }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
These.both = (first, second) => ({
|
|
33
|
+
kind: "Both",
|
|
34
|
+
first,
|
|
35
|
+
second,
|
|
36
|
+
});
|
|
37
|
+
/**
|
|
38
|
+
* Type guard — checks if a These holds only a first value.
|
|
39
|
+
*/
|
|
40
|
+
These.isFirst = (data) => data.kind === "First";
|
|
41
|
+
/**
|
|
42
|
+
* Type guard — checks if a These holds only a second value.
|
|
43
|
+
*/
|
|
44
|
+
These.isSecond = (data) => data.kind === "Second";
|
|
45
|
+
/**
|
|
46
|
+
* Type guard — checks if a These holds both values simultaneously.
|
|
47
|
+
*/
|
|
48
|
+
These.isBoth = (data) => data.kind === "Both";
|
|
49
|
+
/**
|
|
50
|
+
* Returns true if the These contains a first value (First or Both).
|
|
51
|
+
*/
|
|
52
|
+
These.hasFirst = (data) => data.kind === "First" || data.kind === "Both";
|
|
53
|
+
/**
|
|
54
|
+
* Returns true if the These contains a second value (Second or Both).
|
|
55
|
+
*/
|
|
56
|
+
These.hasSecond = (data) => data.kind === "Second" || data.kind === "Both";
|
|
57
|
+
/**
|
|
58
|
+
* Transforms the first value, leaving the second unchanged.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* pipe(These.first(5), These.mapFirst(n => n * 2)); // First(10)
|
|
63
|
+
* pipe(These.both(5, "warn"), These.mapFirst(n => n * 2)); // Both(10, "warn")
|
|
64
|
+
* pipe(These.second("warn"), These.mapFirst(n => n * 2)); // Second("warn")
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
These.mapFirst = (f) => (data) => {
|
|
68
|
+
if (These.isSecond(data))
|
|
69
|
+
return data;
|
|
70
|
+
if (These.isFirst(data))
|
|
71
|
+
return These.first(f(data.first));
|
|
72
|
+
return These.both(f(data.first), data.second);
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Transforms the second value, leaving the first unchanged.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* pipe(These.second("warn"), These.mapSecond(e => e.toUpperCase())); // Second("WARN")
|
|
80
|
+
* pipe(These.both(5, "warn"), These.mapSecond(e => e.toUpperCase())); // Both(5, "WARN")
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
These.mapSecond = (f) => (data) => {
|
|
84
|
+
if (These.isFirst(data))
|
|
85
|
+
return data;
|
|
86
|
+
if (These.isSecond(data))
|
|
87
|
+
return These.second(f(data.second));
|
|
88
|
+
return These.both(data.first, f(data.second));
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Transforms both the first and second values independently.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* pipe(
|
|
96
|
+
* These.both(5, "warn"),
|
|
97
|
+
* These.mapBoth(n => n * 2, e => e.toUpperCase())
|
|
98
|
+
* ); // Both(10, "WARN")
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
These.mapBoth = (onFirst, onSecond) => (data) => {
|
|
102
|
+
if (These.isSecond(data))
|
|
103
|
+
return These.second(onSecond(data.second));
|
|
104
|
+
if (These.isFirst(data))
|
|
105
|
+
return These.first(onFirst(data.first));
|
|
106
|
+
return These.both(onFirst(data.first), onSecond(data.second));
|
|
107
|
+
};
|
|
108
|
+
/**
|
|
109
|
+
* Chains These computations by passing the first value to f.
|
|
110
|
+
* Second propagates unchanged; First and Both apply f to the first value.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* const double = (n: number): These<number, string> => These.first(n * 2);
|
|
115
|
+
*
|
|
116
|
+
* pipe(These.first(5), These.chainFirst(double)); // First(10)
|
|
117
|
+
* pipe(These.both(5, "warn"), These.chainFirst(double)); // First(10)
|
|
118
|
+
* pipe(These.second("warn"), These.chainFirst(double)); // Second("warn")
|
|
119
|
+
* ```
|
|
120
|
+
*/
|
|
121
|
+
These.chainFirst = (f) => (data) => {
|
|
122
|
+
if (These.isSecond(data))
|
|
123
|
+
return data;
|
|
124
|
+
return f(data.first);
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Chains These computations by passing the second value to f.
|
|
128
|
+
* First propagates unchanged; Second and Both apply f to the second value.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const shout = (s: string): These<number, string> => These.second(s.toUpperCase());
|
|
133
|
+
*
|
|
134
|
+
* pipe(These.second("warn"), These.chainSecond(shout)); // Second("WARN")
|
|
135
|
+
* pipe(These.both(5, "warn"), These.chainSecond(shout)); // Second("WARN")
|
|
136
|
+
* pipe(These.first(5), These.chainSecond(shout)); // First(5)
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
These.chainSecond = (f) => (data) => {
|
|
140
|
+
if (These.isFirst(data))
|
|
141
|
+
return data;
|
|
142
|
+
return f(data.second);
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Extracts a value from a These by providing handlers for all three cases.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* pipe(
|
|
150
|
+
* these,
|
|
151
|
+
* These.fold(
|
|
152
|
+
* a => `First: ${a}`,
|
|
153
|
+
* b => `Second: ${b}`,
|
|
154
|
+
* (a, b) => `Both: ${a} / ${b}`
|
|
155
|
+
* )
|
|
156
|
+
* );
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
These.fold = (onFirst, onSecond, onBoth) => (data) => {
|
|
160
|
+
if (These.isSecond(data))
|
|
161
|
+
return onSecond(data.second);
|
|
162
|
+
if (These.isFirst(data))
|
|
163
|
+
return onFirst(data.first);
|
|
164
|
+
return onBoth(data.first, data.second);
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Pattern matches on a These, returning the result of the matching case.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* pipe(
|
|
172
|
+
* these,
|
|
173
|
+
* These.match({
|
|
174
|
+
* first: a => `First: ${a}`,
|
|
175
|
+
* second: b => `Second: ${b}`,
|
|
176
|
+
* both: (a, b) => `Both: ${a} / ${b}`
|
|
177
|
+
* })
|
|
178
|
+
* );
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
These.match = (cases) => (data) => {
|
|
182
|
+
if (These.isSecond(data))
|
|
183
|
+
return cases.second(data.second);
|
|
184
|
+
if (These.isFirst(data))
|
|
185
|
+
return cases.first(data.first);
|
|
186
|
+
return cases.both(data.first, data.second);
|
|
187
|
+
};
|
|
188
|
+
/**
|
|
189
|
+
* Returns the first value, or a default if the These has no first value.
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* ```ts
|
|
193
|
+
* pipe(These.first(5), These.getFirstOrElse(0)); // 5
|
|
194
|
+
* pipe(These.both(5, "warn"), These.getFirstOrElse(0)); // 5
|
|
195
|
+
* pipe(These.second("warn"), These.getFirstOrElse(0)); // 0
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
These.getFirstOrElse = (defaultValue) => (data) => These.hasFirst(data) ? data.first : defaultValue;
|
|
199
|
+
/**
|
|
200
|
+
* Returns the second value, or a default if the These has no second value.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* pipe(These.second("warn"), These.getSecondOrElse("none")); // "warn"
|
|
205
|
+
* pipe(These.both(5, "warn"), These.getSecondOrElse("none")); // "warn"
|
|
206
|
+
* pipe(These.first(5), These.getSecondOrElse("none")); // "none"
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
209
|
+
These.getSecondOrElse = (defaultValue) => (data) => These.hasSecond(data) ? data.second : defaultValue;
|
|
210
|
+
/**
|
|
211
|
+
* Executes a side effect on the first value without changing the These.
|
|
212
|
+
* Useful for logging or debugging.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* pipe(These.first(5), These.tap(console.log)); // logs 5, returns First(5)
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
These.tap = (f) => (data) => {
|
|
220
|
+
if (These.hasFirst(data))
|
|
221
|
+
f(data.first);
|
|
222
|
+
return data;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* Swaps the roles of first and second values.
|
|
226
|
+
* - First(a) → Second(a)
|
|
227
|
+
* - Second(b) → First(b)
|
|
228
|
+
* - Both(a, b) → Both(b, a)
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* These.swap(These.first(5)); // Second(5)
|
|
233
|
+
* These.swap(These.second("warn")); // First("warn")
|
|
234
|
+
* These.swap(These.both(5, "warn")); // Both("warn", 5)
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
These.swap = (data) => {
|
|
238
|
+
if (These.isSecond(data))
|
|
239
|
+
return These.first(data.second);
|
|
240
|
+
if (These.isFirst(data))
|
|
241
|
+
return These.second(data.first);
|
|
242
|
+
return These.both(data.second, data.first);
|
|
243
|
+
};
|
|
244
|
+
})(These || (exports.These = These = {}));
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Validation = void 0;
|
|
4
|
+
const NonEmptyList_js_1 = require("../Types/NonEmptyList.js");
|
|
5
|
+
var Validation;
|
|
6
|
+
(function (Validation) {
|
|
7
|
+
/**
|
|
8
|
+
* Wraps a value in a valid Validation.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* Validation.valid(42); // Valid(42)
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
Validation.valid = (value) => ({
|
|
16
|
+
kind: "Valid",
|
|
17
|
+
value,
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Creates an invalid Validation from a single error.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* Validation.invalid("Invalid input");
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
Validation.invalid = (error) => ({
|
|
28
|
+
kind: "Invalid",
|
|
29
|
+
errors: [error],
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Creates an invalid Validation from multiple errors.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* Validation.invalidAll(["Invalid input"]);
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
Validation.invalidAll = (errors) => ({
|
|
40
|
+
kind: "Invalid",
|
|
41
|
+
errors,
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Type guard that checks if a Validation is valid.
|
|
45
|
+
*/
|
|
46
|
+
Validation.isValid = (data) => data.kind === "Valid";
|
|
47
|
+
/**
|
|
48
|
+
* Type guard that checks if a Validation is invalid.
|
|
49
|
+
*/
|
|
50
|
+
Validation.isInvalid = (data) => data.kind === "Invalid";
|
|
51
|
+
/**
|
|
52
|
+
* Transforms the success value inside a Validation.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* pipe(Validation.valid(5), Validation.map(n => n * 2)); // Valid(10)
|
|
57
|
+
* pipe(Validation.invalid("oops"), Validation.map(n => n * 2)); // Invalid(["oops"])
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
Validation.map = (f) => (data) => Validation.isValid(data) ? Validation.valid(f(data.value)) : data;
|
|
61
|
+
/**
|
|
62
|
+
* Chains Validation computations. If the first is Valid, passes the value to f.
|
|
63
|
+
* If the first is Invalid, propagates the errors.
|
|
64
|
+
*
|
|
65
|
+
* Note: chain short-circuits on first error. Use `ap` to accumulate errors.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* const validatePositive = (n: number): Validation<string, number> =>
|
|
70
|
+
* n > 0 ? Validation.valid(n) : Validation.invalid("Must be positive");
|
|
71
|
+
*
|
|
72
|
+
* pipe(Validation.valid(5), Validation.chain(validatePositive)); // Valid(5)
|
|
73
|
+
* pipe(Validation.valid(-1), Validation.chain(validatePositive)); // Invalid(["Must be positive"])
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
Validation.chain = (f) => (data) => Validation.isValid(data) ? f(data.value) : data;
|
|
77
|
+
/**
|
|
78
|
+
* Applies a function wrapped in a Validation to a value wrapped in a Validation.
|
|
79
|
+
* Accumulates errors from both sides.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```ts
|
|
83
|
+
* const add = (a: number) => (b: number) => a + b;
|
|
84
|
+
* pipe(
|
|
85
|
+
* Validation.valid(add),
|
|
86
|
+
* Validation.ap(Validation.valid(5)),
|
|
87
|
+
* Validation.ap(Validation.valid(3))
|
|
88
|
+
* ); // Valid(8)
|
|
89
|
+
*
|
|
90
|
+
* pipe(
|
|
91
|
+
* Validation.valid(add),
|
|
92
|
+
* Validation.ap(Validation.invalid<string, number>("bad a")),
|
|
93
|
+
* Validation.ap(Validation.invalid<string, number>("bad b"))
|
|
94
|
+
* ); // Invalid(["bad a", "bad b"])
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
Validation.ap = (arg) => (data) => {
|
|
98
|
+
if (Validation.isValid(data) && Validation.isValid(arg))
|
|
99
|
+
return Validation.valid(data.value(arg.value));
|
|
100
|
+
const errors = [
|
|
101
|
+
...(Validation.isInvalid(data) ? data.errors : []),
|
|
102
|
+
...(Validation.isInvalid(arg) ? arg.errors : []),
|
|
103
|
+
];
|
|
104
|
+
return (0, NonEmptyList_js_1.isNonEmptyList)(errors) ? Validation.invalidAll(errors) : Validation.valid(data);
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Extracts the value from a Validation by providing handlers for both cases.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* pipe(
|
|
112
|
+
* Validation.valid(42),
|
|
113
|
+
* Validation.fold(
|
|
114
|
+
* errors => `Errors: ${errors.join(", ")}`,
|
|
115
|
+
* value => `Value: ${value}`
|
|
116
|
+
* )
|
|
117
|
+
* );
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
Validation.fold = (onInvalid, onValid) => (data) => Validation.isValid(data) ? onValid(data.value) : onInvalid(data.errors);
|
|
121
|
+
/**
|
|
122
|
+
* Pattern matches on a Validation, returning the result of the matching case.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* ```ts
|
|
126
|
+
* pipe(
|
|
127
|
+
* validation,
|
|
128
|
+
* Validation.match({
|
|
129
|
+
* valid: value => `Got ${value}`,
|
|
130
|
+
* invalid: errors => `Failed: ${errors.join(", ")}`
|
|
131
|
+
* })
|
|
132
|
+
* );
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
Validation.match = (cases) => (data) => Validation.isValid(data) ? cases.valid(data.value) : cases.invalid(data.errors);
|
|
136
|
+
/**
|
|
137
|
+
* Returns the success value or a default value if the Validation is invalid.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* pipe(Validation.valid(5), Validation.getOrElse(0)); // 5
|
|
142
|
+
* pipe(Validation.invalid("oops"), Validation.getOrElse(0)); // 0
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
Validation.getOrElse = (defaultValue) => (data) => Validation.isValid(data) ? data.value : defaultValue;
|
|
146
|
+
/**
|
|
147
|
+
* Executes a side effect on the success value without changing the Validation.
|
|
148
|
+
*
|
|
149
|
+
* @example
|
|
150
|
+
* ```ts
|
|
151
|
+
* pipe(
|
|
152
|
+
* Validation.valid(5),
|
|
153
|
+
* Validation.tap(n => console.log("Value:", n)),
|
|
154
|
+
* Validation.map(n => n * 2)
|
|
155
|
+
* );
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
Validation.tap = (f) => (data) => {
|
|
159
|
+
if (Validation.isValid(data))
|
|
160
|
+
f(data.value);
|
|
161
|
+
return data;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Recovers from an Invalid state by providing a fallback Validation.
|
|
165
|
+
*/
|
|
166
|
+
Validation.recover = (fallback) => (data) => Validation.isValid(data) ? data : fallback();
|
|
167
|
+
/**
|
|
168
|
+
* Recovers from an Invalid state unless the errors contain any of the blocked errors.
|
|
169
|
+
*/
|
|
170
|
+
Validation.recoverUnless = (blockedErrors, fallback) => (data) => Validation.isInvalid(data) &&
|
|
171
|
+
!data.errors.some((err) => blockedErrors.includes(err))
|
|
172
|
+
? fallback()
|
|
173
|
+
: data;
|
|
174
|
+
/**
|
|
175
|
+
* Combines two Validation instances, accumulating errors from both.
|
|
176
|
+
* If both are Valid, returns the second valid value.
|
|
177
|
+
* If either is Invalid, combines their errors into a single Invalid.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* Validation.combine(
|
|
182
|
+
* Validation.invalid("Error 1"),
|
|
183
|
+
* Validation.invalid("Error 2")
|
|
184
|
+
* ); // Invalid(["Error 1", "Error 2"])
|
|
185
|
+
*
|
|
186
|
+
* Validation.combine(
|
|
187
|
+
* Validation.valid("a"),
|
|
188
|
+
* Validation.valid("b")
|
|
189
|
+
* ); // Valid("b")
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
Validation.combine = (first, second) => {
|
|
193
|
+
if (Validation.isValid(first) && Validation.isValid(second)) {
|
|
194
|
+
return second;
|
|
195
|
+
}
|
|
196
|
+
const errors = [
|
|
197
|
+
...(Validation.isInvalid(first) ? first.errors : []),
|
|
198
|
+
...(Validation.isInvalid(second) ? second.errors : []),
|
|
199
|
+
];
|
|
200
|
+
return (0, NonEmptyList_js_1.isNonEmptyList)(errors) ? Validation.invalidAll(errors) : second;
|
|
201
|
+
};
|
|
202
|
+
/**
|
|
203
|
+
* Combines multiple Validation instances, accumulating all errors.
|
|
204
|
+
* If all are Valid, returns the last valid value.
|
|
205
|
+
* Returns undefined for an empty array.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* Validation.combineAll([
|
|
210
|
+
* validateName(name),
|
|
211
|
+
* validateEmail(email),
|
|
212
|
+
* validateAge(age)
|
|
213
|
+
* ]);
|
|
214
|
+
* ```
|
|
215
|
+
*/
|
|
216
|
+
Validation.combineAll = (data) => data.length === 0 ? undefined : data.reduce((acc, v) => Validation.combine(acc, v));
|
|
217
|
+
})(Validation || (exports.Validation = Validation = {}));
|