@nlozgachev/pipelined 0.63.0 → 0.64.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.
- package/README.md +9 -9
- package/dist/{Validation-C5RGZUXy.d.ts → Task-9SJCMtG4.d.ts} +602 -1411
- package/dist/{Validation-KFUpea_k.d.cts → Task-C_goFYQ1.d.cts} +602 -1411
- package/dist/core.cjs +1200 -1111
- package/dist/core.d.cts +114 -16
- package/dist/core.d.ts +114 -16
- package/dist/core.mjs +1200 -1111
- package/dist/data.cjs +398 -3
- package/dist/data.d.cts +345 -4
- package/dist/data.d.ts +345 -4
- package/dist/data.mjs +394 -0
- package/dist/index.cjs +1587 -1103
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +1583 -1100
- package/package.json +1 -1
package/dist/core.mjs
CHANGED
|
@@ -2108,22 +2108,120 @@ function interpretFn(op, options) {
|
|
|
2108
2108
|
}
|
|
2109
2109
|
}
|
|
2110
2110
|
var Op = {
|
|
2111
|
-
|
|
2111
|
+
make: {
|
|
2112
|
+
/**
|
|
2113
|
+
* Creates an Ok outcome with the given value.
|
|
2114
|
+
*
|
|
2115
|
+
* @example
|
|
2116
|
+
* ```ts
|
|
2117
|
+
* Op.make.ok(42); // { kind: "OpOk", value: 42 }
|
|
2118
|
+
* ```
|
|
2119
|
+
*/
|
|
2120
|
+
ok: makeOk,
|
|
2121
|
+
/**
|
|
2122
|
+
* Creates an Err outcome with the given error.
|
|
2123
|
+
*
|
|
2124
|
+
* @example
|
|
2125
|
+
* ```ts
|
|
2126
|
+
* Op.make.err("Something went wrong"); // { kind: "OpErr", error: "Something went wrong" }
|
|
2127
|
+
* ```
|
|
2128
|
+
*/
|
|
2129
|
+
err: makeErr,
|
|
2130
|
+
/**
|
|
2131
|
+
* Creates a Nil outcome with the given cancellation/drop reason.
|
|
2132
|
+
*
|
|
2133
|
+
* @example
|
|
2134
|
+
* ```ts
|
|
2135
|
+
* Op.make.nil("aborted"); // { kind: "OpNil", reason: "aborted" }
|
|
2136
|
+
* ```
|
|
2137
|
+
*/
|
|
2138
|
+
nil: makeNil
|
|
2139
|
+
},
|
|
2140
|
+
is: {
|
|
2141
|
+
/**
|
|
2142
|
+
* Type guard that checks if an Op state is Idle.
|
|
2143
|
+
*
|
|
2144
|
+
* @example
|
|
2145
|
+
* ```ts
|
|
2146
|
+
* if (Op.is.idle(manager.state)) {
|
|
2147
|
+
* console.log("Ready to execute");
|
|
2148
|
+
* }
|
|
2149
|
+
* ```
|
|
2150
|
+
*/
|
|
2151
|
+
idle: isIdle,
|
|
2152
|
+
/**
|
|
2153
|
+
* Type guard that checks if an Op state is Pending (actively executing).
|
|
2154
|
+
*
|
|
2155
|
+
* @example
|
|
2156
|
+
* ```ts
|
|
2157
|
+
* if (Op.is.pending(manager.state)) {
|
|
2158
|
+
* showSpinner();
|
|
2159
|
+
* }
|
|
2160
|
+
* ```
|
|
2161
|
+
*/
|
|
2162
|
+
pending: isPending,
|
|
2163
|
+
/**
|
|
2164
|
+
* Type guard that checks if an Op state is Queued (waiting in a concurrency queue).
|
|
2165
|
+
*
|
|
2166
|
+
* @example
|
|
2167
|
+
* ```ts
|
|
2168
|
+
* if (Op.is.queued(manager.state)) {
|
|
2169
|
+
* console.log("Position in queue:", manager.state.position);
|
|
2170
|
+
* }
|
|
2171
|
+
* ```
|
|
2172
|
+
*/
|
|
2173
|
+
queued: isQueued,
|
|
2174
|
+
/**
|
|
2175
|
+
* Type guard that checks if an Op state is Retrying after a failure.
|
|
2176
|
+
*
|
|
2177
|
+
* @example
|
|
2178
|
+
* ```ts
|
|
2179
|
+
* if (Op.is.retrying(manager.state)) {
|
|
2180
|
+
* console.log("Retry attempt:", manager.state.attempt);
|
|
2181
|
+
* }
|
|
2182
|
+
* ```
|
|
2183
|
+
*/
|
|
2184
|
+
retrying: isRetrying,
|
|
2185
|
+
/**
|
|
2186
|
+
* Type guard that checks if an Op state or outcome is Ok.
|
|
2187
|
+
*
|
|
2188
|
+
* @example
|
|
2189
|
+
* ```ts
|
|
2190
|
+
* if (Op.is.ok(outcome)) {
|
|
2191
|
+
* render(outcome.value);
|
|
2192
|
+
* }
|
|
2193
|
+
* ```
|
|
2194
|
+
*/
|
|
2195
|
+
ok: isOk,
|
|
2196
|
+
/**
|
|
2197
|
+
* Type guard that checks if an Op state or outcome is Err.
|
|
2198
|
+
*
|
|
2199
|
+
* @example
|
|
2200
|
+
* ```ts
|
|
2201
|
+
* if (Op.is.err(outcome)) {
|
|
2202
|
+
* showError(outcome.error);
|
|
2203
|
+
* }
|
|
2204
|
+
* ```
|
|
2205
|
+
*/
|
|
2206
|
+
err: isErr,
|
|
2207
|
+
/**
|
|
2208
|
+
* Type guard that checks if an Op state or outcome is Nil.
|
|
2209
|
+
*
|
|
2210
|
+
* @example
|
|
2211
|
+
* ```ts
|
|
2212
|
+
* if (Op.is.nil(outcome)) {
|
|
2213
|
+
* console.log("Skipped due to:", outcome.reason);
|
|
2214
|
+
* }
|
|
2215
|
+
* ```
|
|
2216
|
+
*/
|
|
2217
|
+
nil: isNil
|
|
2218
|
+
},
|
|
2112
2219
|
create: (factory, onError) => ({
|
|
2113
2220
|
_factory: (input, signal) => Deferred.from.Promise(
|
|
2114
2221
|
factory(signal)(input).then((value) => Result.make.ok(value)).catch((error) => signal.aborted ? null : Result.make.err(onError(error)))
|
|
2115
2222
|
)
|
|
2116
2223
|
}),
|
|
2117
2224
|
lift: (f) => Op.create((signal) => (input) => f(input, signal), (e) => e),
|
|
2118
|
-
ok: makeOk,
|
|
2119
|
-
err: makeErr,
|
|
2120
|
-
isIdle,
|
|
2121
|
-
isPending,
|
|
2122
|
-
isQueued,
|
|
2123
|
-
isRetrying,
|
|
2124
|
-
isOk,
|
|
2125
|
-
isErr,
|
|
2126
|
-
isNil,
|
|
2127
2225
|
match: (cases) => (outcome) => {
|
|
2128
2226
|
if (outcome.kind === "OpOk") {
|
|
2129
2227
|
return cases.ok(outcome.value);
|
|
@@ -4279,9 +4377,9 @@ var Stream = {
|
|
|
4279
4377
|
var makeSome2 = (value) => Task.resolve(Maybe.make.some(value));
|
|
4280
4378
|
var makeNone2 = () => Task.resolve(Maybe.make.none());
|
|
4281
4379
|
var mapTaskMaybe = (f) => (data) => Task.map(Maybe.map(f))(data);
|
|
4282
|
-
var chainTaskMaybe = (f) => (data) => Task.chain(
|
|
4283
|
-
|
|
4284
|
-
)
|
|
4380
|
+
var chainTaskMaybe = (f) => (data) => Task.chain((option) => Maybe.is.some(option) ? f(option.value) : Task.resolve(Maybe.make.none()))(
|
|
4381
|
+
data
|
|
4382
|
+
);
|
|
4285
4383
|
var TaskMaybe = {
|
|
4286
4384
|
/**
|
|
4287
4385
|
* Wraps a value in a Some inside a Task.
|
|
@@ -4314,8 +4412,6 @@ var TaskMaybe = {
|
|
|
4314
4412
|
*/
|
|
4315
4413
|
none: makeNone2
|
|
4316
4414
|
},
|
|
4317
|
-
some: makeSome2,
|
|
4318
|
-
none: makeNone2,
|
|
4319
4415
|
// --- from ---
|
|
4320
4416
|
from: {
|
|
4321
4417
|
/**
|
|
@@ -4451,7 +4547,7 @@ var TaskMaybe = {
|
|
|
4451
4547
|
*
|
|
4452
4548
|
* @example
|
|
4453
4549
|
* ```ts
|
|
4454
|
-
* pipe(Task.Maybe.some(42), Task.Maybe.bindTo("value")); // Task.Maybe({ value: 42 })
|
|
4550
|
+
* pipe(Task.Maybe.make.some(42), Task.Maybe.bindTo("value")); // Task.Maybe({ value: 42 })
|
|
4455
4551
|
* ```
|
|
4456
4552
|
*/
|
|
4457
4553
|
bindTo: (key) => (data) => mapTaskMaybe((a) => ({ [key]: a }))(data),
|
|
@@ -4461,8 +4557,8 @@ var TaskMaybe = {
|
|
|
4461
4557
|
* @example
|
|
4462
4558
|
* ```ts
|
|
4463
4559
|
* pipe(
|
|
4464
|
-
* Task.Maybe.some({ a: 1 }),
|
|
4465
|
-
* Task.Maybe.bind("b", ({ a }) => Task.Maybe.some(a + 1))
|
|
4560
|
+
* Task.Maybe.make.some({ a: 1 }),
|
|
4561
|
+
* Task.Maybe.bind("b", ({ a }) => Task.Maybe.make.some(a + 1))
|
|
4466
4562
|
* ); // Task.Maybe({ a: 1, b: 2 })
|
|
4467
4563
|
* ```
|
|
4468
4564
|
*/
|
|
@@ -4475,14 +4571,12 @@ var TaskMaybe = {
|
|
|
4475
4571
|
* @example
|
|
4476
4572
|
* ```ts
|
|
4477
4573
|
* pipe(
|
|
4478
|
-
* Task.Maybe.none(),
|
|
4479
|
-
* Task.Maybe.recover(() => Task.Maybe.some(42))
|
|
4574
|
+
* Task.Maybe.make.none(),
|
|
4575
|
+
* Task.Maybe.recover(() => Task.Maybe.make.some(42))
|
|
4480
4576
|
* ); // Task.Maybe(42)
|
|
4481
4577
|
* ```
|
|
4482
4578
|
*/
|
|
4483
|
-
recover: (fallback) => (data) => Task.chain((maybe) => Maybe.is.none(maybe) ? fallback() : Task.resolve(maybe))(
|
|
4484
|
-
data
|
|
4485
|
-
),
|
|
4579
|
+
recover: (fallback) => (data) => Task.chain((maybe) => Maybe.is.none(maybe) ? fallback() : Task.resolve(maybe))(data),
|
|
4486
4580
|
/**
|
|
4487
4581
|
* Combines a record of Task.Maybes into a single Task.Maybe of a record.
|
|
4488
4582
|
* Evaluates fields in parallel and returns None if any task resolves to None.
|
|
@@ -4490,8 +4584,8 @@ var TaskMaybe = {
|
|
|
4490
4584
|
* @example
|
|
4491
4585
|
* ```ts
|
|
4492
4586
|
* Task.Maybe.struct({
|
|
4493
|
-
* name: Task.Maybe.some("Alice"),
|
|
4494
|
-
* age: Task.Maybe.some(30)
|
|
4587
|
+
* name: Task.Maybe.make.some("Alice"),
|
|
4588
|
+
* age: Task.Maybe.make.some(30)
|
|
4495
4589
|
* }); // Task.Maybe({ name: "Alice", age: 30 })
|
|
4496
4590
|
* ```
|
|
4497
4591
|
*/
|
|
@@ -4552,8 +4646,6 @@ var TaskResult = {
|
|
|
4552
4646
|
*/
|
|
4553
4647
|
err: makeErr3
|
|
4554
4648
|
},
|
|
4555
|
-
ok: makeOk3,
|
|
4556
|
-
err: makeErr3,
|
|
4557
4649
|
// --- from ---
|
|
4558
4650
|
from: {
|
|
4559
4651
|
/**
|
|
@@ -4595,7 +4687,7 @@ var TaskResult = {
|
|
|
4595
4687
|
*
|
|
4596
4688
|
* @example
|
|
4597
4689
|
* ```ts
|
|
4598
|
-
* const taskResult = Task.Result.ok(42);
|
|
4690
|
+
* const taskResult = Task.Result.make.ok(42);
|
|
4599
4691
|
* const taskMaybe = pipe(taskResult, Task.Result.to.Maybe);
|
|
4600
4692
|
* ```
|
|
4601
4693
|
*/
|
|
@@ -4658,7 +4750,7 @@ var TaskResult = {
|
|
|
4658
4750
|
* fetchTask,
|
|
4659
4751
|
* Task.Result.recoverUnless(
|
|
4660
4752
|
* (e) => e === "fatal",
|
|
4661
|
-
* () => Task.Result.ok("fallback")
|
|
4753
|
+
* () => Task.Result.make.ok("fallback")
|
|
4662
4754
|
* )
|
|
4663
4755
|
* );
|
|
4664
4756
|
* ```
|
|
@@ -4721,7 +4813,7 @@ var TaskResult = {
|
|
|
4721
4813
|
*
|
|
4722
4814
|
* @example
|
|
4723
4815
|
* ```ts
|
|
4724
|
-
* pipe(Task.Result.ok(42), Task.Result.bindTo("value")); // Task.Result({ value: 42 })
|
|
4816
|
+
* pipe(Task.Result.make.ok(42), Task.Result.bindTo("value")); // Task.Result({ value: 42 })
|
|
4725
4817
|
* ```
|
|
4726
4818
|
*/
|
|
4727
4819
|
bindTo: (key) => (data) => mapTaskResult((a) => ({ [key]: a }))(data),
|
|
@@ -4731,8 +4823,8 @@ var TaskResult = {
|
|
|
4731
4823
|
* @example
|
|
4732
4824
|
* ```ts
|
|
4733
4825
|
* pipe(
|
|
4734
|
-
* Task.Result.ok({ a: 1 }),
|
|
4735
|
-
* Task.Result.bind("b", ({ a }) => Task.Result.ok(a + 1))
|
|
4826
|
+
* Task.Result.make.ok({ a: 1 }),
|
|
4827
|
+
* Task.Result.bind("b", ({ a }) => Task.Result.make.ok(a + 1))
|
|
4736
4828
|
* ); // Task.Result({ a: 1, b: 2 })
|
|
4737
4829
|
* ```
|
|
4738
4830
|
*/
|
|
@@ -4747,8 +4839,8 @@ var TaskResult = {
|
|
|
4747
4839
|
* @example
|
|
4748
4840
|
* ```ts
|
|
4749
4841
|
* Task.Result.struct({
|
|
4750
|
-
* name: Task.Result.ok("Alice"),
|
|
4751
|
-
* age: Task.Result.ok(30)
|
|
4842
|
+
* name: Task.Result.make.ok("Alice"),
|
|
4843
|
+
* age: Task.Result.make.ok(30)
|
|
4752
4844
|
* }); // Task.Result({ name: "Alice", age: 30 })
|
|
4753
4845
|
* ```
|
|
4754
4846
|
*/
|
|
@@ -4868,1553 +4960,1550 @@ var TaskResult = {
|
|
|
4868
4960
|
// src/internal/InternalTypes.ts
|
|
4869
4961
|
var isNonEmptyArr = (list) => list.length > 0;
|
|
4870
4962
|
|
|
4871
|
-
// src/Core/
|
|
4872
|
-
var makePassed = (value) =>
|
|
4873
|
-
var makeFailed = (error) =>
|
|
4874
|
-
var makeFailedAll = (errors) =>
|
|
4875
|
-
var
|
|
4963
|
+
// src/Core/Validation.ts
|
|
4964
|
+
var makePassed = (value) => ({ kind: "Passed", value });
|
|
4965
|
+
var makeFailed = (error) => ({ kind: "Failed", errors: [error] });
|
|
4966
|
+
var makeFailedAll = (errors) => ({ kind: "Failed", errors });
|
|
4967
|
+
var isPassed = (data) => data.kind === "Passed";
|
|
4968
|
+
var isFailed = (data) => data.kind === "Failed";
|
|
4969
|
+
function toResult(arg) {
|
|
4970
|
+
if (typeof arg === "function") {
|
|
4971
|
+
const combine = arg;
|
|
4972
|
+
return (val) => isPassed(val) ? Result.make.ok(val.value) : Result.make.err(combine(val.errors));
|
|
4973
|
+
}
|
|
4974
|
+
return isPassed(arg) ? Result.make.ok(arg.value) : Result.make.err(arg.errors);
|
|
4975
|
+
}
|
|
4976
|
+
var Validation = {
|
|
4876
4977
|
make: {
|
|
4877
4978
|
/**
|
|
4878
|
-
* Wraps a value in a passed
|
|
4979
|
+
* Wraps a value in a passed Validation.
|
|
4879
4980
|
*
|
|
4880
4981
|
* @example
|
|
4881
4982
|
* ```ts
|
|
4882
|
-
*
|
|
4883
|
-
* const res = await task(); // Passed(42)
|
|
4983
|
+
* Validation.make.passed(42); // Passed(42)
|
|
4884
4984
|
* ```
|
|
4885
4985
|
*/
|
|
4886
4986
|
passed: makePassed,
|
|
4887
4987
|
/**
|
|
4888
|
-
* Creates a failed
|
|
4988
|
+
* Creates a failed Validation from a single error.
|
|
4889
4989
|
*
|
|
4890
4990
|
* @example
|
|
4891
4991
|
* ```ts
|
|
4892
|
-
*
|
|
4893
|
-
* const res = await task(); // Failed(["invalid"])
|
|
4992
|
+
* Validation.make.failed("Invalid input");
|
|
4894
4993
|
* ```
|
|
4895
4994
|
*/
|
|
4896
4995
|
failed: makeFailed,
|
|
4897
4996
|
/**
|
|
4898
|
-
* Creates a failed
|
|
4997
|
+
* Creates a failed Validation from multiple errors.
|
|
4899
4998
|
*
|
|
4900
4999
|
* @example
|
|
4901
5000
|
* ```ts
|
|
4902
|
-
*
|
|
4903
|
-
* const res = await task(); // Failed(["err1", "err2"])
|
|
5001
|
+
* Validation.make.failedAll(["Invalid input"]);
|
|
4904
5002
|
* ```
|
|
4905
5003
|
*/
|
|
4906
5004
|
failedAll: makeFailedAll
|
|
4907
5005
|
},
|
|
4908
|
-
|
|
4909
|
-
failed: makeFailed,
|
|
4910
|
-
failedAll: makeFailedAll,
|
|
4911
|
-
// --- from ---
|
|
4912
|
-
from: {
|
|
5006
|
+
is: {
|
|
4913
5007
|
/**
|
|
4914
|
-
*
|
|
5008
|
+
* Type guard that checks if a Validation is passed.
|
|
4915
5009
|
*
|
|
4916
5010
|
* @example
|
|
4917
5011
|
* ```ts
|
|
4918
|
-
*
|
|
5012
|
+
* const v = Validation.make.passed(42);
|
|
5013
|
+
* if (Validation.is.passed(v)) {
|
|
5014
|
+
* console.log(v.value); // 42
|
|
5015
|
+
* }
|
|
4919
5016
|
* ```
|
|
4920
5017
|
*/
|
|
4921
|
-
|
|
5018
|
+
passed: isPassed,
|
|
4922
5019
|
/**
|
|
4923
|
-
*
|
|
4924
|
-
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
4925
|
-
* Otherwise, returns Passed.
|
|
5020
|
+
* Type guard that checks if a Validation is failed.
|
|
4926
5021
|
*
|
|
4927
5022
|
* @example
|
|
4928
5023
|
* ```ts
|
|
4929
|
-
*
|
|
4930
|
-
*
|
|
5024
|
+
* const v = Validation.make.failed("invalid");
|
|
5025
|
+
* if (Validation.is.failed(v)) {
|
|
5026
|
+
* console.log(v.errors); // ["invalid"]
|
|
5027
|
+
* }
|
|
4931
5028
|
* ```
|
|
4932
5029
|
*/
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
5030
|
+
failed: isFailed
|
|
5031
|
+
},
|
|
5032
|
+
/**
|
|
5033
|
+
* Creates a Validation from a synchronous thunk that may throw.
|
|
5034
|
+
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
5035
|
+
*
|
|
5036
|
+
* @example
|
|
5037
|
+
* ```ts
|
|
5038
|
+
* const result = Validation.tryCatch(
|
|
5039
|
+
* () => JSON.parse(rawString),
|
|
5040
|
+
* { onError: (e) => `Parse error: ${e}` }
|
|
5041
|
+
* );
|
|
5042
|
+
* ```
|
|
5043
|
+
*/
|
|
5044
|
+
tryCatch: (f, options) => {
|
|
5045
|
+
try {
|
|
5046
|
+
return makePassed(f());
|
|
5047
|
+
} catch (error) {
|
|
5048
|
+
return makeFailed(options.onError(error));
|
|
5049
|
+
}
|
|
5050
|
+
},
|
|
5051
|
+
// --- from ---
|
|
5052
|
+
from: {
|
|
4936
5053
|
/**
|
|
4937
|
-
* Creates a
|
|
4938
|
-
*
|
|
5054
|
+
* Creates a Validation from a predicate applied to a value.
|
|
5055
|
+
* Returns Passed if the predicate passes, Failed from `onFalse` otherwise.
|
|
4939
5056
|
*
|
|
4940
5057
|
* @example
|
|
4941
5058
|
* ```ts
|
|
4942
|
-
*
|
|
4943
|
-
*
|
|
5059
|
+
* const validateName = Validation.from.Predicate(
|
|
5060
|
+
* (s: string) => s.length > 0,
|
|
5061
|
+
* () => "Name is required"
|
|
5062
|
+
* );
|
|
5063
|
+
*
|
|
5064
|
+
* validateName("Alice"); // Passed("Alice")
|
|
5065
|
+
* validateName(""); // Failed(["Name is required"])
|
|
4944
5066
|
* ```
|
|
4945
5067
|
*/
|
|
4946
|
-
|
|
4947
|
-
Maybe.is.none(maybe) ? Validation.make.failed(onNone()) : Validation.make.passed(maybe.value)
|
|
4948
|
-
),
|
|
5068
|
+
Predicate: (pred, onFalse) => (a) => pred(a) ? makePassed(a) : makeFailed(onFalse(a)),
|
|
4949
5069
|
/**
|
|
4950
|
-
* Creates a
|
|
4951
|
-
*
|
|
5070
|
+
* Creates a Validation from a nullable value.
|
|
5071
|
+
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
5072
|
+
* Otherwise, returns Passed.
|
|
4952
5073
|
*
|
|
4953
5074
|
* @example
|
|
4954
5075
|
* ```ts
|
|
4955
|
-
*
|
|
4956
|
-
*
|
|
5076
|
+
* pipe(null, Validation.from.nullable(() => "is null")); // Failed(["is null"])
|
|
5077
|
+
* pipe(42, Validation.from.nullable(() => "is null")); // Passed(42)
|
|
4957
5078
|
* ```
|
|
4958
5079
|
*/
|
|
4959
|
-
|
|
4960
|
-
},
|
|
4961
|
-
// --- to ---
|
|
4962
|
-
to: {
|
|
5080
|
+
nullable: (onNull) => (value) => value === null || value === void 0 ? makeFailed(onNull()) : makePassed(value),
|
|
4963
5081
|
/**
|
|
4964
|
-
*
|
|
4965
|
-
*
|
|
5082
|
+
* Creates a Validation from a Maybe.
|
|
5083
|
+
* If the Maybe is None, returns Failed with the error from onNone.
|
|
5084
|
+
* Otherwise, returns Passed.
|
|
4966
5085
|
*
|
|
4967
5086
|
* @example
|
|
4968
5087
|
* ```ts
|
|
4969
|
-
*
|
|
5088
|
+
* pipe(Maybe.make.none(), Validation.from.Maybe(() => "is none")); // Failed(["is none"])
|
|
5089
|
+
* pipe(Maybe.make.some(42), Validation.from.Maybe(() => "is none")); // Passed(42)
|
|
4970
5090
|
* ```
|
|
4971
5091
|
*/
|
|
4972
|
-
|
|
5092
|
+
Maybe: (onNone) => (maybe) => Maybe.is.none(maybe) ? makeFailed(onNone()) : makePassed(maybe.value),
|
|
4973
5093
|
/**
|
|
4974
|
-
* Converts a `
|
|
4975
|
-
*
|
|
5094
|
+
* Converts a `Result` to a `Validation`. `Ok` becomes `Passed`; `Err(e)` becomes `Failed([e])`.
|
|
5095
|
+
*
|
|
5096
|
+
* Useful when bridging from error-short-circuiting `Result` pipelines into
|
|
5097
|
+
* error-accumulating `Validation` pipelines.
|
|
4976
5098
|
*
|
|
4977
5099
|
* @example
|
|
4978
5100
|
* ```ts
|
|
4979
|
-
*
|
|
5101
|
+
* Validation.from.Result(Result.make.ok(42)); // Passed(42)
|
|
5102
|
+
* Validation.from.Result(Result.make.err("bad")); // Failed(["bad"])
|
|
4980
5103
|
* ```
|
|
4981
5104
|
*/
|
|
4982
|
-
|
|
5105
|
+
Result: (data) => data.kind === "Ok" ? makePassed(data.value) : makeFailed(data.error)
|
|
4983
5106
|
},
|
|
4984
5107
|
/**
|
|
4985
|
-
*
|
|
4986
|
-
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
4987
|
-
* The thunk optionally receives an `AbortSignal` forwarded from the call site.
|
|
5108
|
+
* Transforms the success value inside a Validation.
|
|
4988
5109
|
*
|
|
4989
5110
|
* @example
|
|
4990
5111
|
* ```ts
|
|
4991
|
-
*
|
|
4992
|
-
*
|
|
4993
|
-
* { onError: (e) => `Failed to load config: ${e}` }
|
|
4994
|
-
* );
|
|
5112
|
+
* pipe(Validation.make.passed(5), Validation.map(n => n * 2)); // Passed(10)
|
|
5113
|
+
* pipe(Validation.make.failed("oops"), Validation.map(n => n * 2)); // Failed(["oops"])
|
|
4995
5114
|
* ```
|
|
4996
5115
|
*/
|
|
4997
|
-
|
|
4998
|
-
// oxlint-disable-next-line require-await
|
|
4999
|
-
globalThis.Promise.resolve().then(async () => f(signal)).then(Validation.make.passed).catch(
|
|
5000
|
-
(error) => Validation.make.failed(options.onError(error))
|
|
5001
|
-
)
|
|
5002
|
-
),
|
|
5003
|
-
/**
|
|
5004
|
-
* Transforms the success value inside a Task.Validation.
|
|
5005
|
-
*/
|
|
5006
|
-
map: (f) => (data) => Task.map(Validation.map(f))(data),
|
|
5116
|
+
map: (f) => (data) => isPassed(data) ? makePassed(f(data.value)) : data,
|
|
5007
5117
|
/**
|
|
5008
|
-
*
|
|
5009
|
-
* Task.Validation. Both Tasks run in parallel and errors from both sides
|
|
5010
|
-
* are accumulated.
|
|
5118
|
+
* Transforms the error list inside a Validation.
|
|
5011
5119
|
*
|
|
5012
5120
|
* @example
|
|
5013
5121
|
* ```ts
|
|
5014
|
-
* pipe(
|
|
5015
|
-
* Task.Validation.passed((name: string) => (age: number) => ({ name, age })),
|
|
5016
|
-
* Task.Validation.ap(validateName(name)),
|
|
5017
|
-
* Task.Validation.ap(validateAge(age))
|
|
5018
|
-
* )();
|
|
5122
|
+
* pipe(Validation.make.failed("oops"), Validation.mapError(e => e.toUpperCase())); // Failed(["OOPS"])
|
|
5019
5123
|
* ```
|
|
5020
5124
|
*/
|
|
5021
|
-
|
|
5022
|
-
Promise.all([Deferred.to.Promise(data(signal)), Deferred.to.Promise(arg(signal))]).then(
|
|
5023
|
-
([vf, va]) => Validation.ap(va)(vf)
|
|
5024
|
-
)
|
|
5025
|
-
),
|
|
5026
|
-
/**
|
|
5027
|
-
* Extracts a value from a Task.Validation by providing handlers for both cases.
|
|
5028
|
-
*/
|
|
5029
|
-
fold: (onFailed, onPassed) => (data) => Task.map(Validation.fold(onFailed, onPassed))(data),
|
|
5125
|
+
mapError: (f) => (data) => isFailed(data) ? makeFailedAll(data.errors.map(f)) : data,
|
|
5030
5126
|
/**
|
|
5031
|
-
*
|
|
5127
|
+
* Applies a function wrapped in a Validation to a value wrapped in a Validation.
|
|
5128
|
+
* Accumulates errors from both sides.
|
|
5032
5129
|
*
|
|
5033
5130
|
* @example
|
|
5034
5131
|
* ```ts
|
|
5132
|
+
* const add = (a: number) => (b: number) => a + b;
|
|
5035
5133
|
* pipe(
|
|
5036
|
-
*
|
|
5037
|
-
*
|
|
5038
|
-
*
|
|
5039
|
-
*
|
|
5040
|
-
*
|
|
5041
|
-
*
|
|
5134
|
+
* Validation.make.passed(add),
|
|
5135
|
+
* Validation.ap(Validation.make.passed(5)),
|
|
5136
|
+
* Validation.ap(Validation.make.passed(3))
|
|
5137
|
+
* ); // Passed(8)
|
|
5138
|
+
*
|
|
5139
|
+
* pipe(
|
|
5140
|
+
* Validation.make.passed(add),
|
|
5141
|
+
* Validation.ap(Validation.make.failed<string>("bad a")),
|
|
5142
|
+
* Validation.ap(Validation.make.failed<string>("bad b"))
|
|
5143
|
+
* ); // Failed(["bad a", "bad b"])
|
|
5042
5144
|
* ```
|
|
5043
5145
|
*/
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
/**
|
|
5051
|
-
* Executes a side effect on the success value without changing the Task.Validation.
|
|
5052
|
-
* Useful for logging or debugging.
|
|
5053
|
-
*/
|
|
5054
|
-
tap: (f) => (data) => Task.map(Validation.tap(f))(data),
|
|
5055
|
-
/**
|
|
5056
|
-
* Recovers from a Failed state by providing a fallback Task.Validation.
|
|
5057
|
-
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
5058
|
-
* The fallback can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
5059
|
-
*/
|
|
5060
|
-
recover: (fallback) => (data) => Task.chain(
|
|
5061
|
-
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : fallback(validation.errors)
|
|
5062
|
-
)(data),
|
|
5146
|
+
ap: (arg) => (data) => {
|
|
5147
|
+
if (isPassed(data)) {
|
|
5148
|
+
return isPassed(arg) ? makePassed(data.value(arg.value)) : makeFailedAll(arg.errors);
|
|
5149
|
+
}
|
|
5150
|
+
return isPassed(arg) ? makeFailedAll(data.errors) : makeFailedAll([...data.errors, ...arg.errors]);
|
|
5151
|
+
},
|
|
5063
5152
|
/**
|
|
5064
|
-
*
|
|
5065
|
-
*
|
|
5153
|
+
* Applies a function wrapped in a Validation to a value wrapped in a Validation,
|
|
5154
|
+
* using a custom error concatenator function when both sides fail.
|
|
5066
5155
|
*
|
|
5067
5156
|
* @example
|
|
5068
5157
|
* ```ts
|
|
5069
|
-
*
|
|
5070
|
-
*
|
|
5071
|
-
*
|
|
5072
|
-
* (errors) => errors.includes("fatal"),
|
|
5073
|
-
* (errors) => Task.Validation.passed("fallback")
|
|
5074
|
-
* )
|
|
5075
|
-
* );
|
|
5158
|
+
* const concat = (e1: NonEmptyArr<string>, e2: NonEmptyArr<string>): NonEmptyArr<string> =>
|
|
5159
|
+
* [...e1, ...e2];
|
|
5160
|
+
* pipe(fnVal, Validation.apCustom(concat)(argVal));
|
|
5076
5161
|
* ```
|
|
5077
5162
|
*/
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5163
|
+
apCustom: (concat) => (arg) => (data) => {
|
|
5164
|
+
if (isPassed(data)) {
|
|
5165
|
+
return isPassed(arg) ? makePassed(data.value(arg.value)) : makeFailedAll(arg.errors);
|
|
5166
|
+
}
|
|
5167
|
+
return isPassed(arg) ? makeFailedAll(data.errors) : makeFailedAll(concat(data.errors, arg.errors));
|
|
5168
|
+
},
|
|
5081
5169
|
/**
|
|
5082
|
-
*
|
|
5083
|
-
* If both are Passed, returns Passed with both values. If either fails, accumulates
|
|
5084
|
-
* errors from both sides.
|
|
5170
|
+
* Extracts the value from a Validation by providing handlers for both cases.
|
|
5085
5171
|
*
|
|
5086
5172
|
* @example
|
|
5087
5173
|
* ```ts
|
|
5088
|
-
*
|
|
5089
|
-
*
|
|
5090
|
-
*
|
|
5091
|
-
*
|
|
5174
|
+
* pipe(
|
|
5175
|
+
* Validation.make.passed(42),
|
|
5176
|
+
* Validation.fold(
|
|
5177
|
+
* errors => `Errors: ${errors.join(", ")}`,
|
|
5178
|
+
* value => `Value: ${value}`
|
|
5179
|
+
* )
|
|
5180
|
+
* );
|
|
5092
5181
|
* ```
|
|
5093
5182
|
*/
|
|
5094
|
-
|
|
5095
|
-
Promise.all([Deferred.to.Promise(first(signal)), Deferred.to.Promise(second(signal))]).then(
|
|
5096
|
-
([va, vb]) => Validation.product(va, vb)
|
|
5097
|
-
)
|
|
5098
|
-
),
|
|
5183
|
+
fold: (onFailed, onPassed) => (data) => isPassed(data) ? onPassed(data.value) : onFailed(data.errors),
|
|
5099
5184
|
/**
|
|
5100
|
-
*
|
|
5101
|
-
* If all are Passed, returns Passed with all values as an array.
|
|
5102
|
-
* If any fail, returns Failed with all accumulated errors.
|
|
5185
|
+
* Pattern matches on a Validation, returning the result of the matching case.
|
|
5103
5186
|
*
|
|
5104
5187
|
* @example
|
|
5105
5188
|
* ```ts
|
|
5106
|
-
*
|
|
5107
|
-
*
|
|
5108
|
-
*
|
|
5109
|
-
*
|
|
5110
|
-
*
|
|
5189
|
+
* pipe(
|
|
5190
|
+
* validation,
|
|
5191
|
+
* Validation.match({
|
|
5192
|
+
* passed: value => `Got ${value}`,
|
|
5193
|
+
* failed: errors => `Failed: ${errors.join(", ")}`
|
|
5194
|
+
* })
|
|
5195
|
+
* );
|
|
5111
5196
|
* ```
|
|
5112
5197
|
*/
|
|
5113
|
-
|
|
5114
|
-
Promise.all(data.map((t) => Deferred.to.Promise(t(signal)))).then((results) => {
|
|
5115
|
-
const [first, ...rest] = results;
|
|
5116
|
-
return Validation.productAll([first, ...rest]);
|
|
5117
|
-
})
|
|
5118
|
-
),
|
|
5198
|
+
match: (cases) => (data) => isPassed(data) ? cases.passed(data.value) : cases.failed(data.errors),
|
|
5119
5199
|
/**
|
|
5120
|
-
*
|
|
5200
|
+
* Returns the success value or a default value if the Validation is failed.
|
|
5201
|
+
* The default can be a different type, widening the result to `A | B`.
|
|
5121
5202
|
*
|
|
5122
5203
|
* @example
|
|
5123
5204
|
* ```ts
|
|
5124
|
-
* pipe(
|
|
5125
|
-
*
|
|
5126
|
-
*
|
|
5127
|
-
* ); // Task.Validation(Failed(["OOPS"]))
|
|
5205
|
+
* pipe(Validation.make.passed(5), Validation.getOrElse(() => 0)); // 5
|
|
5206
|
+
* pipe(Validation.make.failed("oops"), Validation.getOrElse(() => 0)); // 0
|
|
5207
|
+
* pipe(Validation.make.failed("oops"), Validation.getOrElse(() => null)); // null — typed as number | null
|
|
5128
5208
|
* ```
|
|
5129
5209
|
*/
|
|
5130
|
-
|
|
5210
|
+
getOrElse: (defaultValue) => (data) => isPassed(data) ? data.value : defaultValue(),
|
|
5131
5211
|
/**
|
|
5132
|
-
* Executes a side effect on the
|
|
5212
|
+
* Executes a side effect on the success value without changing the Validation.
|
|
5133
5213
|
*
|
|
5134
5214
|
* @example
|
|
5135
5215
|
* ```ts
|
|
5136
5216
|
* pipe(
|
|
5137
|
-
*
|
|
5138
|
-
*
|
|
5217
|
+
* Validation.make.passed(5),
|
|
5218
|
+
* Validation.tap(n => console.log("Value:", n)),
|
|
5219
|
+
* Validation.map(n => n * 2)
|
|
5139
5220
|
* );
|
|
5140
5221
|
* ```
|
|
5141
5222
|
*/
|
|
5142
|
-
|
|
5223
|
+
tap: (f) => (data) => {
|
|
5224
|
+
if (isPassed(data)) {
|
|
5225
|
+
f(data.value);
|
|
5226
|
+
}
|
|
5227
|
+
return data;
|
|
5228
|
+
},
|
|
5143
5229
|
/**
|
|
5144
|
-
*
|
|
5145
|
-
*
|
|
5230
|
+
* Executes a side effect on the accumulated errors without changing the Validation.
|
|
5231
|
+
* Useful for logging or reporting validation failures.
|
|
5146
5232
|
*
|
|
5147
5233
|
* @example
|
|
5148
5234
|
* ```ts
|
|
5149
|
-
*
|
|
5150
|
-
*
|
|
5151
|
-
*
|
|
5152
|
-
*
|
|
5235
|
+
* pipe(
|
|
5236
|
+
* Validation.make.failed("Name required"),
|
|
5237
|
+
* Validation.tapError(errors => console.error("validation failed:", errors)),
|
|
5238
|
+
* Validation.map(toUser)
|
|
5239
|
+
* );
|
|
5153
5240
|
* ```
|
|
5154
5241
|
*/
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
|
|
5161
|
-
for (let i = 0; i < keys.length; i++) {
|
|
5162
|
-
const res = results[i];
|
|
5163
|
-
if (Validation.is.passed(res)) {
|
|
5164
|
-
record[keys[i]] = res.value;
|
|
5165
|
-
} else {
|
|
5166
|
-
errors.push(...res.errors);
|
|
5167
|
-
}
|
|
5168
|
-
}
|
|
5169
|
-
return isNonEmptyArr(errors) ? Validation.make.failedAll(errors) : Validation.make.passed(record);
|
|
5170
|
-
});
|
|
5171
|
-
})()),
|
|
5242
|
+
tapError: (f) => (data) => {
|
|
5243
|
+
if (isFailed(data)) {
|
|
5244
|
+
f(data.errors);
|
|
5245
|
+
}
|
|
5246
|
+
return data;
|
|
5247
|
+
},
|
|
5172
5248
|
/**
|
|
5173
|
-
*
|
|
5174
|
-
*
|
|
5175
|
-
*
|
|
5176
|
-
* @example
|
|
5177
|
-
* ```ts
|
|
5178
|
-
* const validate = Task.Validation.memoize(validateFormTask);
|
|
5179
|
-
* ```
|
|
5249
|
+
* Recovers from a Failed state by providing a fallback Validation.
|
|
5250
|
+
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
5251
|
+
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
5180
5252
|
*/
|
|
5181
|
-
|
|
5182
|
-
};
|
|
5183
|
-
|
|
5184
|
-
// src/Core/Task.ts
|
|
5185
|
-
var toPromise2 = (task, signal) => Deferred.to.Promise(task(signal));
|
|
5186
|
-
var fromPromise2 = (f) => (signal) => Deferred.from.Promise(f(signal));
|
|
5187
|
-
var getMs2 = (duration) => Duration.to.milliseconds(duration);
|
|
5188
|
-
var resolveTask = (value) => () => Deferred.from.Promise(globalThis.Promise.resolve(value));
|
|
5189
|
-
var syncTask = (f) => () => Deferred.from.Promise(globalThis.Promise.resolve(f()));
|
|
5190
|
-
var Task = {
|
|
5253
|
+
recover: (fallback) => (data) => isPassed(data) ? data : fallback(data.errors),
|
|
5191
5254
|
/**
|
|
5192
|
-
*
|
|
5255
|
+
* Recovers from a Failed state unless `isBlocked` returns true for any of the accumulated errors.
|
|
5256
|
+
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
5193
5257
|
*
|
|
5194
5258
|
* @example
|
|
5195
5259
|
* ```ts
|
|
5196
|
-
*
|
|
5197
|
-
*
|
|
5260
|
+
* pipe(
|
|
5261
|
+
* Validation.make.failed("field-error"),
|
|
5262
|
+
* Validation.recoverUnless(e => e === "fatal", () => Validation.make.passed(0))
|
|
5263
|
+
* ); // Passed(0)
|
|
5198
5264
|
* ```
|
|
5199
5265
|
*/
|
|
5200
|
-
|
|
5201
|
-
// ---
|
|
5202
|
-
|
|
5266
|
+
recoverUnless: (isBlocked, fallback) => (data) => isFailed(data) && !data.errors.some(isBlocked) ? fallback() : data,
|
|
5267
|
+
// --- to ---
|
|
5268
|
+
to: {
|
|
5203
5269
|
/**
|
|
5204
|
-
*
|
|
5205
|
-
*
|
|
5270
|
+
* Converts a Validation to a Result.
|
|
5271
|
+
* Passed becomes Ok.
|
|
5272
|
+
* Direct call converts Failed to Err with accumulated error list `NonEmptyArr<E>`.
|
|
5273
|
+
* Curried call converts Failed to Err with combined error `E2` via `combineErrors`.
|
|
5206
5274
|
*
|
|
5207
5275
|
* @example
|
|
5208
5276
|
* ```ts
|
|
5209
|
-
*
|
|
5210
|
-
*
|
|
5277
|
+
* Validation.to.Result(Validation.make.passed(42)); // Ok(42)
|
|
5278
|
+
* Validation.to.Result(Validation.make.failed("oops")); // Err(["oops"])
|
|
5279
|
+
* pipe(Validation.make.failed("oops"), Validation.to.Result(errors => errors.join(", "))); // Err("oops")
|
|
5211
5280
|
* ```
|
|
5212
5281
|
*/
|
|
5213
|
-
|
|
5282
|
+
Result: toResult,
|
|
5283
|
+
/**
|
|
5284
|
+
* Converts a Validation to a Maybe. `Passed` becomes `Some`; `Failed` becomes `None`
|
|
5285
|
+
* (errors are discarded).
|
|
5286
|
+
*
|
|
5287
|
+
* @example
|
|
5288
|
+
* ```ts
|
|
5289
|
+
* Validation.to.Maybe(Validation.make.passed(42)); // Some(42)
|
|
5290
|
+
* Validation.to.Maybe(Validation.make.failed("bad")); // None
|
|
5291
|
+
* ```
|
|
5292
|
+
*/
|
|
5293
|
+
Maybe: (data) => isPassed(data) ? Maybe.make.some(data.value) : Maybe.make.none()
|
|
5214
5294
|
},
|
|
5215
5295
|
/**
|
|
5216
|
-
*
|
|
5217
|
-
*
|
|
5296
|
+
* Combines two independent Validation instances into a tuple.
|
|
5297
|
+
* If both are Passed, returns Passed with both values as a tuple.
|
|
5298
|
+
* If either is Failed, accumulates errors from both sides.
|
|
5218
5299
|
*
|
|
5219
5300
|
* @example
|
|
5220
5301
|
* ```ts
|
|
5221
|
-
*
|
|
5222
|
-
*
|
|
5223
|
-
*
|
|
5224
|
-
* );
|
|
5302
|
+
* Validation.product(
|
|
5303
|
+
* Validation.make.passed("alice"),
|
|
5304
|
+
* Validation.make.passed(30)
|
|
5305
|
+
* ); // Passed(["alice", 30])
|
|
5306
|
+
*
|
|
5307
|
+
* Validation.product(
|
|
5308
|
+
* Validation.make.failed("Name required"),
|
|
5309
|
+
* Validation.make.failed("Age must be >= 0")
|
|
5310
|
+
* ); // Failed(["Name required", "Age must be >= 0"])
|
|
5225
5311
|
* ```
|
|
5226
5312
|
*/
|
|
5227
|
-
|
|
5313
|
+
product: (first, second) => {
|
|
5314
|
+
if (isPassed(first)) {
|
|
5315
|
+
return isPassed(second) ? makePassed([first.value, second.value]) : makeFailedAll(second.errors);
|
|
5316
|
+
}
|
|
5317
|
+
return isPassed(second) ? makeFailedAll(first.errors) : makeFailedAll([...first.errors, ...second.errors]);
|
|
5318
|
+
},
|
|
5228
5319
|
/**
|
|
5229
|
-
*
|
|
5320
|
+
* Combines a non-empty list of Validation instances, accumulating all errors.
|
|
5321
|
+
* If all are Passed, returns Passed with all values collected into an array.
|
|
5322
|
+
* If any are Failed, returns Failed with all accumulated errors.
|
|
5230
5323
|
*
|
|
5231
5324
|
* @example
|
|
5232
5325
|
* ```ts
|
|
5233
|
-
*
|
|
5234
|
-
*
|
|
5235
|
-
*
|
|
5236
|
-
*
|
|
5326
|
+
* Validation.productAll([
|
|
5327
|
+
* validateName(name),
|
|
5328
|
+
* validateEmail(email),
|
|
5329
|
+
* validateAge(age)
|
|
5330
|
+
* ]);
|
|
5331
|
+
* // Passed([name, email, age]) or Failed([...all errors])
|
|
5237
5332
|
* ```
|
|
5238
5333
|
*/
|
|
5239
|
-
|
|
5334
|
+
productAll: (data) => {
|
|
5335
|
+
const values = [];
|
|
5336
|
+
const errors = [];
|
|
5337
|
+
for (const v of data) {
|
|
5338
|
+
if (isPassed(v)) {
|
|
5339
|
+
values.push(v.value);
|
|
5340
|
+
} else {
|
|
5341
|
+
errors.push(...v.errors);
|
|
5342
|
+
}
|
|
5343
|
+
}
|
|
5344
|
+
return isNonEmptyArr(errors) ? makeFailedAll(errors) : makePassed(values);
|
|
5345
|
+
},
|
|
5240
5346
|
/**
|
|
5241
|
-
*
|
|
5347
|
+
* Combines a record of Validations into a single Validation of a record.
|
|
5348
|
+
* Accumulates all failed branches' errors.
|
|
5242
5349
|
*
|
|
5243
5350
|
* @example
|
|
5244
5351
|
* ```ts
|
|
5245
|
-
*
|
|
5246
|
-
*
|
|
5247
|
-
*
|
|
5248
|
-
*
|
|
5249
|
-
* pipe(
|
|
5250
|
-
* readUserId,
|
|
5251
|
-
* Task.chain(loadPrefs)
|
|
5252
|
-
* )(); // Deferred<Preferences>
|
|
5253
|
-
* ```
|
|
5254
|
-
*/
|
|
5255
|
-
chain: (f) => (data) => fromPromise2((signal) => toPromise2(data, signal).then((a) => toPromise2(f(a), signal))),
|
|
5256
|
-
/**
|
|
5257
|
-
* Applies a function wrapped in a Task to a value wrapped in a Task.
|
|
5258
|
-
* Both Tasks run in parallel.
|
|
5352
|
+
* Validation.struct({
|
|
5353
|
+
* name: Validation.make.passed("Alice"),
|
|
5354
|
+
* age: Validation.make.passed(30)
|
|
5355
|
+
* }); // Passed({ name: "Alice", age: 30 })
|
|
5259
5356
|
*
|
|
5260
|
-
*
|
|
5261
|
-
*
|
|
5262
|
-
*
|
|
5263
|
-
*
|
|
5264
|
-
* Task.resolve(add),
|
|
5265
|
-
* Task.ap(Task.resolve(5)),
|
|
5266
|
-
* Task.ap(Task.resolve(3))
|
|
5267
|
-
* )(); // Deferred<8>
|
|
5357
|
+
* Validation.struct({
|
|
5358
|
+
* name: Validation.make.failed("Name required"),
|
|
5359
|
+
* age: Validation.make.failed("Age must be >= 0")
|
|
5360
|
+
* }); // Failed(["Name required", "Age must be >= 0"])
|
|
5268
5361
|
* ```
|
|
5269
5362
|
*/
|
|
5270
|
-
|
|
5363
|
+
struct: (fields) => {
|
|
5364
|
+
const record = {};
|
|
5365
|
+
const errors = [];
|
|
5366
|
+
for (const key in fields) {
|
|
5367
|
+
if (Object.hasOwn(fields, key)) {
|
|
5368
|
+
const val = fields[key];
|
|
5369
|
+
if (isPassed(val)) {
|
|
5370
|
+
record[key] = val.value;
|
|
5371
|
+
} else {
|
|
5372
|
+
errors.push(...val.errors);
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
}
|
|
5376
|
+
return isNonEmptyArr(errors) ? makeFailedAll(errors) : makePassed(record);
|
|
5377
|
+
}
|
|
5378
|
+
};
|
|
5379
|
+
|
|
5380
|
+
// src/Core/TaskValidation.ts
|
|
5381
|
+
var makePassed2 = (value) => Task.resolve(Validation.make.passed(value));
|
|
5382
|
+
var makeFailed2 = (error) => Task.resolve(Validation.make.failed(error));
|
|
5383
|
+
var makeFailedAll2 = (errors) => Task.resolve(Validation.make.failedAll(errors));
|
|
5384
|
+
var TaskValidation = {
|
|
5385
|
+
make: {
|
|
5386
|
+
/**
|
|
5387
|
+
* Wraps a value in a passed Task.Validation.
|
|
5388
|
+
*
|
|
5389
|
+
* @example
|
|
5390
|
+
* ```ts
|
|
5391
|
+
* const task = Task.Validation.make.passed(42);
|
|
5392
|
+
* const res = await task(); // Passed(42)
|
|
5393
|
+
* ```
|
|
5394
|
+
*/
|
|
5395
|
+
passed: makePassed2,
|
|
5396
|
+
/**
|
|
5397
|
+
* Creates a failed Task.Validation with a single error.
|
|
5398
|
+
*
|
|
5399
|
+
* @example
|
|
5400
|
+
* ```ts
|
|
5401
|
+
* const task = Task.Validation.make.failed("invalid");
|
|
5402
|
+
* const res = await task(); // Failed(["invalid"])
|
|
5403
|
+
* ```
|
|
5404
|
+
*/
|
|
5405
|
+
failed: makeFailed2,
|
|
5406
|
+
/**
|
|
5407
|
+
* Creates a failed Task.Validation from multiple errors.
|
|
5408
|
+
*
|
|
5409
|
+
* @example
|
|
5410
|
+
* ```ts
|
|
5411
|
+
* const task = Task.Validation.make.failedAll(["err1", "err2"]);
|
|
5412
|
+
* const res = await task(); // Failed(["err1", "err2"])
|
|
5413
|
+
* ```
|
|
5414
|
+
*/
|
|
5415
|
+
failedAll: makeFailedAll2
|
|
5416
|
+
},
|
|
5417
|
+
// --- from ---
|
|
5418
|
+
from: {
|
|
5419
|
+
/**
|
|
5420
|
+
* Lifts a Validation into a Task.Validation.
|
|
5421
|
+
*
|
|
5422
|
+
* @example
|
|
5423
|
+
* ```ts
|
|
5424
|
+
* Task.Validation.from.Validation(Validation.make.passed(42));
|
|
5425
|
+
* ```
|
|
5426
|
+
*/
|
|
5427
|
+
Validation: (validation) => Task.resolve(validation),
|
|
5428
|
+
/**
|
|
5429
|
+
* Creates a Task.Validation from a nullable value.
|
|
5430
|
+
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
5431
|
+
* Otherwise, returns Passed.
|
|
5432
|
+
*
|
|
5433
|
+
* @example
|
|
5434
|
+
* ```ts
|
|
5435
|
+
* Task.Validation.from.nullable(() => "missing")(42); // resolves to Passed(42)
|
|
5436
|
+
* Task.Validation.from.nullable(() => "missing")(null); // resolves to Failed(["missing"])
|
|
5437
|
+
* ```
|
|
5438
|
+
*/
|
|
5439
|
+
nullable: (onNull) => (value) => Task.resolve(
|
|
5440
|
+
value === null || value === void 0 ? Validation.make.failed(onNull()) : Validation.make.passed(value)
|
|
5441
|
+
),
|
|
5442
|
+
/**
|
|
5443
|
+
* Creates a Task.Validation from a Maybe.
|
|
5444
|
+
* Some becomes Passed, None becomes Failed with the error from onNone.
|
|
5445
|
+
*
|
|
5446
|
+
* @example
|
|
5447
|
+
* ```ts
|
|
5448
|
+
* Task.Validation.from.Maybe(() => "empty")(Maybe.make.some(42)); // resolves to Passed(42)
|
|
5449
|
+
* Task.Validation.from.Maybe(() => "empty")(Maybe.make.none()); // resolves to Failed(["empty"])
|
|
5450
|
+
* ```
|
|
5451
|
+
*/
|
|
5452
|
+
Maybe: (onNone) => (maybe) => Task.resolve(
|
|
5453
|
+
Maybe.is.none(maybe) ? Validation.make.failed(onNone()) : Validation.make.passed(maybe.value)
|
|
5454
|
+
),
|
|
5455
|
+
/**
|
|
5456
|
+
* Creates a Task.Validation from a Result.
|
|
5457
|
+
* Ok becomes Passed, Err(e) becomes Failed([e]).
|
|
5458
|
+
*
|
|
5459
|
+
* @example
|
|
5460
|
+
* ```ts
|
|
5461
|
+
* Task.Validation.from.Result(Result.make.ok(42)); // resolves to Passed(42)
|
|
5462
|
+
* Task.Validation.from.Result(Result.make.err("bad")); // resolves to Failed(["bad"])
|
|
5463
|
+
* ```
|
|
5464
|
+
*/
|
|
5465
|
+
Result: (result) => Task.resolve(Validation.from.Result(result))
|
|
5466
|
+
},
|
|
5467
|
+
// --- to ---
|
|
5468
|
+
to: {
|
|
5469
|
+
/**
|
|
5470
|
+
* Converts a `Task.Validation` to a `Task.Result`, combining accumulated errors using `combineErrors`.
|
|
5471
|
+
* `Passed(a)` becomes `Ok(a)`; `Failed(errors)` becomes `Err(combineErrors(errors))`.
|
|
5472
|
+
*
|
|
5473
|
+
* @example
|
|
5474
|
+
* ```ts
|
|
5475
|
+
* Task.Validation.to.Result((errors) => errors.join(", "))(validationTask);
|
|
5476
|
+
* ```
|
|
5477
|
+
*/
|
|
5478
|
+
Result: (combineErrors) => (data) => Task.map(Validation.to.Result(combineErrors))(data),
|
|
5479
|
+
/**
|
|
5480
|
+
* Converts a `Task.Validation` to a `Task.Maybe`.
|
|
5481
|
+
* `Passed(a)` becomes `Some(a)`; `Failed(errors)` becomes `None` (errors are discarded).
|
|
5482
|
+
*
|
|
5483
|
+
* @example
|
|
5484
|
+
* ```ts
|
|
5485
|
+
* Task.Validation.to.Maybe(validationTask);
|
|
5486
|
+
* ```
|
|
5487
|
+
*/
|
|
5488
|
+
Maybe: (data) => Task.map(Validation.to.Maybe)(data)
|
|
5489
|
+
},
|
|
5271
5490
|
/**
|
|
5272
|
-
*
|
|
5273
|
-
*
|
|
5491
|
+
* Creates a Task.Validation from a Promise-returning thunk that may throw or reject.
|
|
5492
|
+
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
5493
|
+
* The thunk optionally receives an `AbortSignal` forwarded from the call site.
|
|
5274
5494
|
*
|
|
5275
5495
|
* @example
|
|
5276
5496
|
* ```ts
|
|
5277
|
-
*
|
|
5278
|
-
*
|
|
5279
|
-
*
|
|
5280
|
-
* Task.map(buildReport)
|
|
5497
|
+
* const loadConfig = Task.Validation.tryCatch(
|
|
5498
|
+
* (signal) => configStore.get("default", { signal }),
|
|
5499
|
+
* { onError: (e) => `Failed to load config: ${e}` }
|
|
5281
5500
|
* );
|
|
5282
5501
|
* ```
|
|
5283
5502
|
*/
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5503
|
+
tryCatch: (f, options) => (signal) => Deferred.from.Promise(
|
|
5504
|
+
// oxlint-disable-next-line require-await
|
|
5505
|
+
globalThis.Promise.resolve().then(async () => f(signal)).then(Validation.make.passed).catch(
|
|
5506
|
+
(error) => Validation.make.failed(options.onError(error))
|
|
5507
|
+
)
|
|
5289
5508
|
),
|
|
5290
5509
|
/**
|
|
5291
|
-
*
|
|
5292
|
-
*
|
|
5293
|
-
* @example
|
|
5294
|
-
* ```ts
|
|
5295
|
-
* Task.all([loadConfig, detectLocale, loadTheme])();
|
|
5296
|
-
* // Deferred<[Config, string, Theme]>
|
|
5297
|
-
* ```
|
|
5510
|
+
* Transforms the success value inside a Task.Validation.
|
|
5298
5511
|
*/
|
|
5299
|
-
|
|
5300
|
-
(signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))
|
|
5301
|
-
),
|
|
5512
|
+
map: (f) => (data) => Task.map(Validation.map(f))(data),
|
|
5302
5513
|
/**
|
|
5303
|
-
*
|
|
5304
|
-
*
|
|
5514
|
+
* Applies a function wrapped in a Task.Validation to a value wrapped in a
|
|
5515
|
+
* Task.Validation. Both Tasks run in parallel and errors from both sides
|
|
5516
|
+
* are accumulated.
|
|
5305
5517
|
*
|
|
5306
5518
|
* @example
|
|
5307
5519
|
* ```ts
|
|
5308
5520
|
* pipe(
|
|
5309
|
-
* Task.
|
|
5310
|
-
* Task.
|
|
5311
|
-
*
|
|
5521
|
+
* Task.Validation.make.passed((name: string) => (age: number) => ({ name, age })),
|
|
5522
|
+
* Task.Validation.ap(validateName(name)),
|
|
5523
|
+
* Task.Validation.ap(validateAge(age))
|
|
5524
|
+
* )();
|
|
5312
5525
|
* ```
|
|
5313
5526
|
*/
|
|
5314
|
-
|
|
5315
|
-
(signal)
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
clearTimeout(timerId);
|
|
5319
|
-
res(toPromise2(data, signal));
|
|
5320
|
-
};
|
|
5321
|
-
if (signal) {
|
|
5322
|
-
if (signal.aborted) {
|
|
5323
|
-
return res(toPromise2(data, signal));
|
|
5324
|
-
}
|
|
5325
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5326
|
-
}
|
|
5327
|
-
timerId = setTimeout(() => {
|
|
5328
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5329
|
-
res(toPromise2(data, signal));
|
|
5330
|
-
}, getMs2(duration));
|
|
5331
|
-
})
|
|
5527
|
+
ap: (arg) => (data) => (signal) => Deferred.from.Promise(
|
|
5528
|
+
Promise.all([Deferred.to.Promise(data(signal)), Deferred.to.Promise(arg(signal))]).then(
|
|
5529
|
+
([vf, va]) => Validation.ap(va)(vf)
|
|
5530
|
+
)
|
|
5332
5531
|
),
|
|
5333
5532
|
/**
|
|
5334
|
-
*
|
|
5335
|
-
* An optional delay duration can be inserted between runs.
|
|
5336
|
-
*
|
|
5337
|
-
* @example
|
|
5338
|
-
* ```ts
|
|
5339
|
-
* pipe(
|
|
5340
|
-
* pollSensor,
|
|
5341
|
-
* Task.repeat({ times: 5, delay: Duration.seconds(1) })
|
|
5342
|
-
* )(); // Task<Reading[]> — 5 readings, one per second
|
|
5343
|
-
* ```
|
|
5533
|
+
* Extracts a value from a Task.Validation by providing handlers for both cases.
|
|
5344
5534
|
*/
|
|
5345
|
-
|
|
5346
|
-
const { times, delay: delayDuration } = options;
|
|
5347
|
-
if (times <= 0) {
|
|
5348
|
-
return Promise.resolve([]);
|
|
5349
|
-
}
|
|
5350
|
-
const results = [];
|
|
5351
|
-
const wait = () => new Promise((r) => {
|
|
5352
|
-
let timerId;
|
|
5353
|
-
const onAbort = () => {
|
|
5354
|
-
clearTimeout(timerId);
|
|
5355
|
-
r();
|
|
5356
|
-
};
|
|
5357
|
-
if (signal) {
|
|
5358
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5359
|
-
}
|
|
5360
|
-
timerId = setTimeout(() => {
|
|
5361
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5362
|
-
r();
|
|
5363
|
-
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
5364
|
-
});
|
|
5365
|
-
const run = (left) => {
|
|
5366
|
-
if (signal?.aborted) {
|
|
5367
|
-
return Promise.resolve(results);
|
|
5368
|
-
}
|
|
5369
|
-
return toPromise2(task, signal).then((a) => {
|
|
5370
|
-
results.push(a);
|
|
5371
|
-
if (left <= 1 || signal?.aborted) {
|
|
5372
|
-
return results;
|
|
5373
|
-
}
|
|
5374
|
-
return wait().then(() => run(left - 1));
|
|
5375
|
-
});
|
|
5376
|
-
};
|
|
5377
|
-
return run(times);
|
|
5378
|
-
}),
|
|
5535
|
+
fold: (onFailed, onPassed) => (data) => Task.map(Validation.fold(onFailed, onPassed))(data),
|
|
5379
5536
|
/**
|
|
5380
|
-
*
|
|
5381
|
-
* An optional delay duration can be inserted between runs.
|
|
5382
|
-
* An optional `maxAttempts` cap stops the loop after N calls — the last value is returned
|
|
5383
|
-
* regardless of whether the predicate was satisfied.
|
|
5537
|
+
* Pattern matches on a Task.Validation, returning a Task of the result.
|
|
5384
5538
|
*
|
|
5385
5539
|
* @example
|
|
5386
5540
|
* ```ts
|
|
5387
5541
|
* pipe(
|
|
5388
|
-
*
|
|
5389
|
-
* Task.
|
|
5390
|
-
*
|
|
5542
|
+
* validateForm(input),
|
|
5543
|
+
* Task.Validation.match({
|
|
5544
|
+
* passed: data => save(data),
|
|
5545
|
+
* failed: errors => showErrors(errors)
|
|
5546
|
+
* })
|
|
5547
|
+
* )();
|
|
5391
5548
|
* ```
|
|
5392
5549
|
*/
|
|
5393
|
-
|
|
5394
|
-
const { when: predicate, delay: delayDuration, maxAttempts } = options;
|
|
5395
|
-
const wait = () => new Promise((r) => {
|
|
5396
|
-
let timerId;
|
|
5397
|
-
const onAbort = () => {
|
|
5398
|
-
clearTimeout(timerId);
|
|
5399
|
-
r();
|
|
5400
|
-
};
|
|
5401
|
-
if (signal) {
|
|
5402
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5403
|
-
}
|
|
5404
|
-
timerId = setTimeout(() => {
|
|
5405
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5406
|
-
r();
|
|
5407
|
-
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
5408
|
-
});
|
|
5409
|
-
const run = (attempt, lastValue) => {
|
|
5410
|
-
if (signal?.aborted && lastValue !== void 0) {
|
|
5411
|
-
return Promise.resolve(lastValue);
|
|
5412
|
-
}
|
|
5413
|
-
return toPromise2(task, signal).then((a) => {
|
|
5414
|
-
if (predicate(a)) {
|
|
5415
|
-
return a;
|
|
5416
|
-
}
|
|
5417
|
-
if (maxAttempts !== void 0 && attempt >= maxAttempts) {
|
|
5418
|
-
return a;
|
|
5419
|
-
}
|
|
5420
|
-
if (signal?.aborted) {
|
|
5421
|
-
return a;
|
|
5422
|
-
}
|
|
5423
|
-
return wait().then(() => run(attempt + 1, a));
|
|
5424
|
-
});
|
|
5425
|
-
};
|
|
5426
|
-
return run(1);
|
|
5427
|
-
}),
|
|
5550
|
+
match: (cases) => (data) => Task.map(Validation.match(cases))(data),
|
|
5428
5551
|
/**
|
|
5429
|
-
*
|
|
5430
|
-
*
|
|
5431
|
-
* downstream.
|
|
5432
|
-
*
|
|
5433
|
-
* @example
|
|
5434
|
-
* ```ts
|
|
5435
|
-
* const fast = Task.resolve("fast");
|
|
5436
|
-
* const slow = Task.delay(Duration.milliseconds(200))(Task.resolve("slow"));
|
|
5437
|
-
*
|
|
5438
|
-
* await Task.race([fast, slow])(); // "fast"
|
|
5439
|
-
* ```
|
|
5552
|
+
* Returns the success value or a default value if the Task.Validation is failed.
|
|
5553
|
+
* The default can be a different type, widening the result to `Task<A | B>`.
|
|
5440
5554
|
*/
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
onOuterAbort();
|
|
5456
|
-
} else {
|
|
5457
|
-
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
5458
|
-
}
|
|
5459
|
-
}
|
|
5460
|
-
const promises = tasks.map((task, idx) => {
|
|
5461
|
-
const ctrl = controllers[idx];
|
|
5462
|
-
return toPromise2(task, ctrl.signal).then((result) => {
|
|
5463
|
-
for (let i = 0; i < controllers.length; i++) {
|
|
5464
|
-
if (i !== idx) {
|
|
5465
|
-
controllers[i].abort();
|
|
5466
|
-
}
|
|
5467
|
-
}
|
|
5468
|
-
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
5469
|
-
return result;
|
|
5470
|
-
});
|
|
5471
|
-
});
|
|
5472
|
-
return Promise.race(promises);
|
|
5473
|
-
});
|
|
5474
|
-
},
|
|
5555
|
+
getOrElse: (defaultValue) => (data) => Task.map(Validation.getOrElse(defaultValue))(data),
|
|
5556
|
+
/**
|
|
5557
|
+
* Executes a side effect on the success value without changing the Task.Validation.
|
|
5558
|
+
* Useful for logging or debugging.
|
|
5559
|
+
*/
|
|
5560
|
+
tap: (f) => (data) => Task.map(Validation.tap(f))(data),
|
|
5561
|
+
/**
|
|
5562
|
+
* Recovers from a Failed state by providing a fallback Task.Validation.
|
|
5563
|
+
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
5564
|
+
* The fallback can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
5565
|
+
*/
|
|
5566
|
+
recover: (fallback) => (data) => Task.chain(
|
|
5567
|
+
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : fallback(validation.errors)
|
|
5568
|
+
)(data),
|
|
5475
5569
|
/**
|
|
5476
|
-
*
|
|
5477
|
-
*
|
|
5570
|
+
* Recovers from a Failed state unless the predicate `isBlocked` returns true for the accumulated errors.
|
|
5571
|
+
* The fallback receives the accumulated errors and can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
5478
5572
|
*
|
|
5479
5573
|
* @example
|
|
5480
5574
|
* ```ts
|
|
5481
|
-
*
|
|
5482
|
-
*
|
|
5575
|
+
* pipe(
|
|
5576
|
+
* validationTask,
|
|
5577
|
+
* Task.Validation.recoverUnless(
|
|
5578
|
+
* (errors) => errors.includes("fatal"),
|
|
5579
|
+
* (errors) => Task.Validation.make.passed("fallback")
|
|
5580
|
+
* )
|
|
5581
|
+
* );
|
|
5483
5582
|
* ```
|
|
5484
5583
|
*/
|
|
5485
|
-
|
|
5584
|
+
recoverUnless: (isBlocked, fallback) => (data) => Task.chain(
|
|
5585
|
+
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : isBlocked(validation.errors) ? Task.resolve(validation) : fallback(validation.errors)
|
|
5586
|
+
)(data),
|
|
5486
5587
|
/**
|
|
5487
|
-
* Runs
|
|
5488
|
-
*
|
|
5588
|
+
* Runs two Task.Validations concurrently and combines their results into a tuple.
|
|
5589
|
+
* If both are Passed, returns Passed with both values. If either fails, accumulates
|
|
5590
|
+
* errors from both sides.
|
|
5489
5591
|
*
|
|
5490
5592
|
* @example
|
|
5491
5593
|
* ```ts
|
|
5492
|
-
*
|
|
5493
|
-
*
|
|
5594
|
+
* await Task.Validation.product(
|
|
5595
|
+
* validateName(form.name),
|
|
5596
|
+
* validateAge(form.age),
|
|
5597
|
+
* )(); // Passed(["Alice", 30]) or Failed([...errors])
|
|
5598
|
+
* ```
|
|
5599
|
+
*/
|
|
5600
|
+
product: (first, second) => (signal) => Deferred.from.Promise(
|
|
5601
|
+
Promise.all([Deferred.to.Promise(first(signal)), Deferred.to.Promise(second(signal))]).then(
|
|
5602
|
+
([va, vb]) => Validation.product(va, vb)
|
|
5603
|
+
)
|
|
5604
|
+
),
|
|
5605
|
+
/**
|
|
5606
|
+
* Runs all Task.Validations concurrently and collects results.
|
|
5607
|
+
* If all are Passed, returns Passed with all values as an array.
|
|
5608
|
+
* If any fail, returns Failed with all accumulated errors.
|
|
5494
5609
|
*
|
|
5495
|
-
*
|
|
5496
|
-
*
|
|
5610
|
+
* @example
|
|
5611
|
+
* ```ts
|
|
5612
|
+
* await Task.Validation.productAll([
|
|
5613
|
+
* validateName(form.name),
|
|
5614
|
+
* validateEmail(form.email),
|
|
5615
|
+
* validateAge(form.age),
|
|
5616
|
+
* ])(); // Passed([name, email, age]) or Failed([...all errors])
|
|
5497
5617
|
* ```
|
|
5498
5618
|
*/
|
|
5499
|
-
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5505
|
-
results.push(await toPromise2(task, signal));
|
|
5506
|
-
}
|
|
5507
|
-
return results;
|
|
5508
|
-
}),
|
|
5619
|
+
productAll: (data) => (signal) => Deferred.from.Promise(
|
|
5620
|
+
Promise.all(data.map((t) => Deferred.to.Promise(t(signal)))).then((results) => {
|
|
5621
|
+
const [first, ...rest] = results;
|
|
5622
|
+
return Validation.productAll([first, ...rest]);
|
|
5623
|
+
})
|
|
5624
|
+
),
|
|
5509
5625
|
/**
|
|
5510
|
-
*
|
|
5511
|
-
* Task does not complete within the given duration. The inner Task receives an
|
|
5512
|
-
* `AbortSignal` that fires when the deadline passes, so asynchronous operations
|
|
5513
|
-
* that accept a signal are cancelled rather than left dangling.
|
|
5626
|
+
* Transforms all accumulated errors inside a Task.Validation.
|
|
5514
5627
|
*
|
|
5515
5628
|
* @example
|
|
5516
5629
|
* ```ts
|
|
5517
5630
|
* pipe(
|
|
5518
|
-
*
|
|
5519
|
-
* Task.
|
|
5520
|
-
*
|
|
5521
|
-
* );
|
|
5631
|
+
* Task.Validation.make.failed("oops"),
|
|
5632
|
+
* Task.Validation.mapError(e => e.toUpperCase())
|
|
5633
|
+
* ); // Task.Validation(Failed(["OOPS"]))
|
|
5522
5634
|
* ```
|
|
5523
5635
|
*/
|
|
5524
|
-
|
|
5525
|
-
const { duration, onTimeout } = options;
|
|
5526
|
-
const controller = new AbortController();
|
|
5527
|
-
let timerId;
|
|
5528
|
-
let cleanUp = () => {
|
|
5529
|
-
};
|
|
5530
|
-
const onOuterAbort = () => {
|
|
5531
|
-
cleanUp();
|
|
5532
|
-
controller.abort();
|
|
5533
|
-
};
|
|
5534
|
-
cleanUp = () => {
|
|
5535
|
-
clearTimeout(timerId);
|
|
5536
|
-
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
5537
|
-
};
|
|
5538
|
-
if (outerSignal) {
|
|
5539
|
-
if (outerSignal.aborted) {
|
|
5540
|
-
controller.abort();
|
|
5541
|
-
} else {
|
|
5542
|
-
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
5543
|
-
}
|
|
5544
|
-
}
|
|
5545
|
-
return Promise.race([
|
|
5546
|
-
toPromise2(task, controller.signal).then((a) => {
|
|
5547
|
-
cleanUp();
|
|
5548
|
-
return Result.make.ok(a);
|
|
5549
|
-
}),
|
|
5550
|
-
new Promise((res) => {
|
|
5551
|
-
timerId = setTimeout(() => {
|
|
5552
|
-
controller.abort();
|
|
5553
|
-
cleanUp();
|
|
5554
|
-
res(Result.make.err(onTimeout()));
|
|
5555
|
-
}, getMs2(duration));
|
|
5556
|
-
})
|
|
5557
|
-
]);
|
|
5558
|
-
}),
|
|
5636
|
+
mapError: (f) => (data) => Task.map(Validation.mapError(f))(data),
|
|
5559
5637
|
/**
|
|
5560
|
-
*
|
|
5561
|
-
* current in-flight call immediately. Unlike a one-shot abort, calling `task()`
|
|
5562
|
-
* again after `abort()` starts a fresh call with a new signal.
|
|
5563
|
-
*
|
|
5564
|
-
* Each invocation of `task()` automatically cancels the previous in-flight call,
|
|
5565
|
-
* making it safe to call repeatedly (e.g. on user input) without leaking promises.
|
|
5566
|
-
*
|
|
5567
|
-
* If an outer signal is also present (passed at the call site), aborting it
|
|
5568
|
-
* propagates into the internal controller.
|
|
5638
|
+
* Executes a side effect on the accumulated errors without changing the Task.Validation.
|
|
5569
5639
|
*
|
|
5570
5640
|
* @example
|
|
5571
5641
|
* ```ts
|
|
5572
|
-
*
|
|
5573
|
-
* (
|
|
5642
|
+
* pipe(
|
|
5643
|
+
* Task.Validation.make.failed("invalid name"),
|
|
5644
|
+
* Task.Validation.tapError(errs => logger.error(errs))
|
|
5574
5645
|
* );
|
|
5646
|
+
* ```
|
|
5647
|
+
*/
|
|
5648
|
+
tapError: (f) => (data) => Task.map(Validation.tapError(f))(data),
|
|
5649
|
+
/**
|
|
5650
|
+
* Combines a record of Task.Validations into a single Task.Validation of a record.
|
|
5651
|
+
* Evaluates fields in parallel and accumulates all validation errors.
|
|
5575
5652
|
*
|
|
5576
|
-
*
|
|
5577
|
-
*
|
|
5653
|
+
* @example
|
|
5654
|
+
* ```ts
|
|
5655
|
+
* Task.Validation.struct({
|
|
5656
|
+
* name: Task.Validation.make.passed("Alice"),
|
|
5657
|
+
* age: Task.Validation.make.passed(30)
|
|
5658
|
+
* }); // Task.Validation({ name: "Alice", age: 30 })
|
|
5578
5659
|
* ```
|
|
5579
5660
|
*/
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
const
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
if (
|
|
5589
|
-
|
|
5661
|
+
struct: (fields) => (signal) => Deferred.from.Promise((() => {
|
|
5662
|
+
const keys = Object.keys(fields);
|
|
5663
|
+
const promises = keys.map((key) => Deferred.to.Promise(fields[key](signal)));
|
|
5664
|
+
return Promise.all(promises).then((results) => {
|
|
5665
|
+
const record = {};
|
|
5666
|
+
const errors = [];
|
|
5667
|
+
for (let i = 0; i < keys.length; i++) {
|
|
5668
|
+
const res = results[i];
|
|
5669
|
+
if (Validation.is.passed(res)) {
|
|
5670
|
+
record[keys[i]] = res.value;
|
|
5590
5671
|
} else {
|
|
5591
|
-
|
|
5672
|
+
errors.push(...res.errors);
|
|
5592
5673
|
}
|
|
5593
5674
|
}
|
|
5594
|
-
return
|
|
5595
|
-
};
|
|
5596
|
-
|
|
5675
|
+
return isNonEmptyArr(errors) ? Validation.make.failedAll(errors) : Validation.make.passed(record);
|
|
5676
|
+
});
|
|
5677
|
+
})()),
|
|
5678
|
+
/**
|
|
5679
|
+
* Creates a memoized version of a Task.Validation. The task is executed at most once on first call,
|
|
5680
|
+
* and its resolved Validation is cached for all subsequent calls.
|
|
5681
|
+
*
|
|
5682
|
+
* @example
|
|
5683
|
+
* ```ts
|
|
5684
|
+
* const validate = Task.Validation.memoize(validateFormTask);
|
|
5685
|
+
* ```
|
|
5686
|
+
*/
|
|
5687
|
+
memoize: (task) => Task.memoize(task)
|
|
5688
|
+
};
|
|
5689
|
+
|
|
5690
|
+
// src/Core/Task.ts
|
|
5691
|
+
var toPromise2 = (task, signal) => Deferred.to.Promise(task(signal));
|
|
5692
|
+
var fromPromise2 = (f) => (signal) => Deferred.from.Promise(f(signal));
|
|
5693
|
+
var getMs2 = (duration) => Duration.to.milliseconds(duration);
|
|
5694
|
+
var resolveTask = (value) => () => Deferred.from.Promise(globalThis.Promise.resolve(value));
|
|
5695
|
+
var syncTask = (f) => () => Deferred.from.Promise(globalThis.Promise.resolve(f()));
|
|
5696
|
+
var Task = {
|
|
5697
|
+
/**
|
|
5698
|
+
* Creates a Task that immediately resolves to the given value.
|
|
5699
|
+
*
|
|
5700
|
+
* @example
|
|
5701
|
+
* ```ts
|
|
5702
|
+
* const task = Task.resolve(42);
|
|
5703
|
+
* const value = await task(); // 42
|
|
5704
|
+
* ```
|
|
5705
|
+
*/
|
|
5706
|
+
resolve: resolveTask,
|
|
5707
|
+
// --- from ---
|
|
5708
|
+
from: {
|
|
5709
|
+
/**
|
|
5710
|
+
* Creates a Task from a lazy synchronous thunk.
|
|
5711
|
+
* Unlike `Task.resolve(f())`, `from.sync` does not evaluate `f` until the Task is called.
|
|
5712
|
+
*
|
|
5713
|
+
* @example
|
|
5714
|
+
* ```ts
|
|
5715
|
+
* const t = Task.from.sync(() => Date.now()); // Date.now() not called yet
|
|
5716
|
+
* const ts = await t(); // called here, every time
|
|
5717
|
+
* ```
|
|
5718
|
+
*/
|
|
5719
|
+
sync: syncTask
|
|
5597
5720
|
},
|
|
5598
5721
|
/**
|
|
5599
|
-
*
|
|
5722
|
+
* Wraps a Promise-returning thunk that may throw or reject,
|
|
5723
|
+
* trapping errors with a fallback function and returning a guaranteed `Task<A>`.
|
|
5600
5724
|
*
|
|
5601
5725
|
* @example
|
|
5602
5726
|
* ```ts
|
|
5603
|
-
* const
|
|
5604
|
-
*
|
|
5605
|
-
*
|
|
5606
|
-
* Task.run(),
|
|
5727
|
+
* const loadConfig = Task.tryCatch(
|
|
5728
|
+
* () => configStore.get("default"),
|
|
5729
|
+
* { onError: () => DEFAULT_CONFIG }
|
|
5607
5730
|
* );
|
|
5608
5731
|
* ```
|
|
5609
5732
|
*/
|
|
5610
|
-
|
|
5733
|
+
tryCatch: (f, options) => fromPromise2((signal) => globalThis.Promise.resolve().then(() => f(signal)).catch((err2) => options.onError(err2))),
|
|
5611
5734
|
/**
|
|
5612
|
-
*
|
|
5613
|
-
* Initiates the pipeline accumulator record.
|
|
5735
|
+
* Transforms the value inside a Task.
|
|
5614
5736
|
*
|
|
5615
5737
|
* @example
|
|
5616
5738
|
* ```ts
|
|
5617
|
-
* pipe(
|
|
5739
|
+
* pipe(
|
|
5740
|
+
* Task.resolve(5),
|
|
5741
|
+
* Task.map(n => n * 2)
|
|
5742
|
+
* )(); // Deferred<10>
|
|
5618
5743
|
* ```
|
|
5619
5744
|
*/
|
|
5620
|
-
|
|
5745
|
+
map: (f) => (data) => fromPromise2((signal) => toPromise2(data, signal).then(f)),
|
|
5621
5746
|
/**
|
|
5622
|
-
*
|
|
5747
|
+
* Chains Task computations. Passes the resolved value of the first Task to f.
|
|
5623
5748
|
*
|
|
5624
5749
|
* @example
|
|
5625
5750
|
* ```ts
|
|
5751
|
+
* const readUserId: Task<string> = Task.resolve(session.userId);
|
|
5752
|
+
* const loadPrefs = (id: string): Task<Preferences> =>
|
|
5753
|
+
* Task.resolve(prefsCache.get(id));
|
|
5754
|
+
*
|
|
5626
5755
|
* pipe(
|
|
5627
|
-
*
|
|
5628
|
-
* Task.
|
|
5629
|
-
* ); //
|
|
5756
|
+
* readUserId,
|
|
5757
|
+
* Task.chain(loadPrefs)
|
|
5758
|
+
* )(); // Deferred<Preferences>
|
|
5759
|
+
* ```
|
|
5760
|
+
*/
|
|
5761
|
+
chain: (f) => (data) => fromPromise2((signal) => toPromise2(data, signal).then((a) => toPromise2(f(a), signal))),
|
|
5762
|
+
/**
|
|
5763
|
+
* Applies a function wrapped in a Task to a value wrapped in a Task.
|
|
5764
|
+
* Both Tasks run in parallel.
|
|
5765
|
+
*
|
|
5766
|
+
* @example
|
|
5767
|
+
* ```ts
|
|
5768
|
+
* const add = (a: number) => (b: number) => a + b;
|
|
5769
|
+
* pipe(
|
|
5770
|
+
* Task.resolve(add),
|
|
5771
|
+
* Task.ap(Task.resolve(5)),
|
|
5772
|
+
* Task.ap(Task.resolve(3))
|
|
5773
|
+
* )(); // Deferred<8>
|
|
5630
5774
|
* ```
|
|
5631
5775
|
*/
|
|
5632
|
-
|
|
5633
|
-
(signal) => toPromise2(data, signal).then(
|
|
5634
|
-
(a) => toPromise2(f(a), signal).then((b) => ({ ...a, [key]: b }))
|
|
5635
|
-
)
|
|
5636
|
-
),
|
|
5776
|
+
ap: (arg) => (data) => fromPromise2((signal) => Promise.all([toPromise2(data, signal), toPromise2(arg, signal)]).then(([f, a]) => f(a))),
|
|
5637
5777
|
/**
|
|
5638
|
-
*
|
|
5639
|
-
*
|
|
5778
|
+
* Executes a side effect on the value without changing the Task.
|
|
5779
|
+
* Useful for logging or debugging.
|
|
5640
5780
|
*
|
|
5641
5781
|
* @example
|
|
5642
5782
|
* ```ts
|
|
5643
|
-
*
|
|
5644
|
-
*
|
|
5645
|
-
*
|
|
5783
|
+
* pipe(
|
|
5784
|
+
* loadConfig,
|
|
5785
|
+
* Task.tap(cfg => console.log("Config:", cfg)),
|
|
5786
|
+
* Task.map(buildReport)
|
|
5787
|
+
* );
|
|
5646
5788
|
* ```
|
|
5647
5789
|
*/
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
return cached;
|
|
5655
|
-
};
|
|
5656
|
-
},
|
|
5790
|
+
tap: (f) => (data) => fromPromise2(
|
|
5791
|
+
(signal) => toPromise2(data, signal).then((a) => {
|
|
5792
|
+
f(a);
|
|
5793
|
+
return a;
|
|
5794
|
+
})
|
|
5795
|
+
),
|
|
5657
5796
|
/**
|
|
5658
|
-
*
|
|
5797
|
+
* Runs multiple Tasks in parallel and collects their results.
|
|
5659
5798
|
*
|
|
5660
5799
|
* @example
|
|
5661
5800
|
* ```ts
|
|
5662
|
-
*
|
|
5663
|
-
*
|
|
5664
|
-
* Task.withProgress((ratio) => console.log(`Progress: ${ratio * 100}%`))
|
|
5665
|
-
* );
|
|
5801
|
+
* Task.all([loadConfig, detectLocale, loadTheme])();
|
|
5802
|
+
* // Deferred<[Config, string, Theme]>
|
|
5666
5803
|
* ```
|
|
5667
5804
|
*/
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
return Deferred.from.Promise(
|
|
5672
|
-
Deferred.to.Promise(d).then((res) => {
|
|
5673
|
-
onProgress(1);
|
|
5674
|
-
return res;
|
|
5675
|
-
})
|
|
5676
|
-
);
|
|
5677
|
-
},
|
|
5805
|
+
all: (tasks) => fromPromise2(
|
|
5806
|
+
(signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))
|
|
5807
|
+
),
|
|
5678
5808
|
/**
|
|
5679
|
-
*
|
|
5809
|
+
* Delays the execution of a Task by the specified duration.
|
|
5810
|
+
* Useful for debouncing or rate limiting.
|
|
5680
5811
|
*
|
|
5681
5812
|
* @example
|
|
5682
5813
|
* ```ts
|
|
5683
|
-
*
|
|
5684
|
-
*
|
|
5814
|
+
* pipe(
|
|
5815
|
+
* Task.resolve(42),
|
|
5816
|
+
* Task.delay(Duration.seconds(1))
|
|
5817
|
+
* )(); // Resolves after 1 second
|
|
5685
5818
|
* ```
|
|
5686
5819
|
*/
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
var These = {
|
|
5707
|
-
make: {
|
|
5708
|
-
/**
|
|
5709
|
-
* Creates a These holding only a first value.
|
|
5710
|
-
*
|
|
5711
|
-
* @example
|
|
5712
|
-
* ```ts
|
|
5713
|
-
* These.make.first(42); // { kind: "First", first: 42 }
|
|
5714
|
-
* ```
|
|
5715
|
-
*/
|
|
5716
|
-
first: makeFirst,
|
|
5717
|
-
/**
|
|
5718
|
-
* Creates a These holding only a second value.
|
|
5719
|
-
*
|
|
5720
|
-
* @example
|
|
5721
|
-
* ```ts
|
|
5722
|
-
* These.make.second("warning"); // { kind: "Second", second: "warning" }
|
|
5723
|
-
* ```
|
|
5724
|
-
*/
|
|
5725
|
-
second: makeSecond,
|
|
5726
|
-
/**
|
|
5727
|
-
* Creates a These holding both a first and a second value simultaneously.
|
|
5728
|
-
*
|
|
5729
|
-
* @example
|
|
5730
|
-
* ```ts
|
|
5731
|
-
* These.make.both(42, "Deprecated API used"); // { kind: "Both", first: 42, second: "Deprecated API used" }
|
|
5732
|
-
* ```
|
|
5733
|
-
*/
|
|
5734
|
-
both: makeBoth
|
|
5735
|
-
},
|
|
5736
|
-
is: {
|
|
5737
|
-
/**
|
|
5738
|
-
* Type guard — checks if a These holds only a first value.
|
|
5739
|
-
*
|
|
5740
|
-
* @example
|
|
5741
|
-
* ```ts
|
|
5742
|
-
* const val = These.make.first(42);
|
|
5743
|
-
* if (These.is.first(val)) {
|
|
5744
|
-
* console.log(val.first); // 42
|
|
5745
|
-
* }
|
|
5746
|
-
* ```
|
|
5747
|
-
*/
|
|
5748
|
-
first: isFirst,
|
|
5749
|
-
/**
|
|
5750
|
-
* Type guard — checks if a These holds only a second value.
|
|
5751
|
-
*
|
|
5752
|
-
* @example
|
|
5753
|
-
* ```ts
|
|
5754
|
-
* const val = These.make.second("warning");
|
|
5755
|
-
* if (These.is.second(val)) {
|
|
5756
|
-
* console.log(val.second); // "warning"
|
|
5757
|
-
* }
|
|
5758
|
-
* ```
|
|
5759
|
-
*/
|
|
5760
|
-
second: isSecond,
|
|
5761
|
-
/**
|
|
5762
|
-
* Type guard — checks if a These holds both values simultaneously.
|
|
5763
|
-
*
|
|
5764
|
-
* @example
|
|
5765
|
-
* ```ts
|
|
5766
|
-
* const val = These.make.both(42, "warning");
|
|
5767
|
-
* if (These.is.both(val)) {
|
|
5768
|
-
* console.log(val.first, val.second); // 42 "warning"
|
|
5769
|
-
* }
|
|
5770
|
-
* ```
|
|
5771
|
-
*/
|
|
5772
|
-
both: isBoth
|
|
5773
|
-
},
|
|
5820
|
+
delay: (duration) => (data) => fromPromise2(
|
|
5821
|
+
(signal) => new Promise((res) => {
|
|
5822
|
+
let timerId;
|
|
5823
|
+
const onAbort = () => {
|
|
5824
|
+
clearTimeout(timerId);
|
|
5825
|
+
res(toPromise2(data, signal));
|
|
5826
|
+
};
|
|
5827
|
+
if (signal) {
|
|
5828
|
+
if (signal.aborted) {
|
|
5829
|
+
return res(toPromise2(data, signal));
|
|
5830
|
+
}
|
|
5831
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5832
|
+
}
|
|
5833
|
+
timerId = setTimeout(() => {
|
|
5834
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5835
|
+
res(toPromise2(data, signal));
|
|
5836
|
+
}, getMs2(duration));
|
|
5837
|
+
})
|
|
5838
|
+
),
|
|
5774
5839
|
/**
|
|
5775
|
-
*
|
|
5840
|
+
* Runs a Task a fixed number of times sequentially, collecting all results into an array.
|
|
5841
|
+
* An optional delay duration can be inserted between runs.
|
|
5776
5842
|
*
|
|
5777
5843
|
* @example
|
|
5778
5844
|
* ```ts
|
|
5779
|
-
*
|
|
5780
|
-
*
|
|
5781
|
-
*
|
|
5845
|
+
* pipe(
|
|
5846
|
+
* pollSensor,
|
|
5847
|
+
* Task.repeat({ times: 5, delay: Duration.seconds(1) })
|
|
5848
|
+
* )(); // Task<Reading[]> — 5 readings, one per second
|
|
5782
5849
|
* ```
|
|
5783
5850
|
*/
|
|
5784
|
-
|
|
5851
|
+
repeat: (options) => (task) => fromPromise2((signal) => {
|
|
5852
|
+
const { times, delay: delayDuration } = options;
|
|
5853
|
+
if (times <= 0) {
|
|
5854
|
+
return Promise.resolve([]);
|
|
5855
|
+
}
|
|
5856
|
+
const results = [];
|
|
5857
|
+
const wait = () => new Promise((r) => {
|
|
5858
|
+
let timerId;
|
|
5859
|
+
const onAbort = () => {
|
|
5860
|
+
clearTimeout(timerId);
|
|
5861
|
+
r();
|
|
5862
|
+
};
|
|
5863
|
+
if (signal) {
|
|
5864
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5865
|
+
}
|
|
5866
|
+
timerId = setTimeout(() => {
|
|
5867
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5868
|
+
r();
|
|
5869
|
+
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
5870
|
+
});
|
|
5871
|
+
const run = (left) => {
|
|
5872
|
+
if (signal?.aborted) {
|
|
5873
|
+
return Promise.resolve(results);
|
|
5874
|
+
}
|
|
5875
|
+
return toPromise2(task, signal).then((a) => {
|
|
5876
|
+
results.push(a);
|
|
5877
|
+
if (left <= 1 || signal?.aborted) {
|
|
5878
|
+
return results;
|
|
5879
|
+
}
|
|
5880
|
+
return wait().then(() => run(left - 1));
|
|
5881
|
+
});
|
|
5882
|
+
};
|
|
5883
|
+
return run(times);
|
|
5884
|
+
}),
|
|
5785
5885
|
/**
|
|
5786
|
-
*
|
|
5886
|
+
* Runs a Task repeatedly until the result satisfies a predicate, returning that result.
|
|
5887
|
+
* An optional delay duration can be inserted between runs.
|
|
5888
|
+
* An optional `maxAttempts` cap stops the loop after N calls — the last value is returned
|
|
5889
|
+
* regardless of whether the predicate was satisfied.
|
|
5787
5890
|
*
|
|
5788
5891
|
* @example
|
|
5789
5892
|
* ```ts
|
|
5790
|
-
*
|
|
5791
|
-
*
|
|
5792
|
-
*
|
|
5893
|
+
* pipe(
|
|
5894
|
+
* checkStatus,
|
|
5895
|
+
* Task.repeatUntil({ when: (s) => s === "ready", delay: Duration.milliseconds(500) })
|
|
5896
|
+
* )(); // polls every 500ms until status is "ready"
|
|
5793
5897
|
* ```
|
|
5794
5898
|
*/
|
|
5795
|
-
|
|
5899
|
+
repeatUntil: (options) => (task) => fromPromise2((signal) => {
|
|
5900
|
+
const { when: predicate, delay: delayDuration, maxAttempts } = options;
|
|
5901
|
+
const wait = () => new Promise((r) => {
|
|
5902
|
+
let timerId;
|
|
5903
|
+
const onAbort = () => {
|
|
5904
|
+
clearTimeout(timerId);
|
|
5905
|
+
r();
|
|
5906
|
+
};
|
|
5907
|
+
if (signal) {
|
|
5908
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
5909
|
+
}
|
|
5910
|
+
timerId = setTimeout(() => {
|
|
5911
|
+
signal?.removeEventListener("abort", onAbort);
|
|
5912
|
+
r();
|
|
5913
|
+
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
5914
|
+
});
|
|
5915
|
+
const run = (attempt, lastValue) => {
|
|
5916
|
+
if (signal?.aborted && lastValue !== void 0) {
|
|
5917
|
+
return Promise.resolve(lastValue);
|
|
5918
|
+
}
|
|
5919
|
+
return toPromise2(task, signal).then((a) => {
|
|
5920
|
+
if (predicate(a)) {
|
|
5921
|
+
return a;
|
|
5922
|
+
}
|
|
5923
|
+
if (maxAttempts !== void 0 && attempt >= maxAttempts) {
|
|
5924
|
+
return a;
|
|
5925
|
+
}
|
|
5926
|
+
if (signal?.aborted) {
|
|
5927
|
+
return a;
|
|
5928
|
+
}
|
|
5929
|
+
return wait().then(() => run(attempt + 1, a));
|
|
5930
|
+
});
|
|
5931
|
+
};
|
|
5932
|
+
return run(1);
|
|
5933
|
+
}),
|
|
5796
5934
|
/**
|
|
5797
|
-
*
|
|
5935
|
+
* Resolves with the value of the first Task to complete. All Tasks start
|
|
5936
|
+
* immediately. When one resolves, the other tasks are cancelled (aborted)
|
|
5937
|
+
* downstream.
|
|
5798
5938
|
*
|
|
5799
5939
|
* @example
|
|
5800
5940
|
* ```ts
|
|
5801
|
-
*
|
|
5802
|
-
*
|
|
5803
|
-
*
|
|
5941
|
+
* const fast = Task.resolve("fast");
|
|
5942
|
+
* const slow = Task.delay(Duration.milliseconds(200))(Task.resolve("slow"));
|
|
5943
|
+
*
|
|
5944
|
+
* await Task.race([fast, slow])(); // "fast"
|
|
5804
5945
|
* ```
|
|
5805
5946
|
*/
|
|
5806
|
-
|
|
5807
|
-
if (
|
|
5808
|
-
return
|
|
5809
|
-
|
|
5810
|
-
if (isFirst(data)) {
|
|
5811
|
-
return makeFirst(f(data.first));
|
|
5947
|
+
race: (tasks) => {
|
|
5948
|
+
if (tasks.length === 0) {
|
|
5949
|
+
return () => Deferred.from.Promise(new Promise(() => {
|
|
5950
|
+
}));
|
|
5812
5951
|
}
|
|
5813
|
-
return
|
|
5952
|
+
return fromPromise2((outerSignal) => {
|
|
5953
|
+
const controllers = tasks.map(() => new AbortController());
|
|
5954
|
+
const onOuterAbort = () => {
|
|
5955
|
+
for (const ctrl of controllers) {
|
|
5956
|
+
ctrl.abort();
|
|
5957
|
+
}
|
|
5958
|
+
};
|
|
5959
|
+
if (outerSignal) {
|
|
5960
|
+
if (outerSignal.aborted) {
|
|
5961
|
+
onOuterAbort();
|
|
5962
|
+
} else {
|
|
5963
|
+
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
const promises = tasks.map((task, idx) => {
|
|
5967
|
+
const ctrl = controllers[idx];
|
|
5968
|
+
return toPromise2(task, ctrl.signal).then((result) => {
|
|
5969
|
+
for (let i = 0; i < controllers.length; i++) {
|
|
5970
|
+
if (i !== idx) {
|
|
5971
|
+
controllers[i].abort();
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
5975
|
+
return result;
|
|
5976
|
+
});
|
|
5977
|
+
});
|
|
5978
|
+
return Promise.race(promises);
|
|
5979
|
+
});
|
|
5814
5980
|
},
|
|
5815
5981
|
/**
|
|
5816
|
-
*
|
|
5982
|
+
* Runs an array of Tasks concurrently and collects their results in an array.
|
|
5983
|
+
* Forward-propagates the call site's AbortSignal to all subtasks concurrently.
|
|
5817
5984
|
*
|
|
5818
5985
|
* @example
|
|
5819
5986
|
* ```ts
|
|
5820
|
-
*
|
|
5821
|
-
*
|
|
5987
|
+
* Task.sequence([loadConfig, detectLocale, loadTheme])();
|
|
5988
|
+
* // Deferred<[Config, string, Theme]>
|
|
5822
5989
|
* ```
|
|
5823
5990
|
*/
|
|
5824
|
-
|
|
5825
|
-
if (isFirst(data)) {
|
|
5826
|
-
return data;
|
|
5827
|
-
}
|
|
5828
|
-
if (isSecond(data)) {
|
|
5829
|
-
return makeSecond(f(data.second));
|
|
5830
|
-
}
|
|
5831
|
-
return makeBoth(data.first, f(data.second));
|
|
5832
|
-
},
|
|
5991
|
+
sequence: (tasks) => fromPromise2((signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))),
|
|
5833
5992
|
/**
|
|
5834
|
-
*
|
|
5993
|
+
* Runs an array of Tasks one at a time in order, collecting all results.
|
|
5994
|
+
* Each Task starts only after the previous one resolves.
|
|
5835
5995
|
*
|
|
5836
5996
|
* @example
|
|
5837
5997
|
* ```ts
|
|
5838
|
-
*
|
|
5839
|
-
*
|
|
5840
|
-
*
|
|
5841
|
-
* )
|
|
5998
|
+
* let log: number[] = [];
|
|
5999
|
+
* const makeTask = (n: number) => Task.resolve(n);
|
|
6000
|
+
*
|
|
6001
|
+
* await Task.sequential([makeTask(1), makeTask(2), makeTask(3)])();
|
|
6002
|
+
* // log = [1, 2, 3] — tasks ran in order
|
|
5842
6003
|
* ```
|
|
5843
6004
|
*/
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
|
|
5849
|
-
|
|
6005
|
+
sequential: (tasks) => fromPromise2(async (signal) => {
|
|
6006
|
+
const results = [];
|
|
6007
|
+
for (const task of tasks) {
|
|
6008
|
+
if (signal?.aborted) {
|
|
6009
|
+
break;
|
|
6010
|
+
}
|
|
6011
|
+
results.push(await toPromise2(task, signal));
|
|
5850
6012
|
}
|
|
5851
|
-
return
|
|
5852
|
-
},
|
|
6013
|
+
return results;
|
|
6014
|
+
}),
|
|
5853
6015
|
/**
|
|
5854
|
-
*
|
|
5855
|
-
*
|
|
6016
|
+
* Converts a `Task<A>` into a `Task<Result<E, A>>`, resolving to `Err` if the
|
|
6017
|
+
* Task does not complete within the given duration. The inner Task receives an
|
|
6018
|
+
* `AbortSignal` that fires when the deadline passes, so asynchronous operations
|
|
6019
|
+
* that accept a signal are cancelled rather than left dangling.
|
|
5856
6020
|
*
|
|
5857
6021
|
* @example
|
|
5858
6022
|
* ```ts
|
|
5859
|
-
*
|
|
5860
|
-
*
|
|
5861
|
-
*
|
|
5862
|
-
*
|
|
5863
|
-
*
|
|
6023
|
+
* pipe(
|
|
6024
|
+
* heavyComputation,
|
|
6025
|
+
* Task.timeout({ duration: Duration.seconds(5), onTimeout: () => "timed out" }),
|
|
6026
|
+
* Task.Result.chain(processResult)
|
|
6027
|
+
* );
|
|
5864
6028
|
* ```
|
|
5865
6029
|
*/
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
6030
|
+
timeout: (options) => (task) => fromPromise2((outerSignal) => {
|
|
6031
|
+
const { duration, onTimeout } = options;
|
|
6032
|
+
const controller = new AbortController();
|
|
6033
|
+
let timerId;
|
|
6034
|
+
let cleanUp = () => {
|
|
6035
|
+
};
|
|
6036
|
+
const onOuterAbort = () => {
|
|
6037
|
+
cleanUp();
|
|
6038
|
+
controller.abort();
|
|
6039
|
+
};
|
|
6040
|
+
cleanUp = () => {
|
|
6041
|
+
clearTimeout(timerId);
|
|
6042
|
+
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
6043
|
+
};
|
|
6044
|
+
if (outerSignal) {
|
|
6045
|
+
if (outerSignal.aborted) {
|
|
6046
|
+
controller.abort();
|
|
6047
|
+
} else {
|
|
6048
|
+
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
6049
|
+
}
|
|
5869
6050
|
}
|
|
5870
|
-
return
|
|
5871
|
-
|
|
6051
|
+
return Promise.race([
|
|
6052
|
+
toPromise2(task, controller.signal).then((a) => {
|
|
6053
|
+
cleanUp();
|
|
6054
|
+
return Result.make.ok(a);
|
|
6055
|
+
}),
|
|
6056
|
+
new Promise((res) => {
|
|
6057
|
+
timerId = setTimeout(() => {
|
|
6058
|
+
controller.abort();
|
|
6059
|
+
cleanUp();
|
|
6060
|
+
res(Result.make.err(onTimeout()));
|
|
6061
|
+
}, getMs2(duration));
|
|
6062
|
+
})
|
|
6063
|
+
]);
|
|
6064
|
+
}),
|
|
5872
6065
|
/**
|
|
5873
|
-
*
|
|
5874
|
-
*
|
|
6066
|
+
* Creates a Task paired with an `abort` handle. Calling `abort()` cancels the
|
|
6067
|
+
* current in-flight call immediately. Unlike a one-shot abort, calling `task()`
|
|
6068
|
+
* again after `abort()` starts a fresh call with a new signal.
|
|
6069
|
+
*
|
|
6070
|
+
* Each invocation of `task()` automatically cancels the previous in-flight call,
|
|
6071
|
+
* making it safe to call repeatedly (e.g. on user input) without leaking promises.
|
|
6072
|
+
*
|
|
6073
|
+
* If an outer signal is also present (passed at the call site), aborting it
|
|
6074
|
+
* propagates into the internal controller.
|
|
5875
6075
|
*
|
|
5876
6076
|
* @example
|
|
5877
6077
|
* ```ts
|
|
5878
|
-
* const
|
|
6078
|
+
* const { task: poll, abort } = Task.abortable(
|
|
6079
|
+
* (signal) => waitForEvent(bus, "ready", { signal }),
|
|
6080
|
+
* );
|
|
5879
6081
|
*
|
|
5880
|
-
*
|
|
5881
|
-
*
|
|
5882
|
-
* pipe(These.make.first(5), These.chainSecond(shout)); // First(5)
|
|
6082
|
+
* onUnmount(abort);
|
|
6083
|
+
* await poll();
|
|
5883
6084
|
* ```
|
|
5884
6085
|
*/
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
6086
|
+
abortable: (factory) => {
|
|
6087
|
+
let currentController = null;
|
|
6088
|
+
const abort = () => currentController?.abort();
|
|
6089
|
+
const task = (outerSignal) => {
|
|
6090
|
+
currentController?.abort();
|
|
6091
|
+
currentController = new AbortController();
|
|
6092
|
+
const controller = currentController;
|
|
6093
|
+
if (outerSignal) {
|
|
6094
|
+
if (outerSignal.aborted) {
|
|
6095
|
+
controller.abort(outerSignal.reason);
|
|
6096
|
+
} else {
|
|
6097
|
+
outerSignal.addEventListener("abort", () => controller.abort(outerSignal.reason), { once: true });
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
return Deferred.from.Promise(factory(controller.signal));
|
|
6101
|
+
};
|
|
6102
|
+
return { task, abort };
|
|
5890
6103
|
},
|
|
5891
6104
|
/**
|
|
5892
|
-
*
|
|
6105
|
+
* Executes a task with an optional signal. Use as a terminal step in a `pipe` chain.
|
|
5893
6106
|
*
|
|
5894
6107
|
* @example
|
|
5895
6108
|
* ```ts
|
|
5896
|
-
* pipe(
|
|
5897
|
-
*
|
|
5898
|
-
*
|
|
5899
|
-
*
|
|
5900
|
-
* b => `Second: ${b}`,
|
|
5901
|
-
* (a, b) => `Both: ${a} / ${b}`
|
|
5902
|
-
* )
|
|
6109
|
+
* const name = await pipe(
|
|
6110
|
+
* loadConfig,
|
|
6111
|
+
* Task.map(config => config.name),
|
|
6112
|
+
* Task.run(),
|
|
5903
6113
|
* );
|
|
5904
6114
|
* ```
|
|
5905
6115
|
*/
|
|
5906
|
-
|
|
5907
|
-
if (isSecond(data)) {
|
|
5908
|
-
return onSecond(data.second);
|
|
5909
|
-
}
|
|
5910
|
-
if (isFirst(data)) {
|
|
5911
|
-
return onFirst(data.first);
|
|
5912
|
-
}
|
|
5913
|
-
return onBoth(data.first, data.second);
|
|
5914
|
-
},
|
|
6116
|
+
run: (signal) => (task) => task(signal),
|
|
5915
6117
|
/**
|
|
5916
|
-
*
|
|
6118
|
+
* Converts a Task value into an object containing a single property.
|
|
6119
|
+
* Initiates the pipeline accumulator record.
|
|
5917
6120
|
*
|
|
5918
6121
|
* @example
|
|
5919
6122
|
* ```ts
|
|
5920
|
-
* pipe(
|
|
5921
|
-
* these,
|
|
5922
|
-
* These.match({
|
|
5923
|
-
* first: a => `First: ${a}`,
|
|
5924
|
-
* second: b => `Second: ${b}`,
|
|
5925
|
-
* both: (a, b) => `Both: ${a} / ${b}`
|
|
5926
|
-
* })
|
|
5927
|
-
* );
|
|
6123
|
+
* pipe(Task.resolve(42), Task.bindTo("value")); // Task({ value: 42 })
|
|
5928
6124
|
* ```
|
|
5929
6125
|
*/
|
|
5930
|
-
|
|
5931
|
-
if (isSecond(data)) {
|
|
5932
|
-
return cases.second(data.second);
|
|
5933
|
-
}
|
|
5934
|
-
if (isFirst(data)) {
|
|
5935
|
-
return cases.first(data.first);
|
|
5936
|
-
}
|
|
5937
|
-
return cases.both(data.first, data.second);
|
|
5938
|
-
},
|
|
6126
|
+
bindTo: (key) => (data) => fromPromise2((signal) => toPromise2(data, signal).then((a) => ({ [key]: a }))),
|
|
5939
6127
|
/**
|
|
5940
|
-
*
|
|
5941
|
-
*
|
|
5942
|
-
*
|
|
5943
|
-
*
|
|
5944
|
-
*
|
|
5945
|
-
*
|
|
5946
|
-
*
|
|
5947
|
-
*
|
|
5948
|
-
* pipe(These.make.second("warn"), These.getFirstOrElse(() => null)); // null — typed as number | null
|
|
6128
|
+
* Evaluates a new Task using the current accumulator and attaches the output to a new key.
|
|
6129
|
+
*
|
|
6130
|
+
* @example
|
|
6131
|
+
* ```ts
|
|
6132
|
+
* pipe(
|
|
6133
|
+
* Task.resolve({ a: 1 }),
|
|
6134
|
+
* Task.bind("b", ({ a }) => Task.resolve(a + 1))
|
|
6135
|
+
* ); // Task({ a: 1, b: 2 })
|
|
5949
6136
|
* ```
|
|
5950
6137
|
*/
|
|
5951
|
-
|
|
6138
|
+
bind: (key, f) => (data) => fromPromise2(
|
|
6139
|
+
(signal) => toPromise2(data, signal).then(
|
|
6140
|
+
(a) => toPromise2(f(a), signal).then((b) => ({ ...a, [key]: b }))
|
|
6141
|
+
)
|
|
6142
|
+
),
|
|
5952
6143
|
/**
|
|
5953
|
-
*
|
|
5954
|
-
*
|
|
6144
|
+
* Creates a memoized version of a Task. The task is executed at most once on first call,
|
|
6145
|
+
* and its resolved value is cached for all subsequent calls.
|
|
5955
6146
|
*
|
|
5956
6147
|
* @example
|
|
5957
6148
|
* ```ts
|
|
5958
|
-
*
|
|
5959
|
-
*
|
|
5960
|
-
*
|
|
5961
|
-
* pipe(These.make.first(5), These.getSecondOrElse(() => null)); // null — typed as string | null
|
|
6149
|
+
* const loadToken = Task.memoize(loadAuthToken);
|
|
6150
|
+
* const token1 = await loadToken(); // loads token
|
|
6151
|
+
* const token2 = await loadToken(); // returns cached token immediately
|
|
5962
6152
|
* ```
|
|
5963
6153
|
*/
|
|
5964
|
-
|
|
6154
|
+
memoize: (task) => {
|
|
6155
|
+
let cached = null;
|
|
6156
|
+
return (signal) => {
|
|
6157
|
+
if (cached === null) {
|
|
6158
|
+
cached = task(signal);
|
|
6159
|
+
}
|
|
6160
|
+
return cached;
|
|
6161
|
+
};
|
|
6162
|
+
},
|
|
5965
6163
|
/**
|
|
5966
|
-
*
|
|
5967
|
-
* Useful for logging or debugging.
|
|
6164
|
+
* Monitors progress of a Task by calling `onProgress(0)` before execution and `onProgress(1)` upon completion.
|
|
5968
6165
|
*
|
|
5969
6166
|
* @example
|
|
5970
6167
|
* ```ts
|
|
5971
|
-
*
|
|
6168
|
+
* const taskWithProgress = pipe(
|
|
6169
|
+
* readTask,
|
|
6170
|
+
* Task.withProgress((ratio) => console.log(`Progress: ${ratio * 100}%`))
|
|
6171
|
+
* );
|
|
5972
6172
|
* ```
|
|
5973
6173
|
*/
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
6174
|
+
withProgress: (onProgress) => (task) => (signal) => {
|
|
6175
|
+
onProgress(0);
|
|
6176
|
+
const d = task(signal);
|
|
6177
|
+
return Deferred.from.Promise(
|
|
6178
|
+
Deferred.to.Promise(d).then((res) => {
|
|
6179
|
+
onProgress(1);
|
|
6180
|
+
return res;
|
|
6181
|
+
})
|
|
6182
|
+
);
|
|
5979
6183
|
},
|
|
5980
6184
|
/**
|
|
5981
|
-
*
|
|
5982
|
-
* - First(a) → Second(a)
|
|
5983
|
-
* - Second(b) → First(b)
|
|
5984
|
-
* - Both(a, b) → Both(b, a)
|
|
6185
|
+
* Attaches a read-only `.label` property to a Task, preserving the literal string generic type for IDE tooltips.
|
|
5985
6186
|
*
|
|
5986
6187
|
* @example
|
|
5987
6188
|
* ```ts
|
|
5988
|
-
*
|
|
5989
|
-
*
|
|
5990
|
-
* These.swap(These.make.both(5, "warn")); // Both("warn", 5)
|
|
6189
|
+
* const labeledTask = pipe(readTask, Task.withLabel("readUser"));
|
|
6190
|
+
* console.log(labeledTask.label); // "readUser"
|
|
5991
6191
|
* ```
|
|
5992
6192
|
*/
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
}
|
|
6193
|
+
withLabel: (label) => (task) => {
|
|
6194
|
+
const fn = ((signal) => task(signal));
|
|
6195
|
+
Object.defineProperty(fn, "label", { value: label, writable: false, enumerable: true, configurable: true });
|
|
6196
|
+
return fn;
|
|
6197
|
+
},
|
|
6198
|
+
Maybe: TaskMaybe,
|
|
6199
|
+
Result: TaskResult,
|
|
6200
|
+
Validation: TaskValidation
|
|
6002
6201
|
};
|
|
6003
6202
|
|
|
6004
|
-
// src/Core/
|
|
6005
|
-
var
|
|
6006
|
-
var
|
|
6007
|
-
var
|
|
6008
|
-
var
|
|
6009
|
-
var
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
}
|
|
6015
|
-
return isPassed(arg) ? Result.make.ok(arg.value) : Result.make.err(arg.errors);
|
|
6016
|
-
}
|
|
6017
|
-
var Validation = {
|
|
6203
|
+
// src/Core/These.ts
|
|
6204
|
+
var makeFirst = (value) => ({ kind: "First", first: value });
|
|
6205
|
+
var makeSecond = (value) => ({ kind: "Second", second: value });
|
|
6206
|
+
var makeBoth = (f, s) => ({ kind: "Both", first: f, second: s });
|
|
6207
|
+
var isFirst = (data) => data.kind === "First";
|
|
6208
|
+
var isSecond = (data) => data.kind === "Second";
|
|
6209
|
+
var isBoth = (data) => data.kind === "Both";
|
|
6210
|
+
var hasFirst = (data) => data.kind === "First" || data.kind === "Both";
|
|
6211
|
+
var hasSecond = (data) => data.kind === "Second" || data.kind === "Both";
|
|
6212
|
+
var These = {
|
|
6018
6213
|
make: {
|
|
6019
6214
|
/**
|
|
6020
|
-
*
|
|
6215
|
+
* Creates a These holding only a first value.
|
|
6021
6216
|
*
|
|
6022
6217
|
* @example
|
|
6023
6218
|
* ```ts
|
|
6024
|
-
*
|
|
6219
|
+
* These.make.first(42); // { kind: "First", first: 42 }
|
|
6025
6220
|
* ```
|
|
6026
6221
|
*/
|
|
6027
|
-
|
|
6222
|
+
first: makeFirst,
|
|
6028
6223
|
/**
|
|
6029
|
-
* Creates a
|
|
6224
|
+
* Creates a These holding only a second value.
|
|
6030
6225
|
*
|
|
6031
6226
|
* @example
|
|
6032
6227
|
* ```ts
|
|
6033
|
-
*
|
|
6228
|
+
* These.make.second("warning"); // { kind: "Second", second: "warning" }
|
|
6034
6229
|
* ```
|
|
6035
6230
|
*/
|
|
6036
|
-
|
|
6231
|
+
second: makeSecond,
|
|
6037
6232
|
/**
|
|
6038
|
-
* Creates a
|
|
6233
|
+
* Creates a These holding both a first and a second value simultaneously.
|
|
6039
6234
|
*
|
|
6040
6235
|
* @example
|
|
6041
6236
|
* ```ts
|
|
6042
|
-
*
|
|
6237
|
+
* These.make.both(42, "Deprecated API used"); // { kind: "Both", first: 42, second: "Deprecated API used" }
|
|
6043
6238
|
* ```
|
|
6044
6239
|
*/
|
|
6045
|
-
|
|
6240
|
+
both: makeBoth
|
|
6046
6241
|
},
|
|
6047
6242
|
is: {
|
|
6048
6243
|
/**
|
|
6049
|
-
* Type guard
|
|
6244
|
+
* Type guard — checks if a These holds only a first value.
|
|
6050
6245
|
*
|
|
6051
6246
|
* @example
|
|
6052
6247
|
* ```ts
|
|
6053
|
-
* const
|
|
6054
|
-
* if (
|
|
6055
|
-
* console.log(
|
|
6248
|
+
* const val = These.make.first(42);
|
|
6249
|
+
* if (These.is.first(val)) {
|
|
6250
|
+
* console.log(val.first); // 42
|
|
6056
6251
|
* }
|
|
6057
6252
|
* ```
|
|
6058
6253
|
*/
|
|
6059
|
-
|
|
6254
|
+
first: isFirst,
|
|
6060
6255
|
/**
|
|
6061
|
-
* Type guard
|
|
6256
|
+
* Type guard — checks if a These holds only a second value.
|
|
6062
6257
|
*
|
|
6063
6258
|
* @example
|
|
6064
6259
|
* ```ts
|
|
6065
|
-
* const
|
|
6066
|
-
* if (
|
|
6067
|
-
* console.log(
|
|
6260
|
+
* const val = These.make.second("warning");
|
|
6261
|
+
* if (These.is.second(val)) {
|
|
6262
|
+
* console.log(val.second); // "warning"
|
|
6068
6263
|
* }
|
|
6069
6264
|
* ```
|
|
6070
6265
|
*/
|
|
6071
|
-
|
|
6072
|
-
},
|
|
6073
|
-
/**
|
|
6074
|
-
* Creates a Validation from a synchronous thunk that may throw.
|
|
6075
|
-
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
6076
|
-
*
|
|
6077
|
-
* @example
|
|
6078
|
-
* ```ts
|
|
6079
|
-
* const result = Validation.tryCatch(
|
|
6080
|
-
* () => JSON.parse(rawString),
|
|
6081
|
-
* { onError: (e) => `Parse error: ${e}` }
|
|
6082
|
-
* );
|
|
6083
|
-
* ```
|
|
6084
|
-
*/
|
|
6085
|
-
tryCatch: (f, options) => {
|
|
6086
|
-
try {
|
|
6087
|
-
return makePassed2(f());
|
|
6088
|
-
} catch (error) {
|
|
6089
|
-
return makeFailed2(options.onError(error));
|
|
6090
|
-
}
|
|
6091
|
-
},
|
|
6092
|
-
// --- from ---
|
|
6093
|
-
from: {
|
|
6094
|
-
/**
|
|
6095
|
-
* Creates a Validation from a predicate applied to a value.
|
|
6096
|
-
* Returns Passed if the predicate passes, Failed from `onFalse` otherwise.
|
|
6097
|
-
*
|
|
6098
|
-
* @example
|
|
6099
|
-
* ```ts
|
|
6100
|
-
* const validateName = Validation.from.Predicate(
|
|
6101
|
-
* (s: string) => s.length > 0,
|
|
6102
|
-
* () => "Name is required"
|
|
6103
|
-
* );
|
|
6104
|
-
*
|
|
6105
|
-
* validateName("Alice"); // Passed("Alice")
|
|
6106
|
-
* validateName(""); // Failed(["Name is required"])
|
|
6107
|
-
* ```
|
|
6108
|
-
*/
|
|
6109
|
-
Predicate: (pred, onFalse) => (a) => pred(a) ? makePassed2(a) : makeFailed2(onFalse(a)),
|
|
6110
|
-
/**
|
|
6111
|
-
* Creates a Validation from a nullable value.
|
|
6112
|
-
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
6113
|
-
* Otherwise, returns Passed.
|
|
6114
|
-
*
|
|
6115
|
-
* @example
|
|
6116
|
-
* ```ts
|
|
6117
|
-
* pipe(null, Validation.from.nullable(() => "is null")); // Failed(["is null"])
|
|
6118
|
-
* pipe(42, Validation.from.nullable(() => "is null")); // Passed(42)
|
|
6119
|
-
* ```
|
|
6120
|
-
*/
|
|
6121
|
-
nullable: (onNull) => (value) => value === null || value === void 0 ? makeFailed2(onNull()) : makePassed2(value),
|
|
6122
|
-
/**
|
|
6123
|
-
* Creates a Validation from a Maybe.
|
|
6124
|
-
* If the Maybe is None, returns Failed with the error from onNone.
|
|
6125
|
-
* Otherwise, returns Passed.
|
|
6126
|
-
*
|
|
6127
|
-
* @example
|
|
6128
|
-
* ```ts
|
|
6129
|
-
* pipe(Maybe.make.none(), Validation.from.Maybe(() => "is none")); // Failed(["is none"])
|
|
6130
|
-
* pipe(Maybe.make.some(42), Validation.from.Maybe(() => "is none")); // Passed(42)
|
|
6131
|
-
* ```
|
|
6132
|
-
*/
|
|
6133
|
-
Maybe: (onNone) => (maybe) => Maybe.is.none(maybe) ? makeFailed2(onNone()) : makePassed2(maybe.value),
|
|
6266
|
+
second: isSecond,
|
|
6134
6267
|
/**
|
|
6135
|
-
*
|
|
6136
|
-
*
|
|
6137
|
-
* Useful when bridging from error-short-circuiting `Result` pipelines into
|
|
6138
|
-
* error-accumulating `Validation` pipelines.
|
|
6268
|
+
* Type guard — checks if a These holds both values simultaneously.
|
|
6139
6269
|
*
|
|
6140
6270
|
* @example
|
|
6141
6271
|
* ```ts
|
|
6142
|
-
*
|
|
6143
|
-
*
|
|
6272
|
+
* const val = These.make.both(42, "warning");
|
|
6273
|
+
* if (These.is.both(val)) {
|
|
6274
|
+
* console.log(val.first, val.second); // 42 "warning"
|
|
6275
|
+
* }
|
|
6144
6276
|
* ```
|
|
6145
6277
|
*/
|
|
6146
|
-
|
|
6278
|
+
both: isBoth
|
|
6147
6279
|
},
|
|
6148
6280
|
/**
|
|
6149
|
-
*
|
|
6281
|
+
* Returns true if the These contains a first value (First or Both).
|
|
6150
6282
|
*
|
|
6151
6283
|
* @example
|
|
6152
6284
|
* ```ts
|
|
6153
|
-
*
|
|
6154
|
-
*
|
|
6285
|
+
* These.hasFirst(These.make.first(42)); // true
|
|
6286
|
+
* These.hasFirst(These.make.both(42, "warn"));// true
|
|
6287
|
+
* These.hasFirst(These.make.second("warn")); // false
|
|
6155
6288
|
* ```
|
|
6156
6289
|
*/
|
|
6157
|
-
|
|
6290
|
+
hasFirst,
|
|
6158
6291
|
/**
|
|
6159
|
-
*
|
|
6292
|
+
* Returns true if the These contains a second value (Second or Both).
|
|
6160
6293
|
*
|
|
6161
6294
|
* @example
|
|
6162
6295
|
* ```ts
|
|
6163
|
-
*
|
|
6296
|
+
* These.hasSecond(These.make.second("warn")); // true
|
|
6297
|
+
* These.hasSecond(These.make.both(42, "warn"));// true
|
|
6298
|
+
* These.hasSecond(These.make.first(42)); // false
|
|
6164
6299
|
* ```
|
|
6165
6300
|
*/
|
|
6166
|
-
|
|
6301
|
+
hasSecond,
|
|
6167
6302
|
/**
|
|
6168
|
-
*
|
|
6169
|
-
* Accumulates errors from both sides.
|
|
6303
|
+
* Transforms the first value, leaving the second unchanged.
|
|
6170
6304
|
*
|
|
6171
6305
|
* @example
|
|
6172
6306
|
* ```ts
|
|
6173
|
-
*
|
|
6174
|
-
* pipe(
|
|
6175
|
-
*
|
|
6176
|
-
* Validation.ap(Validation.make.passed(5)),
|
|
6177
|
-
* Validation.ap(Validation.make.passed(3))
|
|
6178
|
-
* ); // Passed(8)
|
|
6179
|
-
*
|
|
6180
|
-
* pipe(
|
|
6181
|
-
* Validation.make.passed(add),
|
|
6182
|
-
* Validation.ap(Validation.make.failed<string>("bad a")),
|
|
6183
|
-
* Validation.ap(Validation.make.failed<string>("bad b"))
|
|
6184
|
-
* ); // Failed(["bad a", "bad b"])
|
|
6307
|
+
* pipe(These.make.first(5), These.mapFirst(n => n * 2)); // First(10)
|
|
6308
|
+
* pipe(These.make.both(5, "warn"), These.mapFirst(n => n * 2)); // Both(10, "warn")
|
|
6309
|
+
* pipe(These.make.second("warn"), These.mapFirst(n => n * 2)); // Second("warn")
|
|
6185
6310
|
* ```
|
|
6186
6311
|
*/
|
|
6187
|
-
|
|
6188
|
-
if (
|
|
6189
|
-
return
|
|
6312
|
+
mapFirst: (f) => (data) => {
|
|
6313
|
+
if (isSecond(data)) {
|
|
6314
|
+
return data;
|
|
6315
|
+
}
|
|
6316
|
+
if (isFirst(data)) {
|
|
6317
|
+
return makeFirst(f(data.first));
|
|
6190
6318
|
}
|
|
6191
|
-
return
|
|
6319
|
+
return makeBoth(f(data.first), data.second);
|
|
6192
6320
|
},
|
|
6193
|
-
/**
|
|
6194
|
-
*
|
|
6195
|
-
* using a custom error concatenator function when both sides fail.
|
|
6321
|
+
/**
|
|
6322
|
+
* Transforms the second value, leaving the first unchanged.
|
|
6196
6323
|
*
|
|
6197
6324
|
* @example
|
|
6198
6325
|
* ```ts
|
|
6199
|
-
*
|
|
6200
|
-
*
|
|
6201
|
-
* pipe(fnVal, Validation.apCustom(concat)(argVal));
|
|
6326
|
+
* pipe(These.make.second("warn"), These.mapSecond(e => e.toUpperCase())); // Second("WARN")
|
|
6327
|
+
* pipe(These.make.both(5, "warn"), These.mapSecond(e => e.toUpperCase())); // Both(5, "WARN")
|
|
6202
6328
|
* ```
|
|
6203
6329
|
*/
|
|
6204
|
-
|
|
6205
|
-
if (
|
|
6206
|
-
return
|
|
6330
|
+
mapSecond: (f) => (data) => {
|
|
6331
|
+
if (isFirst(data)) {
|
|
6332
|
+
return data;
|
|
6207
6333
|
}
|
|
6208
|
-
|
|
6334
|
+
if (isSecond(data)) {
|
|
6335
|
+
return makeSecond(f(data.second));
|
|
6336
|
+
}
|
|
6337
|
+
return makeBoth(data.first, f(data.second));
|
|
6209
6338
|
},
|
|
6210
6339
|
/**
|
|
6211
|
-
*
|
|
6340
|
+
* Transforms both the first and second values independently.
|
|
6212
6341
|
*
|
|
6213
6342
|
* @example
|
|
6214
6343
|
* ```ts
|
|
6215
6344
|
* pipe(
|
|
6216
|
-
*
|
|
6217
|
-
*
|
|
6218
|
-
*
|
|
6219
|
-
* value => `Value: ${value}`
|
|
6220
|
-
* )
|
|
6221
|
-
* );
|
|
6345
|
+
* These.make.both(5, "warn"),
|
|
6346
|
+
* These.mapBoth(n => n * 2, e => e.toUpperCase())
|
|
6347
|
+
* ); // Both(10, "WARN")
|
|
6222
6348
|
* ```
|
|
6223
6349
|
*/
|
|
6224
|
-
|
|
6350
|
+
mapBoth: (onFirst, onSecond) => (data) => {
|
|
6351
|
+
if (isSecond(data)) {
|
|
6352
|
+
return makeSecond(onSecond(data.second));
|
|
6353
|
+
}
|
|
6354
|
+
if (isFirst(data)) {
|
|
6355
|
+
return makeFirst(onFirst(data.first));
|
|
6356
|
+
}
|
|
6357
|
+
return makeBoth(onFirst(data.first), onSecond(data.second));
|
|
6358
|
+
},
|
|
6225
6359
|
/**
|
|
6226
|
-
*
|
|
6360
|
+
* Chains These computations by passing the first value to f.
|
|
6361
|
+
* Second propagates unchanged; First and Both apply f to the first value.
|
|
6227
6362
|
*
|
|
6228
6363
|
* @example
|
|
6229
6364
|
* ```ts
|
|
6230
|
-
*
|
|
6231
|
-
*
|
|
6232
|
-
*
|
|
6233
|
-
*
|
|
6234
|
-
*
|
|
6235
|
-
* })
|
|
6236
|
-
* );
|
|
6365
|
+
* const double = (n: number): These<number, string> => These.make.first(n * 2);
|
|
6366
|
+
*
|
|
6367
|
+
* pipe(These.make.first(5), These.chainFirst(double)); // First(10)
|
|
6368
|
+
* pipe(These.make.both(5, "warn"), These.chainFirst(double)); // First(10)
|
|
6369
|
+
* pipe(These.make.second("warn"), These.chainFirst(double)); // Second("warn")
|
|
6237
6370
|
* ```
|
|
6238
6371
|
*/
|
|
6239
|
-
|
|
6372
|
+
chainFirst: (f) => (data) => {
|
|
6373
|
+
if (isSecond(data)) {
|
|
6374
|
+
return data;
|
|
6375
|
+
}
|
|
6376
|
+
return f(data.first);
|
|
6377
|
+
},
|
|
6240
6378
|
/**
|
|
6241
|
-
*
|
|
6242
|
-
*
|
|
6379
|
+
* Chains These computations by passing the second value to f.
|
|
6380
|
+
* First propagates unchanged; Second and Both apply f to the second value.
|
|
6243
6381
|
*
|
|
6244
6382
|
* @example
|
|
6245
6383
|
* ```ts
|
|
6246
|
-
*
|
|
6247
|
-
*
|
|
6248
|
-
* pipe(
|
|
6384
|
+
* const shout = (s: string): These<number, string> => These.make.second(s.toUpperCase());
|
|
6385
|
+
*
|
|
6386
|
+
* pipe(These.make.second("warn"), These.chainSecond(shout)); // Second("WARN")
|
|
6387
|
+
* pipe(These.make.both(5, "warn"), These.chainSecond(shout)); // Second("WARN")
|
|
6388
|
+
* pipe(These.make.first(5), These.chainSecond(shout)); // First(5)
|
|
6249
6389
|
* ```
|
|
6250
6390
|
*/
|
|
6251
|
-
|
|
6391
|
+
chainSecond: (f) => (data) => {
|
|
6392
|
+
if (isFirst(data)) {
|
|
6393
|
+
return data;
|
|
6394
|
+
}
|
|
6395
|
+
return f(data.second);
|
|
6396
|
+
},
|
|
6252
6397
|
/**
|
|
6253
|
-
*
|
|
6398
|
+
* Extracts a value from a These by providing handlers for all three cases.
|
|
6254
6399
|
*
|
|
6255
6400
|
* @example
|
|
6256
6401
|
* ```ts
|
|
6257
6402
|
* pipe(
|
|
6258
|
-
*
|
|
6259
|
-
*
|
|
6260
|
-
*
|
|
6403
|
+
* these,
|
|
6404
|
+
* These.fold(
|
|
6405
|
+
* a => `First: ${a}`,
|
|
6406
|
+
* b => `Second: ${b}`,
|
|
6407
|
+
* (a, b) => `Both: ${a} / ${b}`
|
|
6408
|
+
* )
|
|
6261
6409
|
* );
|
|
6262
6410
|
* ```
|
|
6263
6411
|
*/
|
|
6264
|
-
|
|
6265
|
-
if (
|
|
6266
|
-
|
|
6412
|
+
fold: (onFirst, onSecond, onBoth) => (data) => {
|
|
6413
|
+
if (isSecond(data)) {
|
|
6414
|
+
return onSecond(data.second);
|
|
6267
6415
|
}
|
|
6268
|
-
|
|
6416
|
+
if (isFirst(data)) {
|
|
6417
|
+
return onFirst(data.first);
|
|
6418
|
+
}
|
|
6419
|
+
return onBoth(data.first, data.second);
|
|
6269
6420
|
},
|
|
6270
6421
|
/**
|
|
6271
|
-
*
|
|
6272
|
-
* Useful for logging or reporting validation failures.
|
|
6422
|
+
* Pattern matches on a These, returning the result of the matching case.
|
|
6273
6423
|
*
|
|
6274
6424
|
* @example
|
|
6275
6425
|
* ```ts
|
|
6276
6426
|
* pipe(
|
|
6277
|
-
*
|
|
6278
|
-
*
|
|
6279
|
-
*
|
|
6427
|
+
* these,
|
|
6428
|
+
* These.match({
|
|
6429
|
+
* first: a => `First: ${a}`,
|
|
6430
|
+
* second: b => `Second: ${b}`,
|
|
6431
|
+
* both: (a, b) => `Both: ${a} / ${b}`
|
|
6432
|
+
* })
|
|
6280
6433
|
* );
|
|
6281
6434
|
* ```
|
|
6282
6435
|
*/
|
|
6283
|
-
|
|
6284
|
-
if (
|
|
6285
|
-
|
|
6436
|
+
match: (cases) => (data) => {
|
|
6437
|
+
if (isSecond(data)) {
|
|
6438
|
+
return cases.second(data.second);
|
|
6286
6439
|
}
|
|
6287
|
-
|
|
6440
|
+
if (isFirst(data)) {
|
|
6441
|
+
return cases.first(data.first);
|
|
6442
|
+
}
|
|
6443
|
+
return cases.both(data.first, data.second);
|
|
6288
6444
|
},
|
|
6289
6445
|
/**
|
|
6290
|
-
*
|
|
6291
|
-
* The
|
|
6292
|
-
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
6293
|
-
*/
|
|
6294
|
-
recover: (fallback) => (data) => isPassed(data) ? data : fallback(data.errors),
|
|
6295
|
-
/**
|
|
6296
|
-
* Recovers from a Failed state unless `isBlocked` returns true for any of the accumulated errors.
|
|
6297
|
-
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
6446
|
+
* Returns the first value, or a default if the These has no first value.
|
|
6447
|
+
* The default can be a different type, widening the result to `A | C`.
|
|
6298
6448
|
*
|
|
6299
6449
|
* @example
|
|
6300
6450
|
* ```ts
|
|
6301
|
-
* pipe(
|
|
6302
|
-
*
|
|
6303
|
-
*
|
|
6304
|
-
* );
|
|
6451
|
+
* pipe(These.make.first(5), These.getFirstOrElse(() => 0)); // 5
|
|
6452
|
+
* pipe(These.make.both(5, "warn"), These.getFirstOrElse(() => 0)); // 5
|
|
6453
|
+
* pipe(These.make.second("warn"), These.getFirstOrElse(() => 0)); // 0
|
|
6454
|
+
* pipe(These.make.second("warn"), These.getFirstOrElse(() => null)); // null — typed as number | null
|
|
6305
6455
|
* ```
|
|
6306
6456
|
*/
|
|
6307
|
-
|
|
6308
|
-
// --- to ---
|
|
6309
|
-
to: {
|
|
6310
|
-
/**
|
|
6311
|
-
* Converts a Validation to a Result.
|
|
6312
|
-
* Passed becomes Ok.
|
|
6313
|
-
* Direct call converts Failed to Err with accumulated error list `NonEmptyArr<E>`.
|
|
6314
|
-
* Curried call converts Failed to Err with combined error `E2` via `combineErrors`.
|
|
6315
|
-
*
|
|
6316
|
-
* @example
|
|
6317
|
-
* ```ts
|
|
6318
|
-
* Validation.to.Result(Validation.make.passed(42)); // Ok(42)
|
|
6319
|
-
* Validation.to.Result(Validation.make.failed("oops")); // Err(["oops"])
|
|
6320
|
-
* pipe(Validation.make.failed("oops"), Validation.to.Result(errors => errors.join(", "))); // Err("oops")
|
|
6321
|
-
* ```
|
|
6322
|
-
*/
|
|
6323
|
-
Result: toResult,
|
|
6324
|
-
/**
|
|
6325
|
-
* Converts a Validation to a Maybe. `Passed` becomes `Some`; `Failed` becomes `None`
|
|
6326
|
-
* (errors are discarded).
|
|
6327
|
-
*
|
|
6328
|
-
* @example
|
|
6329
|
-
* ```ts
|
|
6330
|
-
* Validation.to.Maybe(Validation.make.passed(42)); // Some(42)
|
|
6331
|
-
* Validation.to.Maybe(Validation.make.failed("bad")); // None
|
|
6332
|
-
* ```
|
|
6333
|
-
*/
|
|
6334
|
-
Maybe: (data) => isPassed(data) ? Maybe.make.some(data.value) : Maybe.make.none()
|
|
6335
|
-
},
|
|
6457
|
+
getFirstOrElse: (defaultValue) => (data) => hasFirst(data) ? data.first : defaultValue(),
|
|
6336
6458
|
/**
|
|
6337
|
-
*
|
|
6338
|
-
*
|
|
6339
|
-
* If either is Failed, accumulates errors from both sides.
|
|
6459
|
+
* Returns the second value, or a default if the These has no second value.
|
|
6460
|
+
* The default can be a different type, widening the result to `B | D`.
|
|
6340
6461
|
*
|
|
6341
6462
|
* @example
|
|
6342
6463
|
* ```ts
|
|
6343
|
-
*
|
|
6344
|
-
*
|
|
6345
|
-
*
|
|
6346
|
-
* );
|
|
6347
|
-
*
|
|
6348
|
-
* Validation.product(
|
|
6349
|
-
* Validation.make.failed("Name required"),
|
|
6350
|
-
* Validation.make.failed("Age must be >= 0")
|
|
6351
|
-
* ); // Failed(["Name required", "Age must be >= 0"])
|
|
6464
|
+
* pipe(These.make.second("warn"), These.getSecondOrElse(() => "none")); // "warn"
|
|
6465
|
+
* pipe(These.make.both(5, "warn"), These.getSecondOrElse(() => "none")); // "warn"
|
|
6466
|
+
* pipe(These.make.first(5), These.getSecondOrElse(() => "none")); // "none"
|
|
6467
|
+
* pipe(These.make.first(5), These.getSecondOrElse(() => null)); // null — typed as string | null
|
|
6352
6468
|
* ```
|
|
6353
6469
|
*/
|
|
6354
|
-
|
|
6355
|
-
if (isPassed(first)) {
|
|
6356
|
-
return isPassed(second) ? makePassed2([first.value, second.value]) : makeFailedAll2(second.errors);
|
|
6357
|
-
}
|
|
6358
|
-
return isPassed(second) ? makeFailedAll2(first.errors) : makeFailedAll2([...first.errors, ...second.errors]);
|
|
6359
|
-
},
|
|
6470
|
+
getSecondOrElse: (defaultValue) => (data) => hasSecond(data) ? data.second : defaultValue(),
|
|
6360
6471
|
/**
|
|
6361
|
-
*
|
|
6362
|
-
*
|
|
6363
|
-
* If any are Failed, returns Failed with all accumulated errors.
|
|
6472
|
+
* Runs a side effect on the first value without changing the These.
|
|
6473
|
+
* Useful for logging or debugging.
|
|
6364
6474
|
*
|
|
6365
6475
|
* @example
|
|
6366
6476
|
* ```ts
|
|
6367
|
-
*
|
|
6368
|
-
* validateName(name),
|
|
6369
|
-
* validateEmail(email),
|
|
6370
|
-
* validateAge(age)
|
|
6371
|
-
* ]);
|
|
6372
|
-
* // Passed([name, email, age]) or Failed([...all errors])
|
|
6477
|
+
* pipe(These.make.first(5), These.tap(console.log)); // logs 5, returns First(5)
|
|
6373
6478
|
* ```
|
|
6374
6479
|
*/
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
for (const v of data) {
|
|
6379
|
-
if (isPassed(v)) {
|
|
6380
|
-
values.push(v.value);
|
|
6381
|
-
} else {
|
|
6382
|
-
errors.push(...v.errors);
|
|
6383
|
-
}
|
|
6480
|
+
tap: (f) => (data) => {
|
|
6481
|
+
if (hasFirst(data)) {
|
|
6482
|
+
f(data.first);
|
|
6384
6483
|
}
|
|
6385
|
-
return
|
|
6484
|
+
return data;
|
|
6386
6485
|
},
|
|
6387
6486
|
/**
|
|
6388
|
-
*
|
|
6389
|
-
*
|
|
6487
|
+
* Swaps the roles of first and second values.
|
|
6488
|
+
* - First(a) → Second(a)
|
|
6489
|
+
* - Second(b) → First(b)
|
|
6490
|
+
* - Both(a, b) → Both(b, a)
|
|
6390
6491
|
*
|
|
6391
6492
|
* @example
|
|
6392
6493
|
* ```ts
|
|
6393
|
-
*
|
|
6394
|
-
*
|
|
6395
|
-
*
|
|
6396
|
-
* }); // Passed({ name: "Alice", age: 30 })
|
|
6397
|
-
*
|
|
6398
|
-
* Validation.struct({
|
|
6399
|
-
* name: Validation.make.failed("Name required"),
|
|
6400
|
-
* age: Validation.make.failed("Age must be >= 0")
|
|
6401
|
-
* }); // Failed(["Name required", "Age must be >= 0"])
|
|
6494
|
+
* These.swap(These.make.first(5)); // Second(5)
|
|
6495
|
+
* These.swap(These.make.second("warn")); // First("warn")
|
|
6496
|
+
* These.swap(These.make.both(5, "warn")); // Both("warn", 5)
|
|
6402
6497
|
* ```
|
|
6403
6498
|
*/
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
for (const key in fields) {
|
|
6408
|
-
if (Object.hasOwn(fields, key)) {
|
|
6409
|
-
const val = fields[key];
|
|
6410
|
-
if (isPassed(val)) {
|
|
6411
|
-
record[key] = val.value;
|
|
6412
|
-
} else {
|
|
6413
|
-
errors.push(...val.errors);
|
|
6414
|
-
}
|
|
6415
|
-
}
|
|
6499
|
+
swap: (data) => {
|
|
6500
|
+
if (isSecond(data)) {
|
|
6501
|
+
return makeFirst(data.second);
|
|
6416
6502
|
}
|
|
6417
|
-
|
|
6503
|
+
if (isFirst(data)) {
|
|
6504
|
+
return makeSecond(data.first);
|
|
6505
|
+
}
|
|
6506
|
+
return makeBoth(data.second, data.first);
|
|
6418
6507
|
}
|
|
6419
6508
|
};
|
|
6420
6509
|
export {
|