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