@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/index.cjs
CHANGED
|
@@ -3,9 +3,9 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target,
|
|
7
|
-
for (var name in
|
|
8
|
-
__defProp(target, name, { get:
|
|
6
|
+
var __export = (target, all2) => {
|
|
7
|
+
for (var name in all2)
|
|
8
|
+
__defProp(target, name, { get: all2[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
var __copyProps = (to, from, except, desc) => {
|
|
11
11
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
@@ -22,6 +22,7 @@ var src_exports = {};
|
|
|
22
22
|
__export(src_exports, {
|
|
23
23
|
Arr: () => Arr,
|
|
24
24
|
BigNum: () => BigNum,
|
|
25
|
+
Bool: () => Bool,
|
|
25
26
|
Brand: () => Brand,
|
|
26
27
|
Combinable: () => Combinable,
|
|
27
28
|
Deferred: () => Deferred,
|
|
@@ -2676,22 +2677,120 @@ function interpretFn(op, options) {
|
|
|
2676
2677
|
}
|
|
2677
2678
|
}
|
|
2678
2679
|
var Op = {
|
|
2679
|
-
|
|
2680
|
+
make: {
|
|
2681
|
+
/**
|
|
2682
|
+
* Creates an Ok outcome with the given value.
|
|
2683
|
+
*
|
|
2684
|
+
* @example
|
|
2685
|
+
* ```ts
|
|
2686
|
+
* Op.make.ok(42); // { kind: "OpOk", value: 42 }
|
|
2687
|
+
* ```
|
|
2688
|
+
*/
|
|
2689
|
+
ok: makeOk,
|
|
2690
|
+
/**
|
|
2691
|
+
* Creates an Err outcome with the given error.
|
|
2692
|
+
*
|
|
2693
|
+
* @example
|
|
2694
|
+
* ```ts
|
|
2695
|
+
* Op.make.err("Something went wrong"); // { kind: "OpErr", error: "Something went wrong" }
|
|
2696
|
+
* ```
|
|
2697
|
+
*/
|
|
2698
|
+
err: makeErr,
|
|
2699
|
+
/**
|
|
2700
|
+
* Creates a Nil outcome with the given cancellation/drop reason.
|
|
2701
|
+
*
|
|
2702
|
+
* @example
|
|
2703
|
+
* ```ts
|
|
2704
|
+
* Op.make.nil("aborted"); // { kind: "OpNil", reason: "aborted" }
|
|
2705
|
+
* ```
|
|
2706
|
+
*/
|
|
2707
|
+
nil: makeNil
|
|
2708
|
+
},
|
|
2709
|
+
is: {
|
|
2710
|
+
/**
|
|
2711
|
+
* Type guard that checks if an Op state is Idle.
|
|
2712
|
+
*
|
|
2713
|
+
* @example
|
|
2714
|
+
* ```ts
|
|
2715
|
+
* if (Op.is.idle(manager.state)) {
|
|
2716
|
+
* console.log("Ready to execute");
|
|
2717
|
+
* }
|
|
2718
|
+
* ```
|
|
2719
|
+
*/
|
|
2720
|
+
idle: isIdle,
|
|
2721
|
+
/**
|
|
2722
|
+
* Type guard that checks if an Op state is Pending (actively executing).
|
|
2723
|
+
*
|
|
2724
|
+
* @example
|
|
2725
|
+
* ```ts
|
|
2726
|
+
* if (Op.is.pending(manager.state)) {
|
|
2727
|
+
* showSpinner();
|
|
2728
|
+
* }
|
|
2729
|
+
* ```
|
|
2730
|
+
*/
|
|
2731
|
+
pending: isPending,
|
|
2732
|
+
/**
|
|
2733
|
+
* Type guard that checks if an Op state is Queued (waiting in a concurrency queue).
|
|
2734
|
+
*
|
|
2735
|
+
* @example
|
|
2736
|
+
* ```ts
|
|
2737
|
+
* if (Op.is.queued(manager.state)) {
|
|
2738
|
+
* console.log("Position in queue:", manager.state.position);
|
|
2739
|
+
* }
|
|
2740
|
+
* ```
|
|
2741
|
+
*/
|
|
2742
|
+
queued: isQueued,
|
|
2743
|
+
/**
|
|
2744
|
+
* Type guard that checks if an Op state is Retrying after a failure.
|
|
2745
|
+
*
|
|
2746
|
+
* @example
|
|
2747
|
+
* ```ts
|
|
2748
|
+
* if (Op.is.retrying(manager.state)) {
|
|
2749
|
+
* console.log("Retry attempt:", manager.state.attempt);
|
|
2750
|
+
* }
|
|
2751
|
+
* ```
|
|
2752
|
+
*/
|
|
2753
|
+
retrying: isRetrying,
|
|
2754
|
+
/**
|
|
2755
|
+
* Type guard that checks if an Op state or outcome is Ok.
|
|
2756
|
+
*
|
|
2757
|
+
* @example
|
|
2758
|
+
* ```ts
|
|
2759
|
+
* if (Op.is.ok(outcome)) {
|
|
2760
|
+
* render(outcome.value);
|
|
2761
|
+
* }
|
|
2762
|
+
* ```
|
|
2763
|
+
*/
|
|
2764
|
+
ok: isOk,
|
|
2765
|
+
/**
|
|
2766
|
+
* Type guard that checks if an Op state or outcome is Err.
|
|
2767
|
+
*
|
|
2768
|
+
* @example
|
|
2769
|
+
* ```ts
|
|
2770
|
+
* if (Op.is.err(outcome)) {
|
|
2771
|
+
* showError(outcome.error);
|
|
2772
|
+
* }
|
|
2773
|
+
* ```
|
|
2774
|
+
*/
|
|
2775
|
+
err: isErr,
|
|
2776
|
+
/**
|
|
2777
|
+
* Type guard that checks if an Op state or outcome is Nil.
|
|
2778
|
+
*
|
|
2779
|
+
* @example
|
|
2780
|
+
* ```ts
|
|
2781
|
+
* if (Op.is.nil(outcome)) {
|
|
2782
|
+
* console.log("Skipped due to:", outcome.reason);
|
|
2783
|
+
* }
|
|
2784
|
+
* ```
|
|
2785
|
+
*/
|
|
2786
|
+
nil: isNil
|
|
2787
|
+
},
|
|
2680
2788
|
create: (factory, onError) => ({
|
|
2681
2789
|
_factory: (input, signal) => Deferred.from.Promise(
|
|
2682
2790
|
factory(signal)(input).then((value) => Result.make.ok(value)).catch((error) => signal.aborted ? null : Result.make.err(onError(error)))
|
|
2683
2791
|
)
|
|
2684
2792
|
}),
|
|
2685
2793
|
lift: (f) => Op.create((signal) => (input) => f(input, signal), (e) => e),
|
|
2686
|
-
ok: makeOk,
|
|
2687
|
-
err: makeErr,
|
|
2688
|
-
isIdle,
|
|
2689
|
-
isPending,
|
|
2690
|
-
isQueued,
|
|
2691
|
-
isRetrying,
|
|
2692
|
-
isOk,
|
|
2693
|
-
isErr,
|
|
2694
|
-
isNil,
|
|
2695
2794
|
match: (cases) => (outcome) => {
|
|
2696
2795
|
if (outcome.kind === "OpOk") {
|
|
2697
2796
|
return cases.ok(outcome.value);
|
|
@@ -4847,9 +4946,9 @@ var Stream = {
|
|
|
4847
4946
|
var makeSome2 = (value) => Task.resolve(Maybe.make.some(value));
|
|
4848
4947
|
var makeNone2 = () => Task.resolve(Maybe.make.none());
|
|
4849
4948
|
var mapTaskMaybe = (f) => (data) => Task.map(Maybe.map(f))(data);
|
|
4850
|
-
var chainTaskMaybe = (f) => (data) => Task.chain(
|
|
4851
|
-
|
|
4852
|
-
)
|
|
4949
|
+
var chainTaskMaybe = (f) => (data) => Task.chain((option) => Maybe.is.some(option) ? f(option.value) : Task.resolve(Maybe.make.none()))(
|
|
4950
|
+
data
|
|
4951
|
+
);
|
|
4853
4952
|
var TaskMaybe = {
|
|
4854
4953
|
/**
|
|
4855
4954
|
* Wraps a value in a Some inside a Task.
|
|
@@ -4882,8 +4981,6 @@ var TaskMaybe = {
|
|
|
4882
4981
|
*/
|
|
4883
4982
|
none: makeNone2
|
|
4884
4983
|
},
|
|
4885
|
-
some: makeSome2,
|
|
4886
|
-
none: makeNone2,
|
|
4887
4984
|
// --- from ---
|
|
4888
4985
|
from: {
|
|
4889
4986
|
/**
|
|
@@ -5019,7 +5116,7 @@ var TaskMaybe = {
|
|
|
5019
5116
|
*
|
|
5020
5117
|
* @example
|
|
5021
5118
|
* ```ts
|
|
5022
|
-
* pipe(Task.Maybe.some(42), Task.Maybe.bindTo("value")); // Task.Maybe({ value: 42 })
|
|
5119
|
+
* pipe(Task.Maybe.make.some(42), Task.Maybe.bindTo("value")); // Task.Maybe({ value: 42 })
|
|
5023
5120
|
* ```
|
|
5024
5121
|
*/
|
|
5025
5122
|
bindTo: (key) => (data) => mapTaskMaybe((a) => ({ [key]: a }))(data),
|
|
@@ -5029,8 +5126,8 @@ var TaskMaybe = {
|
|
|
5029
5126
|
* @example
|
|
5030
5127
|
* ```ts
|
|
5031
5128
|
* pipe(
|
|
5032
|
-
* Task.Maybe.some({ a: 1 }),
|
|
5033
|
-
* Task.Maybe.bind("b", ({ a }) => Task.Maybe.some(a + 1))
|
|
5129
|
+
* Task.Maybe.make.some({ a: 1 }),
|
|
5130
|
+
* Task.Maybe.bind("b", ({ a }) => Task.Maybe.make.some(a + 1))
|
|
5034
5131
|
* ); // Task.Maybe({ a: 1, b: 2 })
|
|
5035
5132
|
* ```
|
|
5036
5133
|
*/
|
|
@@ -5043,14 +5140,12 @@ var TaskMaybe = {
|
|
|
5043
5140
|
* @example
|
|
5044
5141
|
* ```ts
|
|
5045
5142
|
* pipe(
|
|
5046
|
-
* Task.Maybe.none(),
|
|
5047
|
-
* Task.Maybe.recover(() => Task.Maybe.some(42))
|
|
5143
|
+
* Task.Maybe.make.none(),
|
|
5144
|
+
* Task.Maybe.recover(() => Task.Maybe.make.some(42))
|
|
5048
5145
|
* ); // Task.Maybe(42)
|
|
5049
5146
|
* ```
|
|
5050
5147
|
*/
|
|
5051
|
-
recover: (fallback) => (data) => Task.chain((maybe) => Maybe.is.none(maybe) ? fallback() : Task.resolve(maybe))(
|
|
5052
|
-
data
|
|
5053
|
-
),
|
|
5148
|
+
recover: (fallback) => (data) => Task.chain((maybe) => Maybe.is.none(maybe) ? fallback() : Task.resolve(maybe))(data),
|
|
5054
5149
|
/**
|
|
5055
5150
|
* Combines a record of Task.Maybes into a single Task.Maybe of a record.
|
|
5056
5151
|
* Evaluates fields in parallel and returns None if any task resolves to None.
|
|
@@ -5058,8 +5153,8 @@ var TaskMaybe = {
|
|
|
5058
5153
|
* @example
|
|
5059
5154
|
* ```ts
|
|
5060
5155
|
* Task.Maybe.struct({
|
|
5061
|
-
* name: Task.Maybe.some("Alice"),
|
|
5062
|
-
* age: Task.Maybe.some(30)
|
|
5156
|
+
* name: Task.Maybe.make.some("Alice"),
|
|
5157
|
+
* age: Task.Maybe.make.some(30)
|
|
5063
5158
|
* }); // Task.Maybe({ name: "Alice", age: 30 })
|
|
5064
5159
|
* ```
|
|
5065
5160
|
*/
|
|
@@ -5120,8 +5215,6 @@ var TaskResult = {
|
|
|
5120
5215
|
*/
|
|
5121
5216
|
err: makeErr3
|
|
5122
5217
|
},
|
|
5123
|
-
ok: makeOk3,
|
|
5124
|
-
err: makeErr3,
|
|
5125
5218
|
// --- from ---
|
|
5126
5219
|
from: {
|
|
5127
5220
|
/**
|
|
@@ -5163,7 +5256,7 @@ var TaskResult = {
|
|
|
5163
5256
|
*
|
|
5164
5257
|
* @example
|
|
5165
5258
|
* ```ts
|
|
5166
|
-
* const taskResult = Task.Result.ok(42);
|
|
5259
|
+
* const taskResult = Task.Result.make.ok(42);
|
|
5167
5260
|
* const taskMaybe = pipe(taskResult, Task.Result.to.Maybe);
|
|
5168
5261
|
* ```
|
|
5169
5262
|
*/
|
|
@@ -5226,7 +5319,7 @@ var TaskResult = {
|
|
|
5226
5319
|
* fetchTask,
|
|
5227
5320
|
* Task.Result.recoverUnless(
|
|
5228
5321
|
* (e) => e === "fatal",
|
|
5229
|
-
* () => Task.Result.ok("fallback")
|
|
5322
|
+
* () => Task.Result.make.ok("fallback")
|
|
5230
5323
|
* )
|
|
5231
5324
|
* );
|
|
5232
5325
|
* ```
|
|
@@ -5289,7 +5382,7 @@ var TaskResult = {
|
|
|
5289
5382
|
*
|
|
5290
5383
|
* @example
|
|
5291
5384
|
* ```ts
|
|
5292
|
-
* pipe(Task.Result.ok(42), Task.Result.bindTo("value")); // Task.Result({ value: 42 })
|
|
5385
|
+
* pipe(Task.Result.make.ok(42), Task.Result.bindTo("value")); // Task.Result({ value: 42 })
|
|
5293
5386
|
* ```
|
|
5294
5387
|
*/
|
|
5295
5388
|
bindTo: (key) => (data) => mapTaskResult((a) => ({ [key]: a }))(data),
|
|
@@ -5299,8 +5392,8 @@ var TaskResult = {
|
|
|
5299
5392
|
* @example
|
|
5300
5393
|
* ```ts
|
|
5301
5394
|
* pipe(
|
|
5302
|
-
* Task.Result.ok({ a: 1 }),
|
|
5303
|
-
* Task.Result.bind("b", ({ a }) => Task.Result.ok(a + 1))
|
|
5395
|
+
* Task.Result.make.ok({ a: 1 }),
|
|
5396
|
+
* Task.Result.bind("b", ({ a }) => Task.Result.make.ok(a + 1))
|
|
5304
5397
|
* ); // Task.Result({ a: 1, b: 2 })
|
|
5305
5398
|
* ```
|
|
5306
5399
|
*/
|
|
@@ -5315,8 +5408,8 @@ var TaskResult = {
|
|
|
5315
5408
|
* @example
|
|
5316
5409
|
* ```ts
|
|
5317
5410
|
* Task.Result.struct({
|
|
5318
|
-
* name: Task.Result.ok("Alice"),
|
|
5319
|
-
* age: Task.Result.ok(30)
|
|
5411
|
+
* name: Task.Result.make.ok("Alice"),
|
|
5412
|
+
* age: Task.Result.make.ok(30)
|
|
5320
5413
|
* }); // Task.Result({ name: "Alice", age: 30 })
|
|
5321
5414
|
* ```
|
|
5322
5415
|
*/
|
|
@@ -5436,1553 +5529,1550 @@ var TaskResult = {
|
|
|
5436
5529
|
// src/internal/InternalTypes.ts
|
|
5437
5530
|
var isNonEmptyArr = (list) => list.length > 0;
|
|
5438
5531
|
|
|
5439
|
-
// src/Core/
|
|
5440
|
-
var makePassed = (value) =>
|
|
5441
|
-
var makeFailed = (error) =>
|
|
5442
|
-
var makeFailedAll = (errors) =>
|
|
5443
|
-
var
|
|
5532
|
+
// src/Core/Validation.ts
|
|
5533
|
+
var makePassed = (value) => ({ kind: "Passed", value });
|
|
5534
|
+
var makeFailed = (error) => ({ kind: "Failed", errors: [error] });
|
|
5535
|
+
var makeFailedAll = (errors) => ({ kind: "Failed", errors });
|
|
5536
|
+
var isPassed = (data) => data.kind === "Passed";
|
|
5537
|
+
var isFailed = (data) => data.kind === "Failed";
|
|
5538
|
+
function toResult(arg) {
|
|
5539
|
+
if (typeof arg === "function") {
|
|
5540
|
+
const combine = arg;
|
|
5541
|
+
return (val) => isPassed(val) ? Result.make.ok(val.value) : Result.make.err(combine(val.errors));
|
|
5542
|
+
}
|
|
5543
|
+
return isPassed(arg) ? Result.make.ok(arg.value) : Result.make.err(arg.errors);
|
|
5544
|
+
}
|
|
5545
|
+
var Validation = {
|
|
5444
5546
|
make: {
|
|
5445
5547
|
/**
|
|
5446
|
-
* Wraps a value in a passed
|
|
5548
|
+
* Wraps a value in a passed Validation.
|
|
5447
5549
|
*
|
|
5448
5550
|
* @example
|
|
5449
5551
|
* ```ts
|
|
5450
|
-
*
|
|
5451
|
-
* const res = await task(); // Passed(42)
|
|
5552
|
+
* Validation.make.passed(42); // Passed(42)
|
|
5452
5553
|
* ```
|
|
5453
5554
|
*/
|
|
5454
5555
|
passed: makePassed,
|
|
5455
5556
|
/**
|
|
5456
|
-
* Creates a failed
|
|
5557
|
+
* Creates a failed Validation from a single error.
|
|
5457
5558
|
*
|
|
5458
5559
|
* @example
|
|
5459
5560
|
* ```ts
|
|
5460
|
-
*
|
|
5461
|
-
* const res = await task(); // Failed(["invalid"])
|
|
5561
|
+
* Validation.make.failed("Invalid input");
|
|
5462
5562
|
* ```
|
|
5463
5563
|
*/
|
|
5464
5564
|
failed: makeFailed,
|
|
5465
5565
|
/**
|
|
5466
|
-
* Creates a failed
|
|
5566
|
+
* Creates a failed Validation from multiple errors.
|
|
5467
5567
|
*
|
|
5468
5568
|
* @example
|
|
5469
5569
|
* ```ts
|
|
5470
|
-
*
|
|
5471
|
-
* const res = await task(); // Failed(["err1", "err2"])
|
|
5570
|
+
* Validation.make.failedAll(["Invalid input"]);
|
|
5472
5571
|
* ```
|
|
5473
5572
|
*/
|
|
5474
5573
|
failedAll: makeFailedAll
|
|
5475
5574
|
},
|
|
5476
|
-
|
|
5477
|
-
failed: makeFailed,
|
|
5478
|
-
failedAll: makeFailedAll,
|
|
5479
|
-
// --- from ---
|
|
5480
|
-
from: {
|
|
5575
|
+
is: {
|
|
5481
5576
|
/**
|
|
5482
|
-
*
|
|
5577
|
+
* Type guard that checks if a Validation is passed.
|
|
5483
5578
|
*
|
|
5484
5579
|
* @example
|
|
5485
5580
|
* ```ts
|
|
5486
|
-
*
|
|
5581
|
+
* const v = Validation.make.passed(42);
|
|
5582
|
+
* if (Validation.is.passed(v)) {
|
|
5583
|
+
* console.log(v.value); // 42
|
|
5584
|
+
* }
|
|
5487
5585
|
* ```
|
|
5488
5586
|
*/
|
|
5489
|
-
|
|
5587
|
+
passed: isPassed,
|
|
5490
5588
|
/**
|
|
5491
|
-
*
|
|
5492
|
-
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
5493
|
-
* Otherwise, returns Passed.
|
|
5589
|
+
* Type guard that checks if a Validation is failed.
|
|
5494
5590
|
*
|
|
5495
5591
|
* @example
|
|
5496
5592
|
* ```ts
|
|
5497
|
-
*
|
|
5498
|
-
*
|
|
5593
|
+
* const v = Validation.make.failed("invalid");
|
|
5594
|
+
* if (Validation.is.failed(v)) {
|
|
5595
|
+
* console.log(v.errors); // ["invalid"]
|
|
5596
|
+
* }
|
|
5499
5597
|
* ```
|
|
5500
5598
|
*/
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
|
|
5599
|
+
failed: isFailed
|
|
5600
|
+
},
|
|
5601
|
+
/**
|
|
5602
|
+
* Creates a Validation from a synchronous thunk that may throw.
|
|
5603
|
+
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
5604
|
+
*
|
|
5605
|
+
* @example
|
|
5606
|
+
* ```ts
|
|
5607
|
+
* const result = Validation.tryCatch(
|
|
5608
|
+
* () => JSON.parse(rawString),
|
|
5609
|
+
* { onError: (e) => `Parse error: ${e}` }
|
|
5610
|
+
* );
|
|
5611
|
+
* ```
|
|
5612
|
+
*/
|
|
5613
|
+
tryCatch: (f, options) => {
|
|
5614
|
+
try {
|
|
5615
|
+
return makePassed(f());
|
|
5616
|
+
} catch (error) {
|
|
5617
|
+
return makeFailed(options.onError(error));
|
|
5618
|
+
}
|
|
5619
|
+
},
|
|
5620
|
+
// --- from ---
|
|
5621
|
+
from: {
|
|
5504
5622
|
/**
|
|
5505
|
-
* Creates a
|
|
5506
|
-
*
|
|
5623
|
+
* Creates a Validation from a predicate applied to a value.
|
|
5624
|
+
* Returns Passed if the predicate passes, Failed from `onFalse` otherwise.
|
|
5507
5625
|
*
|
|
5508
5626
|
* @example
|
|
5509
5627
|
* ```ts
|
|
5510
|
-
*
|
|
5511
|
-
*
|
|
5628
|
+
* const validateName = Validation.from.Predicate(
|
|
5629
|
+
* (s: string) => s.length > 0,
|
|
5630
|
+
* () => "Name is required"
|
|
5631
|
+
* );
|
|
5632
|
+
*
|
|
5633
|
+
* validateName("Alice"); // Passed("Alice")
|
|
5634
|
+
* validateName(""); // Failed(["Name is required"])
|
|
5512
5635
|
* ```
|
|
5513
5636
|
*/
|
|
5514
|
-
|
|
5515
|
-
Maybe.is.none(maybe) ? Validation.make.failed(onNone()) : Validation.make.passed(maybe.value)
|
|
5516
|
-
),
|
|
5637
|
+
Predicate: (pred, onFalse) => (a) => pred(a) ? makePassed(a) : makeFailed(onFalse(a)),
|
|
5517
5638
|
/**
|
|
5518
|
-
* Creates a
|
|
5519
|
-
*
|
|
5639
|
+
* Creates a Validation from a nullable value.
|
|
5640
|
+
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
5641
|
+
* Otherwise, returns Passed.
|
|
5520
5642
|
*
|
|
5521
5643
|
* @example
|
|
5522
5644
|
* ```ts
|
|
5523
|
-
*
|
|
5524
|
-
*
|
|
5645
|
+
* pipe(null, Validation.from.nullable(() => "is null")); // Failed(["is null"])
|
|
5646
|
+
* pipe(42, Validation.from.nullable(() => "is null")); // Passed(42)
|
|
5525
5647
|
* ```
|
|
5526
5648
|
*/
|
|
5527
|
-
|
|
5528
|
-
},
|
|
5529
|
-
// --- to ---
|
|
5530
|
-
to: {
|
|
5649
|
+
nullable: (onNull) => (value) => value === null || value === void 0 ? makeFailed(onNull()) : makePassed(value),
|
|
5531
5650
|
/**
|
|
5532
|
-
*
|
|
5533
|
-
*
|
|
5651
|
+
* Creates a Validation from a Maybe.
|
|
5652
|
+
* If the Maybe is None, returns Failed with the error from onNone.
|
|
5653
|
+
* Otherwise, returns Passed.
|
|
5534
5654
|
*
|
|
5535
5655
|
* @example
|
|
5536
5656
|
* ```ts
|
|
5537
|
-
*
|
|
5657
|
+
* pipe(Maybe.make.none(), Validation.from.Maybe(() => "is none")); // Failed(["is none"])
|
|
5658
|
+
* pipe(Maybe.make.some(42), Validation.from.Maybe(() => "is none")); // Passed(42)
|
|
5538
5659
|
* ```
|
|
5539
5660
|
*/
|
|
5540
|
-
|
|
5661
|
+
Maybe: (onNone) => (maybe) => Maybe.is.none(maybe) ? makeFailed(onNone()) : makePassed(maybe.value),
|
|
5541
5662
|
/**
|
|
5542
|
-
* Converts a `
|
|
5543
|
-
*
|
|
5663
|
+
* Converts a `Result` to a `Validation`. `Ok` becomes `Passed`; `Err(e)` becomes `Failed([e])`.
|
|
5664
|
+
*
|
|
5665
|
+
* Useful when bridging from error-short-circuiting `Result` pipelines into
|
|
5666
|
+
* error-accumulating `Validation` pipelines.
|
|
5544
5667
|
*
|
|
5545
5668
|
* @example
|
|
5546
5669
|
* ```ts
|
|
5547
|
-
*
|
|
5670
|
+
* Validation.from.Result(Result.make.ok(42)); // Passed(42)
|
|
5671
|
+
* Validation.from.Result(Result.make.err("bad")); // Failed(["bad"])
|
|
5548
5672
|
* ```
|
|
5549
5673
|
*/
|
|
5550
|
-
|
|
5674
|
+
Result: (data) => data.kind === "Ok" ? makePassed(data.value) : makeFailed(data.error)
|
|
5551
5675
|
},
|
|
5552
5676
|
/**
|
|
5553
|
-
*
|
|
5554
|
-
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
5555
|
-
* The thunk optionally receives an `AbortSignal` forwarded from the call site.
|
|
5677
|
+
* Transforms the success value inside a Validation.
|
|
5556
5678
|
*
|
|
5557
5679
|
* @example
|
|
5558
5680
|
* ```ts
|
|
5559
|
-
*
|
|
5560
|
-
*
|
|
5561
|
-
* { onError: (e) => `Failed to load config: ${e}` }
|
|
5562
|
-
* );
|
|
5681
|
+
* pipe(Validation.make.passed(5), Validation.map(n => n * 2)); // Passed(10)
|
|
5682
|
+
* pipe(Validation.make.failed("oops"), Validation.map(n => n * 2)); // Failed(["oops"])
|
|
5563
5683
|
* ```
|
|
5564
5684
|
*/
|
|
5565
|
-
|
|
5566
|
-
// oxlint-disable-next-line require-await
|
|
5567
|
-
globalThis.Promise.resolve().then(async () => f(signal)).then(Validation.make.passed).catch(
|
|
5568
|
-
(error) => Validation.make.failed(options.onError(error))
|
|
5569
|
-
)
|
|
5570
|
-
),
|
|
5571
|
-
/**
|
|
5572
|
-
* Transforms the success value inside a Task.Validation.
|
|
5573
|
-
*/
|
|
5574
|
-
map: (f) => (data) => Task.map(Validation.map(f))(data),
|
|
5685
|
+
map: (f) => (data) => isPassed(data) ? makePassed(f(data.value)) : data,
|
|
5575
5686
|
/**
|
|
5576
|
-
*
|
|
5577
|
-
* Task.Validation. Both Tasks run in parallel and errors from both sides
|
|
5578
|
-
* are accumulated.
|
|
5687
|
+
* Transforms the error list inside a Validation.
|
|
5579
5688
|
*
|
|
5580
5689
|
* @example
|
|
5581
5690
|
* ```ts
|
|
5582
|
-
* pipe(
|
|
5583
|
-
* Task.Validation.passed((name: string) => (age: number) => ({ name, age })),
|
|
5584
|
-
* Task.Validation.ap(validateName(name)),
|
|
5585
|
-
* Task.Validation.ap(validateAge(age))
|
|
5586
|
-
* )();
|
|
5691
|
+
* pipe(Validation.make.failed("oops"), Validation.mapError(e => e.toUpperCase())); // Failed(["OOPS"])
|
|
5587
5692
|
* ```
|
|
5588
5693
|
*/
|
|
5589
|
-
|
|
5590
|
-
Promise.all([Deferred.to.Promise(data(signal)), Deferred.to.Promise(arg(signal))]).then(
|
|
5591
|
-
([vf, va]) => Validation.ap(va)(vf)
|
|
5592
|
-
)
|
|
5593
|
-
),
|
|
5594
|
-
/**
|
|
5595
|
-
* Extracts a value from a Task.Validation by providing handlers for both cases.
|
|
5596
|
-
*/
|
|
5597
|
-
fold: (onFailed, onPassed) => (data) => Task.map(Validation.fold(onFailed, onPassed))(data),
|
|
5694
|
+
mapError: (f) => (data) => isFailed(data) ? makeFailedAll(data.errors.map(f)) : data,
|
|
5598
5695
|
/**
|
|
5599
|
-
*
|
|
5696
|
+
* Applies a function wrapped in a Validation to a value wrapped in a Validation.
|
|
5697
|
+
* Accumulates errors from both sides.
|
|
5600
5698
|
*
|
|
5601
5699
|
* @example
|
|
5602
5700
|
* ```ts
|
|
5701
|
+
* const add = (a: number) => (b: number) => a + b;
|
|
5603
5702
|
* pipe(
|
|
5604
|
-
*
|
|
5605
|
-
*
|
|
5606
|
-
*
|
|
5607
|
-
*
|
|
5608
|
-
*
|
|
5609
|
-
*
|
|
5703
|
+
* Validation.make.passed(add),
|
|
5704
|
+
* Validation.ap(Validation.make.passed(5)),
|
|
5705
|
+
* Validation.ap(Validation.make.passed(3))
|
|
5706
|
+
* ); // Passed(8)
|
|
5707
|
+
*
|
|
5708
|
+
* pipe(
|
|
5709
|
+
* Validation.make.passed(add),
|
|
5710
|
+
* Validation.ap(Validation.make.failed<string>("bad a")),
|
|
5711
|
+
* Validation.ap(Validation.make.failed<string>("bad b"))
|
|
5712
|
+
* ); // Failed(["bad a", "bad b"])
|
|
5610
5713
|
* ```
|
|
5611
5714
|
*/
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
/**
|
|
5619
|
-
* Executes a side effect on the success value without changing the Task.Validation.
|
|
5620
|
-
* Useful for logging or debugging.
|
|
5621
|
-
*/
|
|
5622
|
-
tap: (f) => (data) => Task.map(Validation.tap(f))(data),
|
|
5623
|
-
/**
|
|
5624
|
-
* Recovers from a Failed state by providing a fallback Task.Validation.
|
|
5625
|
-
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
5626
|
-
* The fallback can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
5627
|
-
*/
|
|
5628
|
-
recover: (fallback) => (data) => Task.chain(
|
|
5629
|
-
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : fallback(validation.errors)
|
|
5630
|
-
)(data),
|
|
5715
|
+
ap: (arg) => (data) => {
|
|
5716
|
+
if (isPassed(data)) {
|
|
5717
|
+
return isPassed(arg) ? makePassed(data.value(arg.value)) : makeFailedAll(arg.errors);
|
|
5718
|
+
}
|
|
5719
|
+
return isPassed(arg) ? makeFailedAll(data.errors) : makeFailedAll([...data.errors, ...arg.errors]);
|
|
5720
|
+
},
|
|
5631
5721
|
/**
|
|
5632
|
-
*
|
|
5633
|
-
*
|
|
5722
|
+
* Applies a function wrapped in a Validation to a value wrapped in a Validation,
|
|
5723
|
+
* using a custom error concatenator function when both sides fail.
|
|
5634
5724
|
*
|
|
5635
5725
|
* @example
|
|
5636
5726
|
* ```ts
|
|
5637
|
-
*
|
|
5638
|
-
*
|
|
5639
|
-
*
|
|
5640
|
-
* (errors) => errors.includes("fatal"),
|
|
5641
|
-
* (errors) => Task.Validation.passed("fallback")
|
|
5642
|
-
* )
|
|
5643
|
-
* );
|
|
5727
|
+
* const concat = (e1: NonEmptyArr<string>, e2: NonEmptyArr<string>): NonEmptyArr<string> =>
|
|
5728
|
+
* [...e1, ...e2];
|
|
5729
|
+
* pipe(fnVal, Validation.apCustom(concat)(argVal));
|
|
5644
5730
|
* ```
|
|
5645
5731
|
*/
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5732
|
+
apCustom: (concat2) => (arg) => (data) => {
|
|
5733
|
+
if (isPassed(data)) {
|
|
5734
|
+
return isPassed(arg) ? makePassed(data.value(arg.value)) : makeFailedAll(arg.errors);
|
|
5735
|
+
}
|
|
5736
|
+
return isPassed(arg) ? makeFailedAll(data.errors) : makeFailedAll(concat2(data.errors, arg.errors));
|
|
5737
|
+
},
|
|
5649
5738
|
/**
|
|
5650
|
-
*
|
|
5651
|
-
* If both are Passed, returns Passed with both values. If either fails, accumulates
|
|
5652
|
-
* errors from both sides.
|
|
5739
|
+
* Extracts the value from a Validation by providing handlers for both cases.
|
|
5653
5740
|
*
|
|
5654
5741
|
* @example
|
|
5655
5742
|
* ```ts
|
|
5656
|
-
*
|
|
5657
|
-
*
|
|
5658
|
-
*
|
|
5659
|
-
*
|
|
5743
|
+
* pipe(
|
|
5744
|
+
* Validation.make.passed(42),
|
|
5745
|
+
* Validation.fold(
|
|
5746
|
+
* errors => `Errors: ${errors.join(", ")}`,
|
|
5747
|
+
* value => `Value: ${value}`
|
|
5748
|
+
* )
|
|
5749
|
+
* );
|
|
5660
5750
|
* ```
|
|
5661
5751
|
*/
|
|
5662
|
-
|
|
5663
|
-
Promise.all([Deferred.to.Promise(first(signal)), Deferred.to.Promise(second(signal))]).then(
|
|
5664
|
-
([va, vb]) => Validation.product(va, vb)
|
|
5665
|
-
)
|
|
5666
|
-
),
|
|
5752
|
+
fold: (onFailed, onPassed) => (data) => isPassed(data) ? onPassed(data.value) : onFailed(data.errors),
|
|
5667
5753
|
/**
|
|
5668
|
-
*
|
|
5669
|
-
* If all are Passed, returns Passed with all values as an array.
|
|
5670
|
-
* If any fail, returns Failed with all accumulated errors.
|
|
5754
|
+
* Pattern matches on a Validation, returning the result of the matching case.
|
|
5671
5755
|
*
|
|
5672
5756
|
* @example
|
|
5673
5757
|
* ```ts
|
|
5674
|
-
*
|
|
5675
|
-
*
|
|
5676
|
-
*
|
|
5677
|
-
*
|
|
5678
|
-
*
|
|
5758
|
+
* pipe(
|
|
5759
|
+
* validation,
|
|
5760
|
+
* Validation.match({
|
|
5761
|
+
* passed: value => `Got ${value}`,
|
|
5762
|
+
* failed: errors => `Failed: ${errors.join(", ")}`
|
|
5763
|
+
* })
|
|
5764
|
+
* );
|
|
5679
5765
|
* ```
|
|
5680
5766
|
*/
|
|
5681
|
-
|
|
5682
|
-
Promise.all(data.map((t) => Deferred.to.Promise(t(signal)))).then((results) => {
|
|
5683
|
-
const [first, ...rest] = results;
|
|
5684
|
-
return Validation.productAll([first, ...rest]);
|
|
5685
|
-
})
|
|
5686
|
-
),
|
|
5767
|
+
match: (cases) => (data) => isPassed(data) ? cases.passed(data.value) : cases.failed(data.errors),
|
|
5687
5768
|
/**
|
|
5688
|
-
*
|
|
5769
|
+
* Returns the success value or a default value if the Validation is failed.
|
|
5770
|
+
* The default can be a different type, widening the result to `A | B`.
|
|
5689
5771
|
*
|
|
5690
5772
|
* @example
|
|
5691
5773
|
* ```ts
|
|
5692
|
-
* pipe(
|
|
5693
|
-
*
|
|
5694
|
-
*
|
|
5695
|
-
* ); // Task.Validation(Failed(["OOPS"]))
|
|
5774
|
+
* pipe(Validation.make.passed(5), Validation.getOrElse(() => 0)); // 5
|
|
5775
|
+
* pipe(Validation.make.failed("oops"), Validation.getOrElse(() => 0)); // 0
|
|
5776
|
+
* pipe(Validation.make.failed("oops"), Validation.getOrElse(() => null)); // null — typed as number | null
|
|
5696
5777
|
* ```
|
|
5697
5778
|
*/
|
|
5698
|
-
|
|
5779
|
+
getOrElse: (defaultValue) => (data) => isPassed(data) ? data.value : defaultValue(),
|
|
5699
5780
|
/**
|
|
5700
|
-
* Executes a side effect on the
|
|
5781
|
+
* Executes a side effect on the success value without changing the Validation.
|
|
5701
5782
|
*
|
|
5702
5783
|
* @example
|
|
5703
5784
|
* ```ts
|
|
5704
5785
|
* pipe(
|
|
5705
|
-
*
|
|
5706
|
-
*
|
|
5786
|
+
* Validation.make.passed(5),
|
|
5787
|
+
* Validation.tap(n => console.log("Value:", n)),
|
|
5788
|
+
* Validation.map(n => n * 2)
|
|
5707
5789
|
* );
|
|
5708
5790
|
* ```
|
|
5709
5791
|
*/
|
|
5710
|
-
|
|
5792
|
+
tap: (f) => (data) => {
|
|
5793
|
+
if (isPassed(data)) {
|
|
5794
|
+
f(data.value);
|
|
5795
|
+
}
|
|
5796
|
+
return data;
|
|
5797
|
+
},
|
|
5711
5798
|
/**
|
|
5712
|
-
*
|
|
5713
|
-
*
|
|
5799
|
+
* Executes a side effect on the accumulated errors without changing the Validation.
|
|
5800
|
+
* Useful for logging or reporting validation failures.
|
|
5714
5801
|
*
|
|
5715
5802
|
* @example
|
|
5716
5803
|
* ```ts
|
|
5717
|
-
*
|
|
5718
|
-
*
|
|
5719
|
-
*
|
|
5720
|
-
*
|
|
5804
|
+
* pipe(
|
|
5805
|
+
* Validation.make.failed("Name required"),
|
|
5806
|
+
* Validation.tapError(errors => console.error("validation failed:", errors)),
|
|
5807
|
+
* Validation.map(toUser)
|
|
5808
|
+
* );
|
|
5721
5809
|
* ```
|
|
5722
5810
|
*/
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
for (let i = 0; i < keys3.length; i++) {
|
|
5730
|
-
const res = results[i];
|
|
5731
|
-
if (Validation.is.passed(res)) {
|
|
5732
|
-
record[keys3[i]] = res.value;
|
|
5733
|
-
} else {
|
|
5734
|
-
errors.push(...res.errors);
|
|
5735
|
-
}
|
|
5736
|
-
}
|
|
5737
|
-
return isNonEmptyArr(errors) ? Validation.make.failedAll(errors) : Validation.make.passed(record);
|
|
5738
|
-
});
|
|
5739
|
-
})()),
|
|
5811
|
+
tapError: (f) => (data) => {
|
|
5812
|
+
if (isFailed(data)) {
|
|
5813
|
+
f(data.errors);
|
|
5814
|
+
}
|
|
5815
|
+
return data;
|
|
5816
|
+
},
|
|
5740
5817
|
/**
|
|
5741
|
-
*
|
|
5742
|
-
*
|
|
5743
|
-
*
|
|
5744
|
-
* @example
|
|
5745
|
-
* ```ts
|
|
5746
|
-
* const validate = Task.Validation.memoize(validateFormTask);
|
|
5747
|
-
* ```
|
|
5818
|
+
* Recovers from a Failed state by providing a fallback Validation.
|
|
5819
|
+
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
5820
|
+
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
5748
5821
|
*/
|
|
5749
|
-
|
|
5750
|
-
};
|
|
5751
|
-
|
|
5752
|
-
// src/Core/Task.ts
|
|
5753
|
-
var toPromise2 = (task, signal) => Deferred.to.Promise(task(signal));
|
|
5754
|
-
var fromPromise2 = (f) => (signal) => Deferred.from.Promise(f(signal));
|
|
5755
|
-
var getMs2 = (duration) => Duration.to.milliseconds(duration);
|
|
5756
|
-
var resolveTask = (value) => () => Deferred.from.Promise(globalThis.Promise.resolve(value));
|
|
5757
|
-
var syncTask = (f) => () => Deferred.from.Promise(globalThis.Promise.resolve(f()));
|
|
5758
|
-
var Task = {
|
|
5822
|
+
recover: (fallback) => (data) => isPassed(data) ? data : fallback(data.errors),
|
|
5759
5823
|
/**
|
|
5760
|
-
*
|
|
5824
|
+
* Recovers from a Failed state unless `isBlocked` returns true for any of the accumulated errors.
|
|
5825
|
+
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
5761
5826
|
*
|
|
5762
5827
|
* @example
|
|
5763
5828
|
* ```ts
|
|
5764
|
-
*
|
|
5765
|
-
*
|
|
5829
|
+
* pipe(
|
|
5830
|
+
* Validation.make.failed("field-error"),
|
|
5831
|
+
* Validation.recoverUnless(e => e === "fatal", () => Validation.make.passed(0))
|
|
5832
|
+
* ); // Passed(0)
|
|
5766
5833
|
* ```
|
|
5767
5834
|
*/
|
|
5768
|
-
|
|
5769
|
-
// ---
|
|
5770
|
-
|
|
5835
|
+
recoverUnless: (isBlocked, fallback) => (data) => isFailed(data) && !data.errors.some(isBlocked) ? fallback() : data,
|
|
5836
|
+
// --- to ---
|
|
5837
|
+
to: {
|
|
5771
5838
|
/**
|
|
5772
|
-
*
|
|
5773
|
-
*
|
|
5839
|
+
* Converts a Validation to a Result.
|
|
5840
|
+
* Passed becomes Ok.
|
|
5841
|
+
* Direct call converts Failed to Err with accumulated error list `NonEmptyArr<E>`.
|
|
5842
|
+
* Curried call converts Failed to Err with combined error `E2` via `combineErrors`.
|
|
5774
5843
|
*
|
|
5775
5844
|
* @example
|
|
5776
5845
|
* ```ts
|
|
5777
|
-
*
|
|
5778
|
-
*
|
|
5846
|
+
* Validation.to.Result(Validation.make.passed(42)); // Ok(42)
|
|
5847
|
+
* Validation.to.Result(Validation.make.failed("oops")); // Err(["oops"])
|
|
5848
|
+
* pipe(Validation.make.failed("oops"), Validation.to.Result(errors => errors.join(", "))); // Err("oops")
|
|
5779
5849
|
* ```
|
|
5780
5850
|
*/
|
|
5781
|
-
|
|
5851
|
+
Result: toResult,
|
|
5852
|
+
/**
|
|
5853
|
+
* Converts a Validation to a Maybe. `Passed` becomes `Some`; `Failed` becomes `None`
|
|
5854
|
+
* (errors are discarded).
|
|
5855
|
+
*
|
|
5856
|
+
* @example
|
|
5857
|
+
* ```ts
|
|
5858
|
+
* Validation.to.Maybe(Validation.make.passed(42)); // Some(42)
|
|
5859
|
+
* Validation.to.Maybe(Validation.make.failed("bad")); // None
|
|
5860
|
+
* ```
|
|
5861
|
+
*/
|
|
5862
|
+
Maybe: (data) => isPassed(data) ? Maybe.make.some(data.value) : Maybe.make.none()
|
|
5782
5863
|
},
|
|
5783
5864
|
/**
|
|
5784
|
-
*
|
|
5785
|
-
*
|
|
5865
|
+
* Combines two independent Validation instances into a tuple.
|
|
5866
|
+
* If both are Passed, returns Passed with both values as a tuple.
|
|
5867
|
+
* If either is Failed, accumulates errors from both sides.
|
|
5786
5868
|
*
|
|
5787
5869
|
* @example
|
|
5788
5870
|
* ```ts
|
|
5789
|
-
*
|
|
5790
|
-
*
|
|
5791
|
-
*
|
|
5792
|
-
* );
|
|
5871
|
+
* Validation.product(
|
|
5872
|
+
* Validation.make.passed("alice"),
|
|
5873
|
+
* Validation.make.passed(30)
|
|
5874
|
+
* ); // Passed(["alice", 30])
|
|
5875
|
+
*
|
|
5876
|
+
* Validation.product(
|
|
5877
|
+
* Validation.make.failed("Name required"),
|
|
5878
|
+
* Validation.make.failed("Age must be >= 0")
|
|
5879
|
+
* ); // Failed(["Name required", "Age must be >= 0"])
|
|
5793
5880
|
* ```
|
|
5794
5881
|
*/
|
|
5795
|
-
|
|
5882
|
+
product: (first, second) => {
|
|
5883
|
+
if (isPassed(first)) {
|
|
5884
|
+
return isPassed(second) ? makePassed([first.value, second.value]) : makeFailedAll(second.errors);
|
|
5885
|
+
}
|
|
5886
|
+
return isPassed(second) ? makeFailedAll(first.errors) : makeFailedAll([...first.errors, ...second.errors]);
|
|
5887
|
+
},
|
|
5796
5888
|
/**
|
|
5797
|
-
*
|
|
5889
|
+
* Combines a non-empty list of Validation instances, accumulating all errors.
|
|
5890
|
+
* If all are Passed, returns Passed with all values collected into an array.
|
|
5891
|
+
* If any are Failed, returns Failed with all accumulated errors.
|
|
5798
5892
|
*
|
|
5799
5893
|
* @example
|
|
5800
5894
|
* ```ts
|
|
5801
|
-
*
|
|
5802
|
-
*
|
|
5803
|
-
*
|
|
5804
|
-
*
|
|
5895
|
+
* Validation.productAll([
|
|
5896
|
+
* validateName(name),
|
|
5897
|
+
* validateEmail(email),
|
|
5898
|
+
* validateAge(age)
|
|
5899
|
+
* ]);
|
|
5900
|
+
* // Passed([name, email, age]) or Failed([...all errors])
|
|
5805
5901
|
* ```
|
|
5806
5902
|
*/
|
|
5807
|
-
|
|
5903
|
+
productAll: (data) => {
|
|
5904
|
+
const values3 = [];
|
|
5905
|
+
const errors = [];
|
|
5906
|
+
for (const v of data) {
|
|
5907
|
+
if (isPassed(v)) {
|
|
5908
|
+
values3.push(v.value);
|
|
5909
|
+
} else {
|
|
5910
|
+
errors.push(...v.errors);
|
|
5911
|
+
}
|
|
5912
|
+
}
|
|
5913
|
+
return isNonEmptyArr(errors) ? makeFailedAll(errors) : makePassed(values3);
|
|
5914
|
+
},
|
|
5808
5915
|
/**
|
|
5809
|
-
*
|
|
5916
|
+
* Combines a record of Validations into a single Validation of a record.
|
|
5917
|
+
* Accumulates all failed branches' errors.
|
|
5810
5918
|
*
|
|
5811
5919
|
* @example
|
|
5812
5920
|
* ```ts
|
|
5813
|
-
*
|
|
5814
|
-
*
|
|
5815
|
-
*
|
|
5921
|
+
* Validation.struct({
|
|
5922
|
+
* name: Validation.make.passed("Alice"),
|
|
5923
|
+
* age: Validation.make.passed(30)
|
|
5924
|
+
* }); // Passed({ name: "Alice", age: 30 })
|
|
5816
5925
|
*
|
|
5817
|
-
*
|
|
5818
|
-
*
|
|
5819
|
-
*
|
|
5820
|
-
* )
|
|
5926
|
+
* Validation.struct({
|
|
5927
|
+
* name: Validation.make.failed("Name required"),
|
|
5928
|
+
* age: Validation.make.failed("Age must be >= 0")
|
|
5929
|
+
* }); // Failed(["Name required", "Age must be >= 0"])
|
|
5821
5930
|
* ```
|
|
5822
5931
|
*/
|
|
5823
|
-
|
|
5932
|
+
struct: (fields) => {
|
|
5933
|
+
const record = {};
|
|
5934
|
+
const errors = [];
|
|
5935
|
+
for (const key in fields) {
|
|
5936
|
+
if (Object.hasOwn(fields, key)) {
|
|
5937
|
+
const val = fields[key];
|
|
5938
|
+
if (isPassed(val)) {
|
|
5939
|
+
record[key] = val.value;
|
|
5940
|
+
} else {
|
|
5941
|
+
errors.push(...val.errors);
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5944
|
+
}
|
|
5945
|
+
return isNonEmptyArr(errors) ? makeFailedAll(errors) : makePassed(record);
|
|
5946
|
+
}
|
|
5947
|
+
};
|
|
5948
|
+
|
|
5949
|
+
// src/Core/TaskValidation.ts
|
|
5950
|
+
var makePassed2 = (value) => Task.resolve(Validation.make.passed(value));
|
|
5951
|
+
var makeFailed2 = (error) => Task.resolve(Validation.make.failed(error));
|
|
5952
|
+
var makeFailedAll2 = (errors) => Task.resolve(Validation.make.failedAll(errors));
|
|
5953
|
+
var TaskValidation = {
|
|
5954
|
+
make: {
|
|
5955
|
+
/**
|
|
5956
|
+
* Wraps a value in a passed Task.Validation.
|
|
5957
|
+
*
|
|
5958
|
+
* @example
|
|
5959
|
+
* ```ts
|
|
5960
|
+
* const task = Task.Validation.make.passed(42);
|
|
5961
|
+
* const res = await task(); // Passed(42)
|
|
5962
|
+
* ```
|
|
5963
|
+
*/
|
|
5964
|
+
passed: makePassed2,
|
|
5965
|
+
/**
|
|
5966
|
+
* Creates a failed Task.Validation with a single error.
|
|
5967
|
+
*
|
|
5968
|
+
* @example
|
|
5969
|
+
* ```ts
|
|
5970
|
+
* const task = Task.Validation.make.failed("invalid");
|
|
5971
|
+
* const res = await task(); // Failed(["invalid"])
|
|
5972
|
+
* ```
|
|
5973
|
+
*/
|
|
5974
|
+
failed: makeFailed2,
|
|
5975
|
+
/**
|
|
5976
|
+
* Creates a failed Task.Validation from multiple errors.
|
|
5977
|
+
*
|
|
5978
|
+
* @example
|
|
5979
|
+
* ```ts
|
|
5980
|
+
* const task = Task.Validation.make.failedAll(["err1", "err2"]);
|
|
5981
|
+
* const res = await task(); // Failed(["err1", "err2"])
|
|
5982
|
+
* ```
|
|
5983
|
+
*/
|
|
5984
|
+
failedAll: makeFailedAll2
|
|
5985
|
+
},
|
|
5986
|
+
// --- from ---
|
|
5987
|
+
from: {
|
|
5988
|
+
/**
|
|
5989
|
+
* Lifts a Validation into a Task.Validation.
|
|
5990
|
+
*
|
|
5991
|
+
* @example
|
|
5992
|
+
* ```ts
|
|
5993
|
+
* Task.Validation.from.Validation(Validation.make.passed(42));
|
|
5994
|
+
* ```
|
|
5995
|
+
*/
|
|
5996
|
+
Validation: (validation) => Task.resolve(validation),
|
|
5997
|
+
/**
|
|
5998
|
+
* Creates a Task.Validation from a nullable value.
|
|
5999
|
+
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
6000
|
+
* Otherwise, returns Passed.
|
|
6001
|
+
*
|
|
6002
|
+
* @example
|
|
6003
|
+
* ```ts
|
|
6004
|
+
* Task.Validation.from.nullable(() => "missing")(42); // resolves to Passed(42)
|
|
6005
|
+
* Task.Validation.from.nullable(() => "missing")(null); // resolves to Failed(["missing"])
|
|
6006
|
+
* ```
|
|
6007
|
+
*/
|
|
6008
|
+
nullable: (onNull) => (value) => Task.resolve(
|
|
6009
|
+
value === null || value === void 0 ? Validation.make.failed(onNull()) : Validation.make.passed(value)
|
|
6010
|
+
),
|
|
6011
|
+
/**
|
|
6012
|
+
* Creates a Task.Validation from a Maybe.
|
|
6013
|
+
* Some becomes Passed, None becomes Failed with the error from onNone.
|
|
6014
|
+
*
|
|
6015
|
+
* @example
|
|
6016
|
+
* ```ts
|
|
6017
|
+
* Task.Validation.from.Maybe(() => "empty")(Maybe.make.some(42)); // resolves to Passed(42)
|
|
6018
|
+
* Task.Validation.from.Maybe(() => "empty")(Maybe.make.none()); // resolves to Failed(["empty"])
|
|
6019
|
+
* ```
|
|
6020
|
+
*/
|
|
6021
|
+
Maybe: (onNone) => (maybe) => Task.resolve(
|
|
6022
|
+
Maybe.is.none(maybe) ? Validation.make.failed(onNone()) : Validation.make.passed(maybe.value)
|
|
6023
|
+
),
|
|
6024
|
+
/**
|
|
6025
|
+
* Creates a Task.Validation from a Result.
|
|
6026
|
+
* Ok becomes Passed, Err(e) becomes Failed([e]).
|
|
6027
|
+
*
|
|
6028
|
+
* @example
|
|
6029
|
+
* ```ts
|
|
6030
|
+
* Task.Validation.from.Result(Result.make.ok(42)); // resolves to Passed(42)
|
|
6031
|
+
* Task.Validation.from.Result(Result.make.err("bad")); // resolves to Failed(["bad"])
|
|
6032
|
+
* ```
|
|
6033
|
+
*/
|
|
6034
|
+
Result: (result) => Task.resolve(Validation.from.Result(result))
|
|
6035
|
+
},
|
|
6036
|
+
// --- to ---
|
|
6037
|
+
to: {
|
|
6038
|
+
/**
|
|
6039
|
+
* Converts a `Task.Validation` to a `Task.Result`, combining accumulated errors using `combineErrors`.
|
|
6040
|
+
* `Passed(a)` becomes `Ok(a)`; `Failed(errors)` becomes `Err(combineErrors(errors))`.
|
|
6041
|
+
*
|
|
6042
|
+
* @example
|
|
6043
|
+
* ```ts
|
|
6044
|
+
* Task.Validation.to.Result((errors) => errors.join(", "))(validationTask);
|
|
6045
|
+
* ```
|
|
6046
|
+
*/
|
|
6047
|
+
Result: (combineErrors) => (data) => Task.map(Validation.to.Result(combineErrors))(data),
|
|
6048
|
+
/**
|
|
6049
|
+
* Converts a `Task.Validation` to a `Task.Maybe`.
|
|
6050
|
+
* `Passed(a)` becomes `Some(a)`; `Failed(errors)` becomes `None` (errors are discarded).
|
|
6051
|
+
*
|
|
6052
|
+
* @example
|
|
6053
|
+
* ```ts
|
|
6054
|
+
* Task.Validation.to.Maybe(validationTask);
|
|
6055
|
+
* ```
|
|
6056
|
+
*/
|
|
6057
|
+
Maybe: (data) => Task.map(Validation.to.Maybe)(data)
|
|
6058
|
+
},
|
|
5824
6059
|
/**
|
|
5825
|
-
*
|
|
5826
|
-
*
|
|
6060
|
+
* Creates a Task.Validation from a Promise-returning thunk that may throw or reject.
|
|
6061
|
+
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
6062
|
+
* The thunk optionally receives an `AbortSignal` forwarded from the call site.
|
|
5827
6063
|
*
|
|
5828
6064
|
* @example
|
|
5829
6065
|
* ```ts
|
|
5830
|
-
* const
|
|
5831
|
-
*
|
|
5832
|
-
*
|
|
5833
|
-
*
|
|
5834
|
-
* Task.ap(Task.resolve(3))
|
|
5835
|
-
* )(); // Deferred<8>
|
|
6066
|
+
* const loadConfig = Task.Validation.tryCatch(
|
|
6067
|
+
* (signal) => configStore.get("default", { signal }),
|
|
6068
|
+
* { onError: (e) => `Failed to load config: ${e}` }
|
|
6069
|
+
* );
|
|
5836
6070
|
* ```
|
|
5837
6071
|
*/
|
|
5838
|
-
|
|
6072
|
+
tryCatch: (f, options) => (signal) => Deferred.from.Promise(
|
|
6073
|
+
// oxlint-disable-next-line require-await
|
|
6074
|
+
globalThis.Promise.resolve().then(async () => f(signal)).then(Validation.make.passed).catch(
|
|
6075
|
+
(error) => Validation.make.failed(options.onError(error))
|
|
6076
|
+
)
|
|
6077
|
+
),
|
|
5839
6078
|
/**
|
|
5840
|
-
*
|
|
5841
|
-
|
|
6079
|
+
* Transforms the success value inside a Task.Validation.
|
|
6080
|
+
*/
|
|
6081
|
+
map: (f) => (data) => Task.map(Validation.map(f))(data),
|
|
6082
|
+
/**
|
|
6083
|
+
* Applies a function wrapped in a Task.Validation to a value wrapped in a
|
|
6084
|
+
* Task.Validation. Both Tasks run in parallel and errors from both sides
|
|
6085
|
+
* are accumulated.
|
|
5842
6086
|
*
|
|
5843
6087
|
* @example
|
|
5844
6088
|
* ```ts
|
|
5845
6089
|
* pipe(
|
|
5846
|
-
*
|
|
5847
|
-
* Task.
|
|
5848
|
-
* Task.
|
|
5849
|
-
* );
|
|
6090
|
+
* Task.Validation.make.passed((name: string) => (age: number) => ({ name, age })),
|
|
6091
|
+
* Task.Validation.ap(validateName(name)),
|
|
6092
|
+
* Task.Validation.ap(validateAge(age))
|
|
6093
|
+
* )();
|
|
5850
6094
|
* ```
|
|
5851
6095
|
*/
|
|
5852
|
-
|
|
5853
|
-
(signal)
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
})
|
|
6096
|
+
ap: (arg) => (data) => (signal) => Deferred.from.Promise(
|
|
6097
|
+
Promise.all([Deferred.to.Promise(data(signal)), Deferred.to.Promise(arg(signal))]).then(
|
|
6098
|
+
([vf, va]) => Validation.ap(va)(vf)
|
|
6099
|
+
)
|
|
5857
6100
|
),
|
|
5858
6101
|
/**
|
|
5859
|
-
*
|
|
6102
|
+
* Extracts a value from a Task.Validation by providing handlers for both cases.
|
|
6103
|
+
*/
|
|
6104
|
+
fold: (onFailed, onPassed) => (data) => Task.map(Validation.fold(onFailed, onPassed))(data),
|
|
6105
|
+
/**
|
|
6106
|
+
* Pattern matches on a Task.Validation, returning a Task of the result.
|
|
5860
6107
|
*
|
|
5861
6108
|
* @example
|
|
5862
6109
|
* ```ts
|
|
5863
|
-
*
|
|
5864
|
-
*
|
|
6110
|
+
* pipe(
|
|
6111
|
+
* validateForm(input),
|
|
6112
|
+
* Task.Validation.match({
|
|
6113
|
+
* passed: data => save(data),
|
|
6114
|
+
* failed: errors => showErrors(errors)
|
|
6115
|
+
* })
|
|
6116
|
+
* )();
|
|
5865
6117
|
* ```
|
|
5866
6118
|
*/
|
|
5867
|
-
|
|
5868
|
-
(signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))
|
|
5869
|
-
),
|
|
6119
|
+
match: (cases) => (data) => Task.map(Validation.match(cases))(data),
|
|
5870
6120
|
/**
|
|
5871
|
-
*
|
|
5872
|
-
*
|
|
6121
|
+
* Returns the success value or a default value if the Task.Validation is failed.
|
|
6122
|
+
* The default can be a different type, widening the result to `Task<A | B>`.
|
|
6123
|
+
*/
|
|
6124
|
+
getOrElse: (defaultValue) => (data) => Task.map(Validation.getOrElse(defaultValue))(data),
|
|
6125
|
+
/**
|
|
6126
|
+
* Executes a side effect on the success value without changing the Task.Validation.
|
|
6127
|
+
* Useful for logging or debugging.
|
|
6128
|
+
*/
|
|
6129
|
+
tap: (f) => (data) => Task.map(Validation.tap(f))(data),
|
|
6130
|
+
/**
|
|
6131
|
+
* Recovers from a Failed state by providing a fallback Task.Validation.
|
|
6132
|
+
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
6133
|
+
* The fallback can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
6134
|
+
*/
|
|
6135
|
+
recover: (fallback) => (data) => Task.chain(
|
|
6136
|
+
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : fallback(validation.errors)
|
|
6137
|
+
)(data),
|
|
6138
|
+
/**
|
|
6139
|
+
* Recovers from a Failed state unless the predicate `isBlocked` returns true for the accumulated errors.
|
|
6140
|
+
* The fallback receives the accumulated errors and can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
5873
6141
|
*
|
|
5874
6142
|
* @example
|
|
5875
6143
|
* ```ts
|
|
5876
6144
|
* pipe(
|
|
5877
|
-
*
|
|
5878
|
-
* Task.
|
|
5879
|
-
*
|
|
6145
|
+
* validationTask,
|
|
6146
|
+
* Task.Validation.recoverUnless(
|
|
6147
|
+
* (errors) => errors.includes("fatal"),
|
|
6148
|
+
* (errors) => Task.Validation.make.passed("fallback")
|
|
6149
|
+
* )
|
|
6150
|
+
* );
|
|
5880
6151
|
* ```
|
|
5881
6152
|
*/
|
|
5882
|
-
|
|
5883
|
-
(
|
|
5884
|
-
|
|
5885
|
-
const onAbort = () => {
|
|
5886
|
-
clearTimeout(timerId);
|
|
5887
|
-
res(toPromise2(data, signal));
|
|
5888
|
-
};
|
|
5889
|
-
if (signal) {
|
|
5890
|
-
if (signal.aborted) {
|
|
5891
|
-
return res(toPromise2(data, signal));
|
|
5892
|
-
}
|
|
5893
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5894
|
-
}
|
|
5895
|
-
timerId = setTimeout(() => {
|
|
5896
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5897
|
-
res(toPromise2(data, signal));
|
|
5898
|
-
}, getMs2(duration));
|
|
5899
|
-
})
|
|
5900
|
-
),
|
|
6153
|
+
recoverUnless: (isBlocked, fallback) => (data) => Task.chain(
|
|
6154
|
+
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : isBlocked(validation.errors) ? Task.resolve(validation) : fallback(validation.errors)
|
|
6155
|
+
)(data),
|
|
5901
6156
|
/**
|
|
5902
|
-
* Runs
|
|
5903
|
-
*
|
|
6157
|
+
* Runs two Task.Validations concurrently and combines their results into a tuple.
|
|
6158
|
+
* If both are Passed, returns Passed with both values. If either fails, accumulates
|
|
6159
|
+
* errors from both sides.
|
|
5904
6160
|
*
|
|
5905
6161
|
* @example
|
|
5906
6162
|
* ```ts
|
|
5907
|
-
*
|
|
5908
|
-
*
|
|
5909
|
-
*
|
|
5910
|
-
* )(); //
|
|
6163
|
+
* await Task.Validation.product(
|
|
6164
|
+
* validateName(form.name),
|
|
6165
|
+
* validateAge(form.age),
|
|
6166
|
+
* )(); // Passed(["Alice", 30]) or Failed([...errors])
|
|
5911
6167
|
* ```
|
|
5912
6168
|
*/
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
const results = [];
|
|
5919
|
-
const wait = () => new Promise((r) => {
|
|
5920
|
-
let timerId;
|
|
5921
|
-
const onAbort = () => {
|
|
5922
|
-
clearTimeout(timerId);
|
|
5923
|
-
r();
|
|
5924
|
-
};
|
|
5925
|
-
if (signal) {
|
|
5926
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5927
|
-
}
|
|
5928
|
-
timerId = setTimeout(() => {
|
|
5929
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5930
|
-
r();
|
|
5931
|
-
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
5932
|
-
});
|
|
5933
|
-
const run = (left) => {
|
|
5934
|
-
if (signal?.aborted) {
|
|
5935
|
-
return Promise.resolve(results);
|
|
5936
|
-
}
|
|
5937
|
-
return toPromise2(task, signal).then((a) => {
|
|
5938
|
-
results.push(a);
|
|
5939
|
-
if (left <= 1 || signal?.aborted) {
|
|
5940
|
-
return results;
|
|
5941
|
-
}
|
|
5942
|
-
return wait().then(() => run(left - 1));
|
|
5943
|
-
});
|
|
5944
|
-
};
|
|
5945
|
-
return run(times);
|
|
5946
|
-
}),
|
|
6169
|
+
product: (first, second) => (signal) => Deferred.from.Promise(
|
|
6170
|
+
Promise.all([Deferred.to.Promise(first(signal)), Deferred.to.Promise(second(signal))]).then(
|
|
6171
|
+
([va, vb]) => Validation.product(va, vb)
|
|
6172
|
+
)
|
|
6173
|
+
),
|
|
5947
6174
|
/**
|
|
5948
|
-
* Runs
|
|
5949
|
-
*
|
|
5950
|
-
*
|
|
5951
|
-
* regardless of whether the predicate was satisfied.
|
|
6175
|
+
* Runs all Task.Validations concurrently and collects results.
|
|
6176
|
+
* If all are Passed, returns Passed with all values as an array.
|
|
6177
|
+
* If any fail, returns Failed with all accumulated errors.
|
|
5952
6178
|
*
|
|
5953
6179
|
* @example
|
|
5954
6180
|
* ```ts
|
|
5955
|
-
*
|
|
5956
|
-
*
|
|
5957
|
-
*
|
|
5958
|
-
*
|
|
6181
|
+
* await Task.Validation.productAll([
|
|
6182
|
+
* validateName(form.name),
|
|
6183
|
+
* validateEmail(form.email),
|
|
6184
|
+
* validateAge(form.age),
|
|
6185
|
+
* ])(); // Passed([name, email, age]) or Failed([...all errors])
|
|
5959
6186
|
* ```
|
|
5960
6187
|
*/
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
r();
|
|
5968
|
-
};
|
|
5969
|
-
if (signal) {
|
|
5970
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5971
|
-
}
|
|
5972
|
-
timerId = setTimeout(() => {
|
|
5973
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5974
|
-
r();
|
|
5975
|
-
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
5976
|
-
});
|
|
5977
|
-
const run = (attempt, lastValue) => {
|
|
5978
|
-
if (signal?.aborted && lastValue !== void 0) {
|
|
5979
|
-
return Promise.resolve(lastValue);
|
|
5980
|
-
}
|
|
5981
|
-
return toPromise2(task, signal).then((a) => {
|
|
5982
|
-
if (predicate(a)) {
|
|
5983
|
-
return a;
|
|
5984
|
-
}
|
|
5985
|
-
if (maxAttempts !== void 0 && attempt >= maxAttempts) {
|
|
5986
|
-
return a;
|
|
5987
|
-
}
|
|
5988
|
-
if (signal?.aborted) {
|
|
5989
|
-
return a;
|
|
5990
|
-
}
|
|
5991
|
-
return wait().then(() => run(attempt + 1, a));
|
|
5992
|
-
});
|
|
5993
|
-
};
|
|
5994
|
-
return run(1);
|
|
5995
|
-
}),
|
|
6188
|
+
productAll: (data) => (signal) => Deferred.from.Promise(
|
|
6189
|
+
Promise.all(data.map((t) => Deferred.to.Promise(t(signal)))).then((results) => {
|
|
6190
|
+
const [first, ...rest] = results;
|
|
6191
|
+
return Validation.productAll([first, ...rest]);
|
|
6192
|
+
})
|
|
6193
|
+
),
|
|
5996
6194
|
/**
|
|
5997
|
-
*
|
|
5998
|
-
* immediately. When one resolves, the other tasks are cancelled (aborted)
|
|
5999
|
-
* downstream.
|
|
6195
|
+
* Transforms all accumulated errors inside a Task.Validation.
|
|
6000
6196
|
*
|
|
6001
6197
|
* @example
|
|
6002
6198
|
* ```ts
|
|
6003
|
-
*
|
|
6004
|
-
*
|
|
6005
|
-
*
|
|
6006
|
-
*
|
|
6199
|
+
* pipe(
|
|
6200
|
+
* Task.Validation.make.failed("oops"),
|
|
6201
|
+
* Task.Validation.mapError(e => e.toUpperCase())
|
|
6202
|
+
* ); // Task.Validation(Failed(["OOPS"]))
|
|
6007
6203
|
* ```
|
|
6008
6204
|
*/
|
|
6009
|
-
|
|
6010
|
-
if (tasks.length === 0) {
|
|
6011
|
-
return () => Deferred.from.Promise(new Promise(() => {
|
|
6012
|
-
}));
|
|
6013
|
-
}
|
|
6014
|
-
return fromPromise2((outerSignal) => {
|
|
6015
|
-
const controllers = tasks.map(() => new AbortController());
|
|
6016
|
-
const onOuterAbort = () => {
|
|
6017
|
-
for (const ctrl of controllers) {
|
|
6018
|
-
ctrl.abort();
|
|
6019
|
-
}
|
|
6020
|
-
};
|
|
6021
|
-
if (outerSignal) {
|
|
6022
|
-
if (outerSignal.aborted) {
|
|
6023
|
-
onOuterAbort();
|
|
6024
|
-
} else {
|
|
6025
|
-
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
6026
|
-
}
|
|
6027
|
-
}
|
|
6028
|
-
const promises = tasks.map((task, idx) => {
|
|
6029
|
-
const ctrl = controllers[idx];
|
|
6030
|
-
return toPromise2(task, ctrl.signal).then((result) => {
|
|
6031
|
-
for (let i = 0; i < controllers.length; i++) {
|
|
6032
|
-
if (i !== idx) {
|
|
6033
|
-
controllers[i].abort();
|
|
6034
|
-
}
|
|
6035
|
-
}
|
|
6036
|
-
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
6037
|
-
return result;
|
|
6038
|
-
});
|
|
6039
|
-
});
|
|
6040
|
-
return Promise.race(promises);
|
|
6041
|
-
});
|
|
6042
|
-
},
|
|
6205
|
+
mapError: (f) => (data) => Task.map(Validation.mapError(f))(data),
|
|
6043
6206
|
/**
|
|
6044
|
-
*
|
|
6045
|
-
* Forward-propagates the call site's AbortSignal to all subtasks concurrently.
|
|
6207
|
+
* Executes a side effect on the accumulated errors without changing the Task.Validation.
|
|
6046
6208
|
*
|
|
6047
6209
|
* @example
|
|
6048
6210
|
* ```ts
|
|
6049
|
-
*
|
|
6050
|
-
*
|
|
6211
|
+
* pipe(
|
|
6212
|
+
* Task.Validation.make.failed("invalid name"),
|
|
6213
|
+
* Task.Validation.tapError(errs => logger.error(errs))
|
|
6214
|
+
* );
|
|
6051
6215
|
* ```
|
|
6052
6216
|
*/
|
|
6053
|
-
|
|
6217
|
+
tapError: (f) => (data) => Task.map(Validation.tapError(f))(data),
|
|
6054
6218
|
/**
|
|
6055
|
-
*
|
|
6056
|
-
*
|
|
6219
|
+
* Combines a record of Task.Validations into a single Task.Validation of a record.
|
|
6220
|
+
* Evaluates fields in parallel and accumulates all validation errors.
|
|
6057
6221
|
*
|
|
6058
6222
|
* @example
|
|
6059
6223
|
* ```ts
|
|
6060
|
-
*
|
|
6061
|
-
*
|
|
6062
|
-
*
|
|
6063
|
-
*
|
|
6064
|
-
* // log = [1, 2, 3] — tasks ran in order
|
|
6224
|
+
* Task.Validation.struct({
|
|
6225
|
+
* name: Task.Validation.make.passed("Alice"),
|
|
6226
|
+
* age: Task.Validation.make.passed(30)
|
|
6227
|
+
* }); // Task.Validation({ name: "Alice", age: 30 })
|
|
6065
6228
|
* ```
|
|
6066
6229
|
*/
|
|
6067
|
-
|
|
6068
|
-
const
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6230
|
+
struct: (fields) => (signal) => Deferred.from.Promise((() => {
|
|
6231
|
+
const keys3 = Object.keys(fields);
|
|
6232
|
+
const promises = keys3.map((key) => Deferred.to.Promise(fields[key](signal)));
|
|
6233
|
+
return Promise.all(promises).then((results) => {
|
|
6234
|
+
const record = {};
|
|
6235
|
+
const errors = [];
|
|
6236
|
+
for (let i = 0; i < keys3.length; i++) {
|
|
6237
|
+
const res = results[i];
|
|
6238
|
+
if (Validation.is.passed(res)) {
|
|
6239
|
+
record[keys3[i]] = res.value;
|
|
6240
|
+
} else {
|
|
6241
|
+
errors.push(...res.errors);
|
|
6242
|
+
}
|
|
6072
6243
|
}
|
|
6073
|
-
|
|
6074
|
-
}
|
|
6075
|
-
|
|
6076
|
-
}),
|
|
6244
|
+
return isNonEmptyArr(errors) ? Validation.make.failedAll(errors) : Validation.make.passed(record);
|
|
6245
|
+
});
|
|
6246
|
+
})()),
|
|
6077
6247
|
/**
|
|
6078
|
-
*
|
|
6079
|
-
*
|
|
6080
|
-
* `AbortSignal` that fires when the deadline passes, so asynchronous operations
|
|
6081
|
-
* that accept a signal are cancelled rather than left dangling.
|
|
6248
|
+
* Creates a memoized version of a Task.Validation. The task is executed at most once on first call,
|
|
6249
|
+
* and its resolved Validation is cached for all subsequent calls.
|
|
6082
6250
|
*
|
|
6083
6251
|
* @example
|
|
6084
6252
|
* ```ts
|
|
6085
|
-
*
|
|
6086
|
-
* heavyComputation,
|
|
6087
|
-
* Task.timeout({ duration: Duration.seconds(5), onTimeout: () => "timed out" }),
|
|
6088
|
-
* Task.Result.chain(processResult)
|
|
6089
|
-
* );
|
|
6253
|
+
* const validate = Task.Validation.memoize(validateFormTask);
|
|
6090
6254
|
* ```
|
|
6091
6255
|
*/
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
cleanUp = () => {
|
|
6103
|
-
clearTimeout(timerId);
|
|
6104
|
-
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
6105
|
-
};
|
|
6106
|
-
if (outerSignal) {
|
|
6107
|
-
if (outerSignal.aborted) {
|
|
6108
|
-
controller.abort();
|
|
6109
|
-
} else {
|
|
6110
|
-
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
6111
|
-
}
|
|
6112
|
-
}
|
|
6113
|
-
return Promise.race([
|
|
6114
|
-
toPromise2(task, controller.signal).then((a) => {
|
|
6115
|
-
cleanUp();
|
|
6116
|
-
return Result.make.ok(a);
|
|
6117
|
-
}),
|
|
6118
|
-
new Promise((res) => {
|
|
6119
|
-
timerId = setTimeout(() => {
|
|
6120
|
-
controller.abort();
|
|
6121
|
-
cleanUp();
|
|
6122
|
-
res(Result.make.err(onTimeout()));
|
|
6123
|
-
}, getMs2(duration));
|
|
6124
|
-
})
|
|
6125
|
-
]);
|
|
6126
|
-
}),
|
|
6256
|
+
memoize: (task) => Task.memoize(task)
|
|
6257
|
+
};
|
|
6258
|
+
|
|
6259
|
+
// src/Core/Task.ts
|
|
6260
|
+
var toPromise2 = (task, signal) => Deferred.to.Promise(task(signal));
|
|
6261
|
+
var fromPromise2 = (f) => (signal) => Deferred.from.Promise(f(signal));
|
|
6262
|
+
var getMs2 = (duration) => Duration.to.milliseconds(duration);
|
|
6263
|
+
var resolveTask = (value) => () => Deferred.from.Promise(globalThis.Promise.resolve(value));
|
|
6264
|
+
var syncTask = (f) => () => Deferred.from.Promise(globalThis.Promise.resolve(f()));
|
|
6265
|
+
var Task = {
|
|
6127
6266
|
/**
|
|
6128
|
-
* Creates a Task
|
|
6129
|
-
* current in-flight call immediately. Unlike a one-shot abort, calling `task()`
|
|
6130
|
-
* again after `abort()` starts a fresh call with a new signal.
|
|
6131
|
-
*
|
|
6132
|
-
* Each invocation of `task()` automatically cancels the previous in-flight call,
|
|
6133
|
-
* making it safe to call repeatedly (e.g. on user input) without leaking promises.
|
|
6134
|
-
*
|
|
6135
|
-
* If an outer signal is also present (passed at the call site), aborting it
|
|
6136
|
-
* propagates into the internal controller.
|
|
6267
|
+
* Creates a Task that immediately resolves to the given value.
|
|
6137
6268
|
*
|
|
6138
6269
|
* @example
|
|
6139
6270
|
* ```ts
|
|
6140
|
-
* const
|
|
6141
|
-
*
|
|
6142
|
-
* );
|
|
6143
|
-
*
|
|
6144
|
-
* onUnmount(abort);
|
|
6145
|
-
* await poll();
|
|
6271
|
+
* const task = Task.resolve(42);
|
|
6272
|
+
* const value = await task(); // 42
|
|
6146
6273
|
* ```
|
|
6147
6274
|
*/
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
return Deferred.from.Promise(factory(controller.signal));
|
|
6163
|
-
};
|
|
6164
|
-
return { task, abort };
|
|
6275
|
+
resolve: resolveTask,
|
|
6276
|
+
// --- from ---
|
|
6277
|
+
from: {
|
|
6278
|
+
/**
|
|
6279
|
+
* Creates a Task from a lazy synchronous thunk.
|
|
6280
|
+
* Unlike `Task.resolve(f())`, `from.sync` does not evaluate `f` until the Task is called.
|
|
6281
|
+
*
|
|
6282
|
+
* @example
|
|
6283
|
+
* ```ts
|
|
6284
|
+
* const t = Task.from.sync(() => Date.now()); // Date.now() not called yet
|
|
6285
|
+
* const ts = await t(); // called here, every time
|
|
6286
|
+
* ```
|
|
6287
|
+
*/
|
|
6288
|
+
sync: syncTask
|
|
6165
6289
|
},
|
|
6166
6290
|
/**
|
|
6167
|
-
*
|
|
6291
|
+
* Wraps a Promise-returning thunk that may throw or reject,
|
|
6292
|
+
* trapping errors with a fallback function and returning a guaranteed `Task<A>`.
|
|
6168
6293
|
*
|
|
6169
6294
|
* @example
|
|
6170
6295
|
* ```ts
|
|
6171
|
-
* const
|
|
6172
|
-
*
|
|
6173
|
-
*
|
|
6174
|
-
* Task.run(),
|
|
6296
|
+
* const loadConfig = Task.tryCatch(
|
|
6297
|
+
* () => configStore.get("default"),
|
|
6298
|
+
* { onError: () => DEFAULT_CONFIG }
|
|
6175
6299
|
* );
|
|
6176
6300
|
* ```
|
|
6177
6301
|
*/
|
|
6178
|
-
|
|
6302
|
+
tryCatch: (f, options) => fromPromise2((signal) => globalThis.Promise.resolve().then(() => f(signal)).catch((err2) => options.onError(err2))),
|
|
6179
6303
|
/**
|
|
6180
|
-
*
|
|
6181
|
-
* Initiates the pipeline accumulator record.
|
|
6304
|
+
* Transforms the value inside a Task.
|
|
6182
6305
|
*
|
|
6183
6306
|
* @example
|
|
6184
6307
|
* ```ts
|
|
6185
|
-
* pipe(
|
|
6308
|
+
* pipe(
|
|
6309
|
+
* Task.resolve(5),
|
|
6310
|
+
* Task.map(n => n * 2)
|
|
6311
|
+
* )(); // Deferred<10>
|
|
6186
6312
|
* ```
|
|
6187
6313
|
*/
|
|
6188
|
-
|
|
6314
|
+
map: (f) => (data) => fromPromise2((signal) => toPromise2(data, signal).then(f)),
|
|
6189
6315
|
/**
|
|
6190
|
-
*
|
|
6316
|
+
* Chains Task computations. Passes the resolved value of the first Task to f.
|
|
6191
6317
|
*
|
|
6192
6318
|
* @example
|
|
6193
6319
|
* ```ts
|
|
6320
|
+
* const readUserId: Task<string> = Task.resolve(session.userId);
|
|
6321
|
+
* const loadPrefs = (id: string): Task<Preferences> =>
|
|
6322
|
+
* Task.resolve(prefsCache.get(id));
|
|
6323
|
+
*
|
|
6194
6324
|
* pipe(
|
|
6195
|
-
*
|
|
6196
|
-
* Task.
|
|
6197
|
-
* ); //
|
|
6325
|
+
* readUserId,
|
|
6326
|
+
* Task.chain(loadPrefs)
|
|
6327
|
+
* )(); // Deferred<Preferences>
|
|
6198
6328
|
* ```
|
|
6199
6329
|
*/
|
|
6200
|
-
|
|
6201
|
-
(signal) => toPromise2(data, signal).then(
|
|
6202
|
-
(a) => toPromise2(f(a), signal).then((b) => ({ ...a, [key]: b }))
|
|
6203
|
-
)
|
|
6204
|
-
),
|
|
6330
|
+
chain: (f) => (data) => fromPromise2((signal) => toPromise2(data, signal).then((a) => toPromise2(f(a), signal))),
|
|
6205
6331
|
/**
|
|
6206
|
-
*
|
|
6207
|
-
*
|
|
6332
|
+
* Applies a function wrapped in a Task to a value wrapped in a Task.
|
|
6333
|
+
* Both Tasks run in parallel.
|
|
6208
6334
|
*
|
|
6209
6335
|
* @example
|
|
6210
6336
|
* ```ts
|
|
6211
|
-
* const
|
|
6212
|
-
*
|
|
6213
|
-
*
|
|
6337
|
+
* const add = (a: number) => (b: number) => a + b;
|
|
6338
|
+
* pipe(
|
|
6339
|
+
* Task.resolve(add),
|
|
6340
|
+
* Task.ap(Task.resolve(5)),
|
|
6341
|
+
* Task.ap(Task.resolve(3))
|
|
6342
|
+
* )(); // Deferred<8>
|
|
6214
6343
|
* ```
|
|
6215
6344
|
*/
|
|
6216
|
-
|
|
6217
|
-
let cached = null;
|
|
6218
|
-
return (signal) => {
|
|
6219
|
-
if (cached === null) {
|
|
6220
|
-
cached = task(signal);
|
|
6221
|
-
}
|
|
6222
|
-
return cached;
|
|
6223
|
-
};
|
|
6224
|
-
},
|
|
6345
|
+
ap: (arg) => (data) => fromPromise2((signal) => Promise.all([toPromise2(data, signal), toPromise2(arg, signal)]).then(([f, a]) => f(a))),
|
|
6225
6346
|
/**
|
|
6226
|
-
*
|
|
6347
|
+
* Executes a side effect on the value without changing the Task.
|
|
6348
|
+
* Useful for logging or debugging.
|
|
6227
6349
|
*
|
|
6228
6350
|
* @example
|
|
6229
6351
|
* ```ts
|
|
6230
|
-
*
|
|
6231
|
-
*
|
|
6232
|
-
* Task.
|
|
6352
|
+
* pipe(
|
|
6353
|
+
* loadConfig,
|
|
6354
|
+
* Task.tap(cfg => console.log("Config:", cfg)),
|
|
6355
|
+
* Task.map(buildReport)
|
|
6233
6356
|
* );
|
|
6234
6357
|
* ```
|
|
6235
6358
|
*/
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
return res;
|
|
6243
|
-
})
|
|
6244
|
-
);
|
|
6245
|
-
},
|
|
6359
|
+
tap: (f) => (data) => fromPromise2(
|
|
6360
|
+
(signal) => toPromise2(data, signal).then((a) => {
|
|
6361
|
+
f(a);
|
|
6362
|
+
return a;
|
|
6363
|
+
})
|
|
6364
|
+
),
|
|
6246
6365
|
/**
|
|
6247
|
-
*
|
|
6366
|
+
* Runs multiple Tasks in parallel and collects their results.
|
|
6248
6367
|
*
|
|
6249
6368
|
* @example
|
|
6250
6369
|
* ```ts
|
|
6251
|
-
*
|
|
6252
|
-
*
|
|
6370
|
+
* Task.all([loadConfig, detectLocale, loadTheme])();
|
|
6371
|
+
* // Deferred<[Config, string, Theme]>
|
|
6253
6372
|
* ```
|
|
6254
6373
|
*/
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
return fn;
|
|
6259
|
-
},
|
|
6260
|
-
Maybe: TaskMaybe,
|
|
6261
|
-
Result: TaskResult,
|
|
6262
|
-
Validation: TaskValidation
|
|
6263
|
-
};
|
|
6264
|
-
|
|
6265
|
-
// src/Core/These.ts
|
|
6266
|
-
var makeFirst = (value) => ({ kind: "First", first: value });
|
|
6267
|
-
var makeSecond = (value) => ({ kind: "Second", second: value });
|
|
6268
|
-
var makeBoth = (f, s) => ({ kind: "Both", first: f, second: s });
|
|
6269
|
-
var isFirst = (data) => data.kind === "First";
|
|
6270
|
-
var isSecond = (data) => data.kind === "Second";
|
|
6271
|
-
var isBoth = (data) => data.kind === "Both";
|
|
6272
|
-
var hasFirst = (data) => data.kind === "First" || data.kind === "Both";
|
|
6273
|
-
var hasSecond = (data) => data.kind === "Second" || data.kind === "Both";
|
|
6274
|
-
var These = {
|
|
6275
|
-
make: {
|
|
6276
|
-
/**
|
|
6277
|
-
* Creates a These holding only a first value.
|
|
6278
|
-
*
|
|
6279
|
-
* @example
|
|
6280
|
-
* ```ts
|
|
6281
|
-
* These.make.first(42); // { kind: "First", first: 42 }
|
|
6282
|
-
* ```
|
|
6283
|
-
*/
|
|
6284
|
-
first: makeFirst,
|
|
6285
|
-
/**
|
|
6286
|
-
* Creates a These holding only a second value.
|
|
6287
|
-
*
|
|
6288
|
-
* @example
|
|
6289
|
-
* ```ts
|
|
6290
|
-
* These.make.second("warning"); // { kind: "Second", second: "warning" }
|
|
6291
|
-
* ```
|
|
6292
|
-
*/
|
|
6293
|
-
second: makeSecond,
|
|
6294
|
-
/**
|
|
6295
|
-
* Creates a These holding both a first and a second value simultaneously.
|
|
6296
|
-
*
|
|
6297
|
-
* @example
|
|
6298
|
-
* ```ts
|
|
6299
|
-
* These.make.both(42, "Deprecated API used"); // { kind: "Both", first: 42, second: "Deprecated API used" }
|
|
6300
|
-
* ```
|
|
6301
|
-
*/
|
|
6302
|
-
both: makeBoth
|
|
6303
|
-
},
|
|
6304
|
-
is: {
|
|
6305
|
-
/**
|
|
6306
|
-
* Type guard — checks if a These holds only a first value.
|
|
6307
|
-
*
|
|
6308
|
-
* @example
|
|
6309
|
-
* ```ts
|
|
6310
|
-
* const val = These.make.first(42);
|
|
6311
|
-
* if (These.is.first(val)) {
|
|
6312
|
-
* console.log(val.first); // 42
|
|
6313
|
-
* }
|
|
6314
|
-
* ```
|
|
6315
|
-
*/
|
|
6316
|
-
first: isFirst,
|
|
6317
|
-
/**
|
|
6318
|
-
* Type guard — checks if a These holds only a second value.
|
|
6319
|
-
*
|
|
6320
|
-
* @example
|
|
6321
|
-
* ```ts
|
|
6322
|
-
* const val = These.make.second("warning");
|
|
6323
|
-
* if (These.is.second(val)) {
|
|
6324
|
-
* console.log(val.second); // "warning"
|
|
6325
|
-
* }
|
|
6326
|
-
* ```
|
|
6327
|
-
*/
|
|
6328
|
-
second: isSecond,
|
|
6329
|
-
/**
|
|
6330
|
-
* Type guard — checks if a These holds both values simultaneously.
|
|
6331
|
-
*
|
|
6332
|
-
* @example
|
|
6333
|
-
* ```ts
|
|
6334
|
-
* const val = These.make.both(42, "warning");
|
|
6335
|
-
* if (These.is.both(val)) {
|
|
6336
|
-
* console.log(val.first, val.second); // 42 "warning"
|
|
6337
|
-
* }
|
|
6338
|
-
* ```
|
|
6339
|
-
*/
|
|
6340
|
-
both: isBoth
|
|
6341
|
-
},
|
|
6374
|
+
all: (tasks) => fromPromise2(
|
|
6375
|
+
(signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))
|
|
6376
|
+
),
|
|
6342
6377
|
/**
|
|
6343
|
-
*
|
|
6378
|
+
* Delays the execution of a Task by the specified duration.
|
|
6379
|
+
* Useful for debouncing or rate limiting.
|
|
6344
6380
|
*
|
|
6345
6381
|
* @example
|
|
6346
6382
|
* ```ts
|
|
6347
|
-
*
|
|
6348
|
-
*
|
|
6349
|
-
*
|
|
6383
|
+
* pipe(
|
|
6384
|
+
* Task.resolve(42),
|
|
6385
|
+
* Task.delay(Duration.seconds(1))
|
|
6386
|
+
* )(); // Resolves after 1 second
|
|
6350
6387
|
* ```
|
|
6351
6388
|
*/
|
|
6352
|
-
|
|
6389
|
+
delay: (duration) => (data) => fromPromise2(
|
|
6390
|
+
(signal) => new Promise((res) => {
|
|
6391
|
+
let timerId;
|
|
6392
|
+
const onAbort = () => {
|
|
6393
|
+
clearTimeout(timerId);
|
|
6394
|
+
res(toPromise2(data, signal));
|
|
6395
|
+
};
|
|
6396
|
+
if (signal) {
|
|
6397
|
+
if (signal.aborted) {
|
|
6398
|
+
return res(toPromise2(data, signal));
|
|
6399
|
+
}
|
|
6400
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6401
|
+
}
|
|
6402
|
+
timerId = setTimeout(() => {
|
|
6403
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6404
|
+
res(toPromise2(data, signal));
|
|
6405
|
+
}, getMs2(duration));
|
|
6406
|
+
})
|
|
6407
|
+
),
|
|
6408
|
+
/**
|
|
6409
|
+
* Runs a Task a fixed number of times sequentially, collecting all results into an array.
|
|
6410
|
+
* An optional delay duration can be inserted between runs.
|
|
6411
|
+
*
|
|
6412
|
+
* @example
|
|
6413
|
+
* ```ts
|
|
6414
|
+
* pipe(
|
|
6415
|
+
* pollSensor,
|
|
6416
|
+
* Task.repeat({ times: 5, delay: Duration.seconds(1) })
|
|
6417
|
+
* )(); // Task<Reading[]> — 5 readings, one per second
|
|
6418
|
+
* ```
|
|
6419
|
+
*/
|
|
6420
|
+
repeat: (options) => (task) => fromPromise2((signal) => {
|
|
6421
|
+
const { times, delay: delayDuration } = options;
|
|
6422
|
+
if (times <= 0) {
|
|
6423
|
+
return Promise.resolve([]);
|
|
6424
|
+
}
|
|
6425
|
+
const results = [];
|
|
6426
|
+
const wait = () => new Promise((r) => {
|
|
6427
|
+
let timerId;
|
|
6428
|
+
const onAbort = () => {
|
|
6429
|
+
clearTimeout(timerId);
|
|
6430
|
+
r();
|
|
6431
|
+
};
|
|
6432
|
+
if (signal) {
|
|
6433
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6434
|
+
}
|
|
6435
|
+
timerId = setTimeout(() => {
|
|
6436
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6437
|
+
r();
|
|
6438
|
+
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
6439
|
+
});
|
|
6440
|
+
const run = (left) => {
|
|
6441
|
+
if (signal?.aborted) {
|
|
6442
|
+
return Promise.resolve(results);
|
|
6443
|
+
}
|
|
6444
|
+
return toPromise2(task, signal).then((a) => {
|
|
6445
|
+
results.push(a);
|
|
6446
|
+
if (left <= 1 || signal?.aborted) {
|
|
6447
|
+
return results;
|
|
6448
|
+
}
|
|
6449
|
+
return wait().then(() => run(left - 1));
|
|
6450
|
+
});
|
|
6451
|
+
};
|
|
6452
|
+
return run(times);
|
|
6453
|
+
}),
|
|
6353
6454
|
/**
|
|
6354
|
-
*
|
|
6455
|
+
* Runs a Task repeatedly until the result satisfies a predicate, returning that result.
|
|
6456
|
+
* An optional delay duration can be inserted between runs.
|
|
6457
|
+
* An optional `maxAttempts` cap stops the loop after N calls — the last value is returned
|
|
6458
|
+
* regardless of whether the predicate was satisfied.
|
|
6355
6459
|
*
|
|
6356
6460
|
* @example
|
|
6357
6461
|
* ```ts
|
|
6358
|
-
*
|
|
6359
|
-
*
|
|
6360
|
-
*
|
|
6462
|
+
* pipe(
|
|
6463
|
+
* checkStatus,
|
|
6464
|
+
* Task.repeatUntil({ when: (s) => s === "ready", delay: Duration.milliseconds(500) })
|
|
6465
|
+
* )(); // polls every 500ms until status is "ready"
|
|
6361
6466
|
* ```
|
|
6362
6467
|
*/
|
|
6363
|
-
|
|
6468
|
+
repeatUntil: (options) => (task) => fromPromise2((signal) => {
|
|
6469
|
+
const { when: predicate, delay: delayDuration, maxAttempts } = options;
|
|
6470
|
+
const wait = () => new Promise((r) => {
|
|
6471
|
+
let timerId;
|
|
6472
|
+
const onAbort = () => {
|
|
6473
|
+
clearTimeout(timerId);
|
|
6474
|
+
r();
|
|
6475
|
+
};
|
|
6476
|
+
if (signal) {
|
|
6477
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6478
|
+
}
|
|
6479
|
+
timerId = setTimeout(() => {
|
|
6480
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6481
|
+
r();
|
|
6482
|
+
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
6483
|
+
});
|
|
6484
|
+
const run = (attempt, lastValue) => {
|
|
6485
|
+
if (signal?.aborted && lastValue !== void 0) {
|
|
6486
|
+
return Promise.resolve(lastValue);
|
|
6487
|
+
}
|
|
6488
|
+
return toPromise2(task, signal).then((a) => {
|
|
6489
|
+
if (predicate(a)) {
|
|
6490
|
+
return a;
|
|
6491
|
+
}
|
|
6492
|
+
if (maxAttempts !== void 0 && attempt >= maxAttempts) {
|
|
6493
|
+
return a;
|
|
6494
|
+
}
|
|
6495
|
+
if (signal?.aborted) {
|
|
6496
|
+
return a;
|
|
6497
|
+
}
|
|
6498
|
+
return wait().then(() => run(attempt + 1, a));
|
|
6499
|
+
});
|
|
6500
|
+
};
|
|
6501
|
+
return run(1);
|
|
6502
|
+
}),
|
|
6364
6503
|
/**
|
|
6365
|
-
*
|
|
6504
|
+
* Resolves with the value of the first Task to complete. All Tasks start
|
|
6505
|
+
* immediately. When one resolves, the other tasks are cancelled (aborted)
|
|
6506
|
+
* downstream.
|
|
6366
6507
|
*
|
|
6367
6508
|
* @example
|
|
6368
6509
|
* ```ts
|
|
6369
|
-
*
|
|
6370
|
-
*
|
|
6371
|
-
*
|
|
6510
|
+
* const fast = Task.resolve("fast");
|
|
6511
|
+
* const slow = Task.delay(Duration.milliseconds(200))(Task.resolve("slow"));
|
|
6512
|
+
*
|
|
6513
|
+
* await Task.race([fast, slow])(); // "fast"
|
|
6372
6514
|
* ```
|
|
6373
6515
|
*/
|
|
6374
|
-
|
|
6375
|
-
if (
|
|
6376
|
-
return
|
|
6377
|
-
|
|
6378
|
-
if (isFirst(data)) {
|
|
6379
|
-
return makeFirst(f(data.first));
|
|
6516
|
+
race: (tasks) => {
|
|
6517
|
+
if (tasks.length === 0) {
|
|
6518
|
+
return () => Deferred.from.Promise(new Promise(() => {
|
|
6519
|
+
}));
|
|
6380
6520
|
}
|
|
6381
|
-
return
|
|
6521
|
+
return fromPromise2((outerSignal) => {
|
|
6522
|
+
const controllers = tasks.map(() => new AbortController());
|
|
6523
|
+
const onOuterAbort = () => {
|
|
6524
|
+
for (const ctrl of controllers) {
|
|
6525
|
+
ctrl.abort();
|
|
6526
|
+
}
|
|
6527
|
+
};
|
|
6528
|
+
if (outerSignal) {
|
|
6529
|
+
if (outerSignal.aborted) {
|
|
6530
|
+
onOuterAbort();
|
|
6531
|
+
} else {
|
|
6532
|
+
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
6533
|
+
}
|
|
6534
|
+
}
|
|
6535
|
+
const promises = tasks.map((task, idx) => {
|
|
6536
|
+
const ctrl = controllers[idx];
|
|
6537
|
+
return toPromise2(task, ctrl.signal).then((result) => {
|
|
6538
|
+
for (let i = 0; i < controllers.length; i++) {
|
|
6539
|
+
if (i !== idx) {
|
|
6540
|
+
controllers[i].abort();
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
6544
|
+
return result;
|
|
6545
|
+
});
|
|
6546
|
+
});
|
|
6547
|
+
return Promise.race(promises);
|
|
6548
|
+
});
|
|
6382
6549
|
},
|
|
6383
6550
|
/**
|
|
6384
|
-
*
|
|
6551
|
+
* Runs an array of Tasks concurrently and collects their results in an array.
|
|
6552
|
+
* Forward-propagates the call site's AbortSignal to all subtasks concurrently.
|
|
6385
6553
|
*
|
|
6386
6554
|
* @example
|
|
6387
6555
|
* ```ts
|
|
6388
|
-
*
|
|
6389
|
-
*
|
|
6556
|
+
* Task.sequence([loadConfig, detectLocale, loadTheme])();
|
|
6557
|
+
* // Deferred<[Config, string, Theme]>
|
|
6390
6558
|
* ```
|
|
6391
6559
|
*/
|
|
6392
|
-
|
|
6393
|
-
if (isFirst(data)) {
|
|
6394
|
-
return data;
|
|
6395
|
-
}
|
|
6396
|
-
if (isSecond(data)) {
|
|
6397
|
-
return makeSecond(f(data.second));
|
|
6398
|
-
}
|
|
6399
|
-
return makeBoth(data.first, f(data.second));
|
|
6400
|
-
},
|
|
6560
|
+
sequence: (tasks) => fromPromise2((signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))),
|
|
6401
6561
|
/**
|
|
6402
|
-
*
|
|
6562
|
+
* Runs an array of Tasks one at a time in order, collecting all results.
|
|
6563
|
+
* Each Task starts only after the previous one resolves.
|
|
6403
6564
|
*
|
|
6404
6565
|
* @example
|
|
6405
6566
|
* ```ts
|
|
6406
|
-
*
|
|
6407
|
-
*
|
|
6408
|
-
*
|
|
6409
|
-
* )
|
|
6567
|
+
* let log: number[] = [];
|
|
6568
|
+
* const makeTask = (n: number) => Task.resolve(n);
|
|
6569
|
+
*
|
|
6570
|
+
* await Task.sequential([makeTask(1), makeTask(2), makeTask(3)])();
|
|
6571
|
+
* // log = [1, 2, 3] — tasks ran in order
|
|
6410
6572
|
* ```
|
|
6411
6573
|
*/
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6574
|
+
sequential: (tasks) => fromPromise2(async (signal) => {
|
|
6575
|
+
const results = [];
|
|
6576
|
+
for (const task of tasks) {
|
|
6577
|
+
if (signal?.aborted) {
|
|
6578
|
+
break;
|
|
6579
|
+
}
|
|
6580
|
+
results.push(await toPromise2(task, signal));
|
|
6418
6581
|
}
|
|
6419
|
-
return
|
|
6420
|
-
},
|
|
6582
|
+
return results;
|
|
6583
|
+
}),
|
|
6421
6584
|
/**
|
|
6422
|
-
*
|
|
6423
|
-
*
|
|
6585
|
+
* Converts a `Task<A>` into a `Task<Result<E, A>>`, resolving to `Err` if the
|
|
6586
|
+
* Task does not complete within the given duration. The inner Task receives an
|
|
6587
|
+
* `AbortSignal` that fires when the deadline passes, so asynchronous operations
|
|
6588
|
+
* that accept a signal are cancelled rather than left dangling.
|
|
6424
6589
|
*
|
|
6425
6590
|
* @example
|
|
6426
6591
|
* ```ts
|
|
6427
|
-
*
|
|
6428
|
-
*
|
|
6429
|
-
*
|
|
6430
|
-
*
|
|
6431
|
-
*
|
|
6592
|
+
* pipe(
|
|
6593
|
+
* heavyComputation,
|
|
6594
|
+
* Task.timeout({ duration: Duration.seconds(5), onTimeout: () => "timed out" }),
|
|
6595
|
+
* Task.Result.chain(processResult)
|
|
6596
|
+
* );
|
|
6432
6597
|
* ```
|
|
6433
6598
|
*/
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6599
|
+
timeout: (options) => (task) => fromPromise2((outerSignal) => {
|
|
6600
|
+
const { duration, onTimeout } = options;
|
|
6601
|
+
const controller = new AbortController();
|
|
6602
|
+
let timerId;
|
|
6603
|
+
let cleanUp = () => {
|
|
6604
|
+
};
|
|
6605
|
+
const onOuterAbort = () => {
|
|
6606
|
+
cleanUp();
|
|
6607
|
+
controller.abort();
|
|
6608
|
+
};
|
|
6609
|
+
cleanUp = () => {
|
|
6610
|
+
clearTimeout(timerId);
|
|
6611
|
+
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
6612
|
+
};
|
|
6613
|
+
if (outerSignal) {
|
|
6614
|
+
if (outerSignal.aborted) {
|
|
6615
|
+
controller.abort();
|
|
6616
|
+
} else {
|
|
6617
|
+
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
6618
|
+
}
|
|
6437
6619
|
}
|
|
6438
|
-
return
|
|
6439
|
-
|
|
6620
|
+
return Promise.race([
|
|
6621
|
+
toPromise2(task, controller.signal).then((a) => {
|
|
6622
|
+
cleanUp();
|
|
6623
|
+
return Result.make.ok(a);
|
|
6624
|
+
}),
|
|
6625
|
+
new Promise((res) => {
|
|
6626
|
+
timerId = setTimeout(() => {
|
|
6627
|
+
controller.abort();
|
|
6628
|
+
cleanUp();
|
|
6629
|
+
res(Result.make.err(onTimeout()));
|
|
6630
|
+
}, getMs2(duration));
|
|
6631
|
+
})
|
|
6632
|
+
]);
|
|
6633
|
+
}),
|
|
6440
6634
|
/**
|
|
6441
|
-
*
|
|
6442
|
-
*
|
|
6635
|
+
* Creates a Task paired with an `abort` handle. Calling `abort()` cancels the
|
|
6636
|
+
* current in-flight call immediately. Unlike a one-shot abort, calling `task()`
|
|
6637
|
+
* again after `abort()` starts a fresh call with a new signal.
|
|
6638
|
+
*
|
|
6639
|
+
* Each invocation of `task()` automatically cancels the previous in-flight call,
|
|
6640
|
+
* making it safe to call repeatedly (e.g. on user input) without leaking promises.
|
|
6641
|
+
*
|
|
6642
|
+
* If an outer signal is also present (passed at the call site), aborting it
|
|
6643
|
+
* propagates into the internal controller.
|
|
6443
6644
|
*
|
|
6444
6645
|
* @example
|
|
6445
6646
|
* ```ts
|
|
6446
|
-
* const
|
|
6647
|
+
* const { task: poll, abort } = Task.abortable(
|
|
6648
|
+
* (signal) => waitForEvent(bus, "ready", { signal }),
|
|
6649
|
+
* );
|
|
6447
6650
|
*
|
|
6448
|
-
*
|
|
6449
|
-
*
|
|
6450
|
-
* pipe(These.make.first(5), These.chainSecond(shout)); // First(5)
|
|
6651
|
+
* onUnmount(abort);
|
|
6652
|
+
* await poll();
|
|
6451
6653
|
* ```
|
|
6452
6654
|
*/
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6655
|
+
abortable: (factory) => {
|
|
6656
|
+
let currentController = null;
|
|
6657
|
+
const abort = () => currentController?.abort();
|
|
6658
|
+
const task = (outerSignal) => {
|
|
6659
|
+
currentController?.abort();
|
|
6660
|
+
currentController = new AbortController();
|
|
6661
|
+
const controller = currentController;
|
|
6662
|
+
if (outerSignal) {
|
|
6663
|
+
if (outerSignal.aborted) {
|
|
6664
|
+
controller.abort(outerSignal.reason);
|
|
6665
|
+
} else {
|
|
6666
|
+
outerSignal.addEventListener("abort", () => controller.abort(outerSignal.reason), { once: true });
|
|
6667
|
+
}
|
|
6668
|
+
}
|
|
6669
|
+
return Deferred.from.Promise(factory(controller.signal));
|
|
6670
|
+
};
|
|
6671
|
+
return { task, abort };
|
|
6458
6672
|
},
|
|
6459
6673
|
/**
|
|
6460
|
-
*
|
|
6674
|
+
* Executes a task with an optional signal. Use as a terminal step in a `pipe` chain.
|
|
6461
6675
|
*
|
|
6462
6676
|
* @example
|
|
6463
6677
|
* ```ts
|
|
6464
|
-
* pipe(
|
|
6465
|
-
*
|
|
6466
|
-
*
|
|
6467
|
-
*
|
|
6468
|
-
* b => `Second: ${b}`,
|
|
6469
|
-
* (a, b) => `Both: ${a} / ${b}`
|
|
6470
|
-
* )
|
|
6678
|
+
* const name = await pipe(
|
|
6679
|
+
* loadConfig,
|
|
6680
|
+
* Task.map(config => config.name),
|
|
6681
|
+
* Task.run(),
|
|
6471
6682
|
* );
|
|
6472
6683
|
* ```
|
|
6473
6684
|
*/
|
|
6474
|
-
|
|
6475
|
-
if (isSecond(data)) {
|
|
6476
|
-
return onSecond(data.second);
|
|
6477
|
-
}
|
|
6478
|
-
if (isFirst(data)) {
|
|
6479
|
-
return onFirst(data.first);
|
|
6480
|
-
}
|
|
6481
|
-
return onBoth(data.first, data.second);
|
|
6482
|
-
},
|
|
6685
|
+
run: (signal) => (task) => task(signal),
|
|
6483
6686
|
/**
|
|
6484
|
-
*
|
|
6687
|
+
* Converts a Task value into an object containing a single property.
|
|
6688
|
+
* Initiates the pipeline accumulator record.
|
|
6485
6689
|
*
|
|
6486
6690
|
* @example
|
|
6487
6691
|
* ```ts
|
|
6488
|
-
* pipe(
|
|
6489
|
-
* these,
|
|
6490
|
-
* These.match({
|
|
6491
|
-
* first: a => `First: ${a}`,
|
|
6492
|
-
* second: b => `Second: ${b}`,
|
|
6493
|
-
* both: (a, b) => `Both: ${a} / ${b}`
|
|
6494
|
-
* })
|
|
6495
|
-
* );
|
|
6692
|
+
* pipe(Task.resolve(42), Task.bindTo("value")); // Task({ value: 42 })
|
|
6496
6693
|
* ```
|
|
6497
6694
|
*/
|
|
6498
|
-
|
|
6499
|
-
if (isSecond(data)) {
|
|
6500
|
-
return cases.second(data.second);
|
|
6501
|
-
}
|
|
6502
|
-
if (isFirst(data)) {
|
|
6503
|
-
return cases.first(data.first);
|
|
6504
|
-
}
|
|
6505
|
-
return cases.both(data.first, data.second);
|
|
6506
|
-
},
|
|
6695
|
+
bindTo: (key) => (data) => fromPromise2((signal) => toPromise2(data, signal).then((a) => ({ [key]: a }))),
|
|
6507
6696
|
/**
|
|
6508
|
-
*
|
|
6509
|
-
* The default can be a different type, widening the result to `A | C`.
|
|
6697
|
+
* Evaluates a new Task using the current accumulator and attaches the output to a new key.
|
|
6510
6698
|
*
|
|
6511
6699
|
* @example
|
|
6512
6700
|
* ```ts
|
|
6513
|
-
* pipe(
|
|
6514
|
-
*
|
|
6515
|
-
*
|
|
6516
|
-
*
|
|
6701
|
+
* pipe(
|
|
6702
|
+
* Task.resolve({ a: 1 }),
|
|
6703
|
+
* Task.bind("b", ({ a }) => Task.resolve(a + 1))
|
|
6704
|
+
* ); // Task({ a: 1, b: 2 })
|
|
6517
6705
|
* ```
|
|
6518
6706
|
*/
|
|
6519
|
-
|
|
6707
|
+
bind: (key, f) => (data) => fromPromise2(
|
|
6708
|
+
(signal) => toPromise2(data, signal).then(
|
|
6709
|
+
(a) => toPromise2(f(a), signal).then((b) => ({ ...a, [key]: b }))
|
|
6710
|
+
)
|
|
6711
|
+
),
|
|
6520
6712
|
/**
|
|
6521
|
-
*
|
|
6522
|
-
*
|
|
6713
|
+
* Creates a memoized version of a Task. The task is executed at most once on first call,
|
|
6714
|
+
* and its resolved value is cached for all subsequent calls.
|
|
6523
6715
|
*
|
|
6524
6716
|
* @example
|
|
6525
6717
|
* ```ts
|
|
6526
|
-
*
|
|
6527
|
-
*
|
|
6528
|
-
*
|
|
6529
|
-
* pipe(These.make.first(5), These.getSecondOrElse(() => null)); // null — typed as string | null
|
|
6718
|
+
* const loadToken = Task.memoize(loadAuthToken);
|
|
6719
|
+
* const token1 = await loadToken(); // loads token
|
|
6720
|
+
* const token2 = await loadToken(); // returns cached token immediately
|
|
6530
6721
|
* ```
|
|
6531
6722
|
*/
|
|
6532
|
-
|
|
6723
|
+
memoize: (task) => {
|
|
6724
|
+
let cached = null;
|
|
6725
|
+
return (signal) => {
|
|
6726
|
+
if (cached === null) {
|
|
6727
|
+
cached = task(signal);
|
|
6728
|
+
}
|
|
6729
|
+
return cached;
|
|
6730
|
+
};
|
|
6731
|
+
},
|
|
6533
6732
|
/**
|
|
6534
|
-
*
|
|
6535
|
-
* Useful for logging or debugging.
|
|
6733
|
+
* Monitors progress of a Task by calling `onProgress(0)` before execution and `onProgress(1)` upon completion.
|
|
6536
6734
|
*
|
|
6537
6735
|
* @example
|
|
6538
6736
|
* ```ts
|
|
6539
|
-
*
|
|
6737
|
+
* const taskWithProgress = pipe(
|
|
6738
|
+
* readTask,
|
|
6739
|
+
* Task.withProgress((ratio) => console.log(`Progress: ${ratio * 100}%`))
|
|
6740
|
+
* );
|
|
6540
6741
|
* ```
|
|
6541
6742
|
*/
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6743
|
+
withProgress: (onProgress) => (task) => (signal) => {
|
|
6744
|
+
onProgress(0);
|
|
6745
|
+
const d = task(signal);
|
|
6746
|
+
return Deferred.from.Promise(
|
|
6747
|
+
Deferred.to.Promise(d).then((res) => {
|
|
6748
|
+
onProgress(1);
|
|
6749
|
+
return res;
|
|
6750
|
+
})
|
|
6751
|
+
);
|
|
6547
6752
|
},
|
|
6548
6753
|
/**
|
|
6549
|
-
*
|
|
6550
|
-
* - First(a) → Second(a)
|
|
6551
|
-
* - Second(b) → First(b)
|
|
6552
|
-
* - Both(a, b) → Both(b, a)
|
|
6754
|
+
* Attaches a read-only `.label` property to a Task, preserving the literal string generic type for IDE tooltips.
|
|
6553
6755
|
*
|
|
6554
6756
|
* @example
|
|
6555
6757
|
* ```ts
|
|
6556
|
-
*
|
|
6557
|
-
*
|
|
6558
|
-
* These.swap(These.make.both(5, "warn")); // Both("warn", 5)
|
|
6758
|
+
* const labeledTask = pipe(readTask, Task.withLabel("readUser"));
|
|
6759
|
+
* console.log(labeledTask.label); // "readUser"
|
|
6559
6760
|
* ```
|
|
6560
6761
|
*/
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
}
|
|
6762
|
+
withLabel: (label) => (task) => {
|
|
6763
|
+
const fn = ((signal) => task(signal));
|
|
6764
|
+
Object.defineProperty(fn, "label", { value: label, writable: false, enumerable: true, configurable: true });
|
|
6765
|
+
return fn;
|
|
6766
|
+
},
|
|
6767
|
+
Maybe: TaskMaybe,
|
|
6768
|
+
Result: TaskResult,
|
|
6769
|
+
Validation: TaskValidation
|
|
6570
6770
|
};
|
|
6571
6771
|
|
|
6572
|
-
// src/Core/
|
|
6573
|
-
var
|
|
6574
|
-
var
|
|
6575
|
-
var
|
|
6576
|
-
var
|
|
6577
|
-
var
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
}
|
|
6583
|
-
return isPassed(arg) ? Result.make.ok(arg.value) : Result.make.err(arg.errors);
|
|
6584
|
-
}
|
|
6585
|
-
var Validation = {
|
|
6772
|
+
// src/Core/These.ts
|
|
6773
|
+
var makeFirst = (value) => ({ kind: "First", first: value });
|
|
6774
|
+
var makeSecond = (value) => ({ kind: "Second", second: value });
|
|
6775
|
+
var makeBoth = (f, s) => ({ kind: "Both", first: f, second: s });
|
|
6776
|
+
var isFirst = (data) => data.kind === "First";
|
|
6777
|
+
var isSecond = (data) => data.kind === "Second";
|
|
6778
|
+
var isBoth = (data) => data.kind === "Both";
|
|
6779
|
+
var hasFirst = (data) => data.kind === "First" || data.kind === "Both";
|
|
6780
|
+
var hasSecond = (data) => data.kind === "Second" || data.kind === "Both";
|
|
6781
|
+
var These = {
|
|
6586
6782
|
make: {
|
|
6587
6783
|
/**
|
|
6588
|
-
*
|
|
6784
|
+
* Creates a These holding only a first value.
|
|
6589
6785
|
*
|
|
6590
6786
|
* @example
|
|
6591
6787
|
* ```ts
|
|
6592
|
-
*
|
|
6788
|
+
* These.make.first(42); // { kind: "First", first: 42 }
|
|
6593
6789
|
* ```
|
|
6594
6790
|
*/
|
|
6595
|
-
|
|
6791
|
+
first: makeFirst,
|
|
6596
6792
|
/**
|
|
6597
|
-
* Creates a
|
|
6793
|
+
* Creates a These holding only a second value.
|
|
6598
6794
|
*
|
|
6599
6795
|
* @example
|
|
6600
6796
|
* ```ts
|
|
6601
|
-
*
|
|
6797
|
+
* These.make.second("warning"); // { kind: "Second", second: "warning" }
|
|
6602
6798
|
* ```
|
|
6603
6799
|
*/
|
|
6604
|
-
|
|
6800
|
+
second: makeSecond,
|
|
6605
6801
|
/**
|
|
6606
|
-
* Creates a
|
|
6802
|
+
* Creates a These holding both a first and a second value simultaneously.
|
|
6607
6803
|
*
|
|
6608
6804
|
* @example
|
|
6609
6805
|
* ```ts
|
|
6610
|
-
*
|
|
6806
|
+
* These.make.both(42, "Deprecated API used"); // { kind: "Both", first: 42, second: "Deprecated API used" }
|
|
6611
6807
|
* ```
|
|
6612
6808
|
*/
|
|
6613
|
-
|
|
6809
|
+
both: makeBoth
|
|
6614
6810
|
},
|
|
6615
6811
|
is: {
|
|
6616
6812
|
/**
|
|
6617
|
-
* Type guard
|
|
6813
|
+
* Type guard — checks if a These holds only a first value.
|
|
6618
6814
|
*
|
|
6619
6815
|
* @example
|
|
6620
6816
|
* ```ts
|
|
6621
|
-
* const
|
|
6622
|
-
* if (
|
|
6623
|
-
* console.log(
|
|
6817
|
+
* const val = These.make.first(42);
|
|
6818
|
+
* if (These.is.first(val)) {
|
|
6819
|
+
* console.log(val.first); // 42
|
|
6624
6820
|
* }
|
|
6625
6821
|
* ```
|
|
6626
6822
|
*/
|
|
6627
|
-
|
|
6823
|
+
first: isFirst,
|
|
6628
6824
|
/**
|
|
6629
|
-
* Type guard
|
|
6825
|
+
* Type guard — checks if a These holds only a second value.
|
|
6630
6826
|
*
|
|
6631
6827
|
* @example
|
|
6632
6828
|
* ```ts
|
|
6633
|
-
* const
|
|
6634
|
-
* if (
|
|
6635
|
-
* console.log(
|
|
6829
|
+
* const val = These.make.second("warning");
|
|
6830
|
+
* if (These.is.second(val)) {
|
|
6831
|
+
* console.log(val.second); // "warning"
|
|
6636
6832
|
* }
|
|
6637
6833
|
* ```
|
|
6638
6834
|
*/
|
|
6639
|
-
|
|
6640
|
-
},
|
|
6641
|
-
/**
|
|
6642
|
-
* Creates a Validation from a synchronous thunk that may throw.
|
|
6643
|
-
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
6644
|
-
*
|
|
6645
|
-
* @example
|
|
6646
|
-
* ```ts
|
|
6647
|
-
* const result = Validation.tryCatch(
|
|
6648
|
-
* () => JSON.parse(rawString),
|
|
6649
|
-
* { onError: (e) => `Parse error: ${e}` }
|
|
6650
|
-
* );
|
|
6651
|
-
* ```
|
|
6652
|
-
*/
|
|
6653
|
-
tryCatch: (f, options) => {
|
|
6654
|
-
try {
|
|
6655
|
-
return makePassed2(f());
|
|
6656
|
-
} catch (error) {
|
|
6657
|
-
return makeFailed2(options.onError(error));
|
|
6658
|
-
}
|
|
6659
|
-
},
|
|
6660
|
-
// --- from ---
|
|
6661
|
-
from: {
|
|
6662
|
-
/**
|
|
6663
|
-
* Creates a Validation from a predicate applied to a value.
|
|
6664
|
-
* Returns Passed if the predicate passes, Failed from `onFalse` otherwise.
|
|
6665
|
-
*
|
|
6666
|
-
* @example
|
|
6667
|
-
* ```ts
|
|
6668
|
-
* const validateName = Validation.from.Predicate(
|
|
6669
|
-
* (s: string) => s.length > 0,
|
|
6670
|
-
* () => "Name is required"
|
|
6671
|
-
* );
|
|
6672
|
-
*
|
|
6673
|
-
* validateName("Alice"); // Passed("Alice")
|
|
6674
|
-
* validateName(""); // Failed(["Name is required"])
|
|
6675
|
-
* ```
|
|
6676
|
-
*/
|
|
6677
|
-
Predicate: (pred, onFalse) => (a) => pred(a) ? makePassed2(a) : makeFailed2(onFalse(a)),
|
|
6678
|
-
/**
|
|
6679
|
-
* Creates a Validation from a nullable value.
|
|
6680
|
-
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
6681
|
-
* Otherwise, returns Passed.
|
|
6682
|
-
*
|
|
6683
|
-
* @example
|
|
6684
|
-
* ```ts
|
|
6685
|
-
* pipe(null, Validation.from.nullable(() => "is null")); // Failed(["is null"])
|
|
6686
|
-
* pipe(42, Validation.from.nullable(() => "is null")); // Passed(42)
|
|
6687
|
-
* ```
|
|
6688
|
-
*/
|
|
6689
|
-
nullable: (onNull) => (value) => value === null || value === void 0 ? makeFailed2(onNull()) : makePassed2(value),
|
|
6690
|
-
/**
|
|
6691
|
-
* Creates a Validation from a Maybe.
|
|
6692
|
-
* If the Maybe is None, returns Failed with the error from onNone.
|
|
6693
|
-
* Otherwise, returns Passed.
|
|
6694
|
-
*
|
|
6695
|
-
* @example
|
|
6696
|
-
* ```ts
|
|
6697
|
-
* pipe(Maybe.make.none(), Validation.from.Maybe(() => "is none")); // Failed(["is none"])
|
|
6698
|
-
* pipe(Maybe.make.some(42), Validation.from.Maybe(() => "is none")); // Passed(42)
|
|
6699
|
-
* ```
|
|
6700
|
-
*/
|
|
6701
|
-
Maybe: (onNone) => (maybe) => Maybe.is.none(maybe) ? makeFailed2(onNone()) : makePassed2(maybe.value),
|
|
6835
|
+
second: isSecond,
|
|
6702
6836
|
/**
|
|
6703
|
-
*
|
|
6704
|
-
*
|
|
6705
|
-
* Useful when bridging from error-short-circuiting `Result` pipelines into
|
|
6706
|
-
* error-accumulating `Validation` pipelines.
|
|
6837
|
+
* Type guard — checks if a These holds both values simultaneously.
|
|
6707
6838
|
*
|
|
6708
6839
|
* @example
|
|
6709
6840
|
* ```ts
|
|
6710
|
-
*
|
|
6711
|
-
*
|
|
6841
|
+
* const val = These.make.both(42, "warning");
|
|
6842
|
+
* if (These.is.both(val)) {
|
|
6843
|
+
* console.log(val.first, val.second); // 42 "warning"
|
|
6844
|
+
* }
|
|
6712
6845
|
* ```
|
|
6713
6846
|
*/
|
|
6714
|
-
|
|
6847
|
+
both: isBoth
|
|
6715
6848
|
},
|
|
6716
6849
|
/**
|
|
6717
|
-
*
|
|
6850
|
+
* Returns true if the These contains a first value (First or Both).
|
|
6718
6851
|
*
|
|
6719
6852
|
* @example
|
|
6720
6853
|
* ```ts
|
|
6721
|
-
*
|
|
6722
|
-
*
|
|
6854
|
+
* These.hasFirst(These.make.first(42)); // true
|
|
6855
|
+
* These.hasFirst(These.make.both(42, "warn"));// true
|
|
6856
|
+
* These.hasFirst(These.make.second("warn")); // false
|
|
6723
6857
|
* ```
|
|
6724
6858
|
*/
|
|
6725
|
-
|
|
6859
|
+
hasFirst,
|
|
6726
6860
|
/**
|
|
6727
|
-
*
|
|
6861
|
+
* Returns true if the These contains a second value (Second or Both).
|
|
6728
6862
|
*
|
|
6729
6863
|
* @example
|
|
6730
6864
|
* ```ts
|
|
6731
|
-
*
|
|
6865
|
+
* These.hasSecond(These.make.second("warn")); // true
|
|
6866
|
+
* These.hasSecond(These.make.both(42, "warn"));// true
|
|
6867
|
+
* These.hasSecond(These.make.first(42)); // false
|
|
6732
6868
|
* ```
|
|
6733
6869
|
*/
|
|
6734
|
-
|
|
6870
|
+
hasSecond,
|
|
6735
6871
|
/**
|
|
6736
|
-
*
|
|
6737
|
-
* Accumulates errors from both sides.
|
|
6872
|
+
* Transforms the first value, leaving the second unchanged.
|
|
6738
6873
|
*
|
|
6739
6874
|
* @example
|
|
6740
6875
|
* ```ts
|
|
6741
|
-
*
|
|
6742
|
-
* pipe(
|
|
6743
|
-
*
|
|
6744
|
-
* Validation.ap(Validation.make.passed(5)),
|
|
6745
|
-
* Validation.ap(Validation.make.passed(3))
|
|
6746
|
-
* ); // Passed(8)
|
|
6747
|
-
*
|
|
6748
|
-
* pipe(
|
|
6749
|
-
* Validation.make.passed(add),
|
|
6750
|
-
* Validation.ap(Validation.make.failed<string>("bad a")),
|
|
6751
|
-
* Validation.ap(Validation.make.failed<string>("bad b"))
|
|
6752
|
-
* ); // Failed(["bad a", "bad b"])
|
|
6876
|
+
* pipe(These.make.first(5), These.mapFirst(n => n * 2)); // First(10)
|
|
6877
|
+
* pipe(These.make.both(5, "warn"), These.mapFirst(n => n * 2)); // Both(10, "warn")
|
|
6878
|
+
* pipe(These.make.second("warn"), These.mapFirst(n => n * 2)); // Second("warn")
|
|
6753
6879
|
* ```
|
|
6754
6880
|
*/
|
|
6755
|
-
|
|
6756
|
-
if (
|
|
6757
|
-
return
|
|
6881
|
+
mapFirst: (f) => (data) => {
|
|
6882
|
+
if (isSecond(data)) {
|
|
6883
|
+
return data;
|
|
6884
|
+
}
|
|
6885
|
+
if (isFirst(data)) {
|
|
6886
|
+
return makeFirst(f(data.first));
|
|
6758
6887
|
}
|
|
6759
|
-
return
|
|
6888
|
+
return makeBoth(f(data.first), data.second);
|
|
6760
6889
|
},
|
|
6761
6890
|
/**
|
|
6762
|
-
*
|
|
6763
|
-
* using a custom error concatenator function when both sides fail.
|
|
6891
|
+
* Transforms the second value, leaving the first unchanged.
|
|
6764
6892
|
*
|
|
6765
6893
|
* @example
|
|
6766
6894
|
* ```ts
|
|
6767
|
-
*
|
|
6768
|
-
*
|
|
6769
|
-
* pipe(fnVal, Validation.apCustom(concat)(argVal));
|
|
6895
|
+
* pipe(These.make.second("warn"), These.mapSecond(e => e.toUpperCase())); // Second("WARN")
|
|
6896
|
+
* pipe(These.make.both(5, "warn"), These.mapSecond(e => e.toUpperCase())); // Both(5, "WARN")
|
|
6770
6897
|
* ```
|
|
6771
6898
|
*/
|
|
6772
|
-
|
|
6773
|
-
if (
|
|
6774
|
-
return
|
|
6899
|
+
mapSecond: (f) => (data) => {
|
|
6900
|
+
if (isFirst(data)) {
|
|
6901
|
+
return data;
|
|
6902
|
+
}
|
|
6903
|
+
if (isSecond(data)) {
|
|
6904
|
+
return makeSecond(f(data.second));
|
|
6775
6905
|
}
|
|
6776
|
-
return
|
|
6906
|
+
return makeBoth(data.first, f(data.second));
|
|
6777
6907
|
},
|
|
6778
6908
|
/**
|
|
6779
|
-
*
|
|
6909
|
+
* Transforms both the first and second values independently.
|
|
6780
6910
|
*
|
|
6781
6911
|
* @example
|
|
6782
6912
|
* ```ts
|
|
6783
6913
|
* pipe(
|
|
6784
|
-
*
|
|
6785
|
-
*
|
|
6786
|
-
*
|
|
6787
|
-
* value => `Value: ${value}`
|
|
6788
|
-
* )
|
|
6789
|
-
* );
|
|
6914
|
+
* These.make.both(5, "warn"),
|
|
6915
|
+
* These.mapBoth(n => n * 2, e => e.toUpperCase())
|
|
6916
|
+
* ); // Both(10, "WARN")
|
|
6790
6917
|
* ```
|
|
6791
6918
|
*/
|
|
6792
|
-
|
|
6919
|
+
mapBoth: (onFirst, onSecond) => (data) => {
|
|
6920
|
+
if (isSecond(data)) {
|
|
6921
|
+
return makeSecond(onSecond(data.second));
|
|
6922
|
+
}
|
|
6923
|
+
if (isFirst(data)) {
|
|
6924
|
+
return makeFirst(onFirst(data.first));
|
|
6925
|
+
}
|
|
6926
|
+
return makeBoth(onFirst(data.first), onSecond(data.second));
|
|
6927
|
+
},
|
|
6793
6928
|
/**
|
|
6794
|
-
*
|
|
6929
|
+
* Chains These computations by passing the first value to f.
|
|
6930
|
+
* Second propagates unchanged; First and Both apply f to the first value.
|
|
6795
6931
|
*
|
|
6796
6932
|
* @example
|
|
6797
6933
|
* ```ts
|
|
6798
|
-
*
|
|
6799
|
-
*
|
|
6800
|
-
*
|
|
6801
|
-
*
|
|
6802
|
-
*
|
|
6803
|
-
* })
|
|
6804
|
-
* );
|
|
6934
|
+
* const double = (n: number): These<number, string> => These.make.first(n * 2);
|
|
6935
|
+
*
|
|
6936
|
+
* pipe(These.make.first(5), These.chainFirst(double)); // First(10)
|
|
6937
|
+
* pipe(These.make.both(5, "warn"), These.chainFirst(double)); // First(10)
|
|
6938
|
+
* pipe(These.make.second("warn"), These.chainFirst(double)); // Second("warn")
|
|
6805
6939
|
* ```
|
|
6806
6940
|
*/
|
|
6807
|
-
|
|
6941
|
+
chainFirst: (f) => (data) => {
|
|
6942
|
+
if (isSecond(data)) {
|
|
6943
|
+
return data;
|
|
6944
|
+
}
|
|
6945
|
+
return f(data.first);
|
|
6946
|
+
},
|
|
6808
6947
|
/**
|
|
6809
|
-
*
|
|
6810
|
-
*
|
|
6948
|
+
* Chains These computations by passing the second value to f.
|
|
6949
|
+
* First propagates unchanged; Second and Both apply f to the second value.
|
|
6811
6950
|
*
|
|
6812
6951
|
* @example
|
|
6813
6952
|
* ```ts
|
|
6814
|
-
*
|
|
6815
|
-
*
|
|
6816
|
-
* pipe(
|
|
6953
|
+
* const shout = (s: string): These<number, string> => These.make.second(s.toUpperCase());
|
|
6954
|
+
*
|
|
6955
|
+
* pipe(These.make.second("warn"), These.chainSecond(shout)); // Second("WARN")
|
|
6956
|
+
* pipe(These.make.both(5, "warn"), These.chainSecond(shout)); // Second("WARN")
|
|
6957
|
+
* pipe(These.make.first(5), These.chainSecond(shout)); // First(5)
|
|
6817
6958
|
* ```
|
|
6818
6959
|
*/
|
|
6819
|
-
|
|
6960
|
+
chainSecond: (f) => (data) => {
|
|
6961
|
+
if (isFirst(data)) {
|
|
6962
|
+
return data;
|
|
6963
|
+
}
|
|
6964
|
+
return f(data.second);
|
|
6965
|
+
},
|
|
6820
6966
|
/**
|
|
6821
|
-
*
|
|
6967
|
+
* Extracts a value from a These by providing handlers for all three cases.
|
|
6822
6968
|
*
|
|
6823
6969
|
* @example
|
|
6824
6970
|
* ```ts
|
|
6825
6971
|
* pipe(
|
|
6826
|
-
*
|
|
6827
|
-
*
|
|
6828
|
-
*
|
|
6972
|
+
* these,
|
|
6973
|
+
* These.fold(
|
|
6974
|
+
* a => `First: ${a}`,
|
|
6975
|
+
* b => `Second: ${b}`,
|
|
6976
|
+
* (a, b) => `Both: ${a} / ${b}`
|
|
6977
|
+
* )
|
|
6829
6978
|
* );
|
|
6830
6979
|
* ```
|
|
6831
6980
|
*/
|
|
6832
|
-
|
|
6833
|
-
if (
|
|
6834
|
-
|
|
6981
|
+
fold: (onFirst, onSecond, onBoth) => (data) => {
|
|
6982
|
+
if (isSecond(data)) {
|
|
6983
|
+
return onSecond(data.second);
|
|
6835
6984
|
}
|
|
6836
|
-
|
|
6985
|
+
if (isFirst(data)) {
|
|
6986
|
+
return onFirst(data.first);
|
|
6987
|
+
}
|
|
6988
|
+
return onBoth(data.first, data.second);
|
|
6837
6989
|
},
|
|
6838
6990
|
/**
|
|
6839
|
-
*
|
|
6840
|
-
* Useful for logging or reporting validation failures.
|
|
6991
|
+
* Pattern matches on a These, returning the result of the matching case.
|
|
6841
6992
|
*
|
|
6842
6993
|
* @example
|
|
6843
6994
|
* ```ts
|
|
6844
6995
|
* pipe(
|
|
6845
|
-
*
|
|
6846
|
-
*
|
|
6847
|
-
*
|
|
6996
|
+
* these,
|
|
6997
|
+
* These.match({
|
|
6998
|
+
* first: a => `First: ${a}`,
|
|
6999
|
+
* second: b => `Second: ${b}`,
|
|
7000
|
+
* both: (a, b) => `Both: ${a} / ${b}`
|
|
7001
|
+
* })
|
|
6848
7002
|
* );
|
|
6849
7003
|
* ```
|
|
6850
7004
|
*/
|
|
6851
|
-
|
|
6852
|
-
if (
|
|
6853
|
-
|
|
7005
|
+
match: (cases) => (data) => {
|
|
7006
|
+
if (isSecond(data)) {
|
|
7007
|
+
return cases.second(data.second);
|
|
6854
7008
|
}
|
|
6855
|
-
|
|
7009
|
+
if (isFirst(data)) {
|
|
7010
|
+
return cases.first(data.first);
|
|
7011
|
+
}
|
|
7012
|
+
return cases.both(data.first, data.second);
|
|
6856
7013
|
},
|
|
6857
7014
|
/**
|
|
6858
|
-
*
|
|
6859
|
-
* The
|
|
6860
|
-
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
6861
|
-
*/
|
|
6862
|
-
recover: (fallback) => (data) => isPassed(data) ? data : fallback(data.errors),
|
|
6863
|
-
/**
|
|
6864
|
-
* Recovers from a Failed state unless `isBlocked` returns true for any of the accumulated errors.
|
|
6865
|
-
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
7015
|
+
* Returns the first value, or a default if the These has no first value.
|
|
7016
|
+
* The default can be a different type, widening the result to `A | C`.
|
|
6866
7017
|
*
|
|
6867
7018
|
* @example
|
|
6868
7019
|
* ```ts
|
|
6869
|
-
* pipe(
|
|
6870
|
-
*
|
|
6871
|
-
*
|
|
6872
|
-
* );
|
|
7020
|
+
* pipe(These.make.first(5), These.getFirstOrElse(() => 0)); // 5
|
|
7021
|
+
* pipe(These.make.both(5, "warn"), These.getFirstOrElse(() => 0)); // 5
|
|
7022
|
+
* pipe(These.make.second("warn"), These.getFirstOrElse(() => 0)); // 0
|
|
7023
|
+
* pipe(These.make.second("warn"), These.getFirstOrElse(() => null)); // null — typed as number | null
|
|
6873
7024
|
* ```
|
|
6874
7025
|
*/
|
|
6875
|
-
|
|
6876
|
-
// --- to ---
|
|
6877
|
-
to: {
|
|
6878
|
-
/**
|
|
6879
|
-
* Converts a Validation to a Result.
|
|
6880
|
-
* Passed becomes Ok.
|
|
6881
|
-
* Direct call converts Failed to Err with accumulated error list `NonEmptyArr<E>`.
|
|
6882
|
-
* Curried call converts Failed to Err with combined error `E2` via `combineErrors`.
|
|
6883
|
-
*
|
|
6884
|
-
* @example
|
|
6885
|
-
* ```ts
|
|
6886
|
-
* Validation.to.Result(Validation.make.passed(42)); // Ok(42)
|
|
6887
|
-
* Validation.to.Result(Validation.make.failed("oops")); // Err(["oops"])
|
|
6888
|
-
* pipe(Validation.make.failed("oops"), Validation.to.Result(errors => errors.join(", "))); // Err("oops")
|
|
6889
|
-
* ```
|
|
6890
|
-
*/
|
|
6891
|
-
Result: toResult,
|
|
6892
|
-
/**
|
|
6893
|
-
* Converts a Validation to a Maybe. `Passed` becomes `Some`; `Failed` becomes `None`
|
|
6894
|
-
* (errors are discarded).
|
|
6895
|
-
*
|
|
6896
|
-
* @example
|
|
6897
|
-
* ```ts
|
|
6898
|
-
* Validation.to.Maybe(Validation.make.passed(42)); // Some(42)
|
|
6899
|
-
* Validation.to.Maybe(Validation.make.failed("bad")); // None
|
|
6900
|
-
* ```
|
|
6901
|
-
*/
|
|
6902
|
-
Maybe: (data) => isPassed(data) ? Maybe.make.some(data.value) : Maybe.make.none()
|
|
6903
|
-
},
|
|
7026
|
+
getFirstOrElse: (defaultValue) => (data) => hasFirst(data) ? data.first : defaultValue(),
|
|
6904
7027
|
/**
|
|
6905
|
-
*
|
|
6906
|
-
*
|
|
6907
|
-
* If either is Failed, accumulates errors from both sides.
|
|
7028
|
+
* Returns the second value, or a default if the These has no second value.
|
|
7029
|
+
* The default can be a different type, widening the result to `B | D`.
|
|
6908
7030
|
*
|
|
6909
7031
|
* @example
|
|
6910
7032
|
* ```ts
|
|
6911
|
-
*
|
|
6912
|
-
*
|
|
6913
|
-
*
|
|
6914
|
-
* );
|
|
6915
|
-
*
|
|
6916
|
-
* Validation.product(
|
|
6917
|
-
* Validation.make.failed("Name required"),
|
|
6918
|
-
* Validation.make.failed("Age must be >= 0")
|
|
6919
|
-
* ); // Failed(["Name required", "Age must be >= 0"])
|
|
7033
|
+
* pipe(These.make.second("warn"), These.getSecondOrElse(() => "none")); // "warn"
|
|
7034
|
+
* pipe(These.make.both(5, "warn"), These.getSecondOrElse(() => "none")); // "warn"
|
|
7035
|
+
* pipe(These.make.first(5), These.getSecondOrElse(() => "none")); // "none"
|
|
7036
|
+
* pipe(These.make.first(5), These.getSecondOrElse(() => null)); // null — typed as string | null
|
|
6920
7037
|
* ```
|
|
6921
7038
|
*/
|
|
6922
|
-
|
|
6923
|
-
if (isPassed(first)) {
|
|
6924
|
-
return isPassed(second) ? makePassed2([first.value, second.value]) : makeFailedAll2(second.errors);
|
|
6925
|
-
}
|
|
6926
|
-
return isPassed(second) ? makeFailedAll2(first.errors) : makeFailedAll2([...first.errors, ...second.errors]);
|
|
6927
|
-
},
|
|
7039
|
+
getSecondOrElse: (defaultValue) => (data) => hasSecond(data) ? data.second : defaultValue(),
|
|
6928
7040
|
/**
|
|
6929
|
-
*
|
|
6930
|
-
*
|
|
6931
|
-
* If any are Failed, returns Failed with all accumulated errors.
|
|
7041
|
+
* Runs a side effect on the first value without changing the These.
|
|
7042
|
+
* Useful for logging or debugging.
|
|
6932
7043
|
*
|
|
6933
|
-
* @example
|
|
6934
|
-
* ```ts
|
|
6935
|
-
*
|
|
6936
|
-
*
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
* ```
|
|
6942
|
-
*/
|
|
6943
|
-
productAll: (data) => {
|
|
6944
|
-
const values3 = [];
|
|
6945
|
-
const errors = [];
|
|
6946
|
-
for (const v of data) {
|
|
6947
|
-
if (isPassed(v)) {
|
|
6948
|
-
values3.push(v.value);
|
|
6949
|
-
} else {
|
|
6950
|
-
errors.push(...v.errors);
|
|
6951
|
-
}
|
|
7044
|
+
* @example
|
|
7045
|
+
* ```ts
|
|
7046
|
+
* pipe(These.make.first(5), These.tap(console.log)); // logs 5, returns First(5)
|
|
7047
|
+
* ```
|
|
7048
|
+
*/
|
|
7049
|
+
tap: (f) => (data) => {
|
|
7050
|
+
if (hasFirst(data)) {
|
|
7051
|
+
f(data.first);
|
|
6952
7052
|
}
|
|
6953
|
-
return
|
|
7053
|
+
return data;
|
|
6954
7054
|
},
|
|
6955
7055
|
/**
|
|
6956
|
-
*
|
|
6957
|
-
*
|
|
7056
|
+
* Swaps the roles of first and second values.
|
|
7057
|
+
* - First(a) → Second(a)
|
|
7058
|
+
* - Second(b) → First(b)
|
|
7059
|
+
* - Both(a, b) → Both(b, a)
|
|
6958
7060
|
*
|
|
6959
7061
|
* @example
|
|
6960
7062
|
* ```ts
|
|
6961
|
-
*
|
|
6962
|
-
*
|
|
6963
|
-
*
|
|
6964
|
-
* }); // Passed({ name: "Alice", age: 30 })
|
|
6965
|
-
*
|
|
6966
|
-
* Validation.struct({
|
|
6967
|
-
* name: Validation.make.failed("Name required"),
|
|
6968
|
-
* age: Validation.make.failed("Age must be >= 0")
|
|
6969
|
-
* }); // Failed(["Name required", "Age must be >= 0"])
|
|
7063
|
+
* These.swap(These.make.first(5)); // Second(5)
|
|
7064
|
+
* These.swap(These.make.second("warn")); // First("warn")
|
|
7065
|
+
* These.swap(These.make.both(5, "warn")); // Both("warn", 5)
|
|
6970
7066
|
* ```
|
|
6971
7067
|
*/
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
if (isPassed(val)) {
|
|
6979
|
-
record[key] = val.value;
|
|
6980
|
-
} else {
|
|
6981
|
-
errors.push(...val.errors);
|
|
6982
|
-
}
|
|
6983
|
-
}
|
|
7068
|
+
swap: (data) => {
|
|
7069
|
+
if (isSecond(data)) {
|
|
7070
|
+
return makeFirst(data.second);
|
|
7071
|
+
}
|
|
7072
|
+
if (isFirst(data)) {
|
|
7073
|
+
return makeSecond(data.first);
|
|
6984
7074
|
}
|
|
6985
|
-
return
|
|
7075
|
+
return makeBoth(data.second, data.first);
|
|
6986
7076
|
}
|
|
6987
7077
|
};
|
|
6988
7078
|
|
|
@@ -7521,6 +7611,60 @@ var Arr = {
|
|
|
7521
7611
|
|
|
7522
7612
|
// src/Data/BigNum.ts
|
|
7523
7613
|
var BigNum = {
|
|
7614
|
+
is: {
|
|
7615
|
+
/**
|
|
7616
|
+
* Returns `true` when the bigint is equal to zero (`0n`).
|
|
7617
|
+
*
|
|
7618
|
+
* @example
|
|
7619
|
+
* ```ts
|
|
7620
|
+
* BigNum.is.zero(0n); // true
|
|
7621
|
+
* BigNum.is.zero(5n); // false
|
|
7622
|
+
* ```
|
|
7623
|
+
*/
|
|
7624
|
+
zero: (b) => b === 0n,
|
|
7625
|
+
/**
|
|
7626
|
+
* Returns `true` when the bigint is an even integer.
|
|
7627
|
+
*
|
|
7628
|
+
* @example
|
|
7629
|
+
* ```ts
|
|
7630
|
+
* BigNum.is.even(4n); // true
|
|
7631
|
+
* BigNum.is.even(3n); // false
|
|
7632
|
+
* ```
|
|
7633
|
+
*/
|
|
7634
|
+
even: (b) => b % 2n === 0n,
|
|
7635
|
+
/**
|
|
7636
|
+
* Returns `true` when the bigint is an odd integer.
|
|
7637
|
+
*
|
|
7638
|
+
* @example
|
|
7639
|
+
* ```ts
|
|
7640
|
+
* BigNum.is.odd(3n); // true
|
|
7641
|
+
* BigNum.is.odd(4n); // false
|
|
7642
|
+
* ```
|
|
7643
|
+
*/
|
|
7644
|
+
odd: (b) => b % 2n !== 0n,
|
|
7645
|
+
/**
|
|
7646
|
+
* Returns `true` when the bigint is strictly greater than zero (`0n`).
|
|
7647
|
+
*
|
|
7648
|
+
* @example
|
|
7649
|
+
* ```ts
|
|
7650
|
+
* BigNum.is.positive(5n); // true
|
|
7651
|
+
* BigNum.is.positive(0n); // false
|
|
7652
|
+
* BigNum.is.positive(-5n); // false
|
|
7653
|
+
* ```
|
|
7654
|
+
*/
|
|
7655
|
+
positive: (b) => b > 0n,
|
|
7656
|
+
/**
|
|
7657
|
+
* Returns `true` when the bigint is strictly less than zero (`0n`).
|
|
7658
|
+
*
|
|
7659
|
+
* @example
|
|
7660
|
+
* ```ts
|
|
7661
|
+
* BigNum.is.negative(-5n); // true
|
|
7662
|
+
* BigNum.is.negative(0n); // false
|
|
7663
|
+
* BigNum.is.negative(5n); // false
|
|
7664
|
+
* ```
|
|
7665
|
+
*/
|
|
7666
|
+
negative: (b) => b < 0n
|
|
7667
|
+
},
|
|
7524
7668
|
// --- from ---
|
|
7525
7669
|
from: {
|
|
7526
7670
|
/**
|
|
@@ -7670,6 +7814,345 @@ var BigNum = {
|
|
|
7670
7814
|
max: (b) => (a) => a > b ? a : b
|
|
7671
7815
|
};
|
|
7672
7816
|
|
|
7817
|
+
// src/Data/Bool.ts
|
|
7818
|
+
var isBoolean = (u) => typeof u === "boolean";
|
|
7819
|
+
var isTrue = (u) => u === true;
|
|
7820
|
+
var isFalse = (u) => u === false;
|
|
7821
|
+
var isTruthy = (u) => Boolean(u);
|
|
7822
|
+
var isFalsy = (u) => !u;
|
|
7823
|
+
var not2 = (b) => !b;
|
|
7824
|
+
var and2 = (that) => (self) => self && that;
|
|
7825
|
+
var or2 = (that) => (self) => self || that;
|
|
7826
|
+
var xor = (that) => (self) => self !== that;
|
|
7827
|
+
var andLazy = (that) => (self) => self && that();
|
|
7828
|
+
var orLazy = (that) => (self) => self || that();
|
|
7829
|
+
var all = (booleans) => {
|
|
7830
|
+
for (let i = 0; i < booleans.length; i++) {
|
|
7831
|
+
if (!booleans[i]) {
|
|
7832
|
+
return false;
|
|
7833
|
+
}
|
|
7834
|
+
}
|
|
7835
|
+
return true;
|
|
7836
|
+
};
|
|
7837
|
+
var any = (booleans) => {
|
|
7838
|
+
for (let i = 0; i < booleans.length; i++) {
|
|
7839
|
+
if (booleans[i]) {
|
|
7840
|
+
return true;
|
|
7841
|
+
}
|
|
7842
|
+
}
|
|
7843
|
+
return false;
|
|
7844
|
+
};
|
|
7845
|
+
var fold = (onFalse, onTrue) => (b) => b ? onTrue() : onFalse();
|
|
7846
|
+
var match = (cases) => (b) => b ? cases.true() : cases.false();
|
|
7847
|
+
var fromString = (s) => {
|
|
7848
|
+
const trimmed = s.trim().toLowerCase();
|
|
7849
|
+
if (trimmed === "true") {
|
|
7850
|
+
return Maybe.make.some(true);
|
|
7851
|
+
}
|
|
7852
|
+
if (trimmed === "false") {
|
|
7853
|
+
return Maybe.make.some(false);
|
|
7854
|
+
}
|
|
7855
|
+
return Maybe.make.none();
|
|
7856
|
+
};
|
|
7857
|
+
var fromNumber = (n) => {
|
|
7858
|
+
if (n === 1) {
|
|
7859
|
+
return Maybe.make.some(true);
|
|
7860
|
+
}
|
|
7861
|
+
if (n === 0) {
|
|
7862
|
+
return Maybe.make.some(false);
|
|
7863
|
+
}
|
|
7864
|
+
return Maybe.make.none();
|
|
7865
|
+
};
|
|
7866
|
+
var fromTruthy = (value) => Boolean(value);
|
|
7867
|
+
var toMaybe = (onTrue) => (b) => b ? Maybe.make.some(onTrue()) : Maybe.make.none();
|
|
7868
|
+
var toResult2 = (onErr, onOk) => (b) => b ? Result.make.ok(onOk()) : Result.make.err(onErr());
|
|
7869
|
+
var toNumber = (b) => b ? 1 : 0;
|
|
7870
|
+
var toString = (b) => b ? "true" : "false";
|
|
7871
|
+
var Bool = {
|
|
7872
|
+
is: {
|
|
7873
|
+
/**
|
|
7874
|
+
* Type guard — checks if a value is a primitive boolean.
|
|
7875
|
+
*
|
|
7876
|
+
* @example
|
|
7877
|
+
* ```ts
|
|
7878
|
+
* Bool.is.boolean(true); // true
|
|
7879
|
+
* Bool.is.boolean(false); // true
|
|
7880
|
+
* Bool.is.boolean("true"); // false
|
|
7881
|
+
* Bool.is.boolean(null); // false
|
|
7882
|
+
* ```
|
|
7883
|
+
*/
|
|
7884
|
+
boolean: isBoolean,
|
|
7885
|
+
/**
|
|
7886
|
+
* Narrowing guard — checks if a value is strictly `true`.
|
|
7887
|
+
*
|
|
7888
|
+
* @example
|
|
7889
|
+
* ```ts
|
|
7890
|
+
* Bool.is.true(true); // true
|
|
7891
|
+
* Bool.is.true(false); // false
|
|
7892
|
+
* ```
|
|
7893
|
+
*/
|
|
7894
|
+
true: isTrue,
|
|
7895
|
+
/**
|
|
7896
|
+
* Narrowing guard — checks if a value is strictly `false`.
|
|
7897
|
+
*
|
|
7898
|
+
* @example
|
|
7899
|
+
* ```ts
|
|
7900
|
+
* Bool.is.false(false); // true
|
|
7901
|
+
* Bool.is.false(true); // false
|
|
7902
|
+
* ```
|
|
7903
|
+
*/
|
|
7904
|
+
false: isFalse,
|
|
7905
|
+
/**
|
|
7906
|
+
* Type guard — checks if a value is truthy (not `false`, `0`, `0n`, `""`, `null`, `undefined`, or `NaN`).
|
|
7907
|
+
*
|
|
7908
|
+
* @example
|
|
7909
|
+
* ```ts
|
|
7910
|
+
* Bool.is.truthy("hello"); // true
|
|
7911
|
+
* Bool.is.truthy(42); // true
|
|
7912
|
+
* Bool.is.truthy(0); // false
|
|
7913
|
+
* Bool.is.truthy(null); // false
|
|
7914
|
+
* ```
|
|
7915
|
+
*/
|
|
7916
|
+
truthy: isTruthy,
|
|
7917
|
+
/**
|
|
7918
|
+
* Type guard — checks if a value is falsy (`false`, `0`, `0n`, `""`, `null`, `undefined`, or `NaN`).
|
|
7919
|
+
*
|
|
7920
|
+
* @example
|
|
7921
|
+
* ```ts
|
|
7922
|
+
* Bool.is.falsy(""); // true
|
|
7923
|
+
* Bool.is.falsy(null); // true
|
|
7924
|
+
* Bool.is.falsy("content"); // false
|
|
7925
|
+
* ```
|
|
7926
|
+
*/
|
|
7927
|
+
falsy: isFalsy
|
|
7928
|
+
},
|
|
7929
|
+
/**
|
|
7930
|
+
* Unary boolean negation: inverts the given boolean value.
|
|
7931
|
+
*
|
|
7932
|
+
* @example
|
|
7933
|
+
* ```ts
|
|
7934
|
+
* Bool.not(true); // false
|
|
7935
|
+
* Bool.not(false); // true
|
|
7936
|
+
* ```
|
|
7937
|
+
*/
|
|
7938
|
+
not: not2,
|
|
7939
|
+
/**
|
|
7940
|
+
* Logical AND combinator. Returns `true` only if both `self` and `that` are `true`.
|
|
7941
|
+
*
|
|
7942
|
+
* Data-last: `pipe(self, Bool.and(that))`.
|
|
7943
|
+
*
|
|
7944
|
+
* @example
|
|
7945
|
+
* ```ts
|
|
7946
|
+
* pipe(true, Bool.and(true)); // true
|
|
7947
|
+
* pipe(true, Bool.and(false)); // false
|
|
7948
|
+
* ```
|
|
7949
|
+
*/
|
|
7950
|
+
and: and2,
|
|
7951
|
+
/**
|
|
7952
|
+
* Logical OR combinator. Returns `true` if either `self` or `that` is `true`.
|
|
7953
|
+
*
|
|
7954
|
+
* Data-last: `pipe(self, Bool.or(that))`.
|
|
7955
|
+
*
|
|
7956
|
+
* @example
|
|
7957
|
+
* ```ts
|
|
7958
|
+
* pipe(false, Bool.or(true)); // true
|
|
7959
|
+
* pipe(false, Bool.or(false)); // false
|
|
7960
|
+
* ```
|
|
7961
|
+
*/
|
|
7962
|
+
or: or2,
|
|
7963
|
+
/**
|
|
7964
|
+
* Logical XOR (exclusive OR) combinator. Returns `true` if exactly one of `self` and `that` is `true`.
|
|
7965
|
+
*
|
|
7966
|
+
* Data-last: `pipe(self, Bool.xor(that))`.
|
|
7967
|
+
*
|
|
7968
|
+
* @example
|
|
7969
|
+
* ```ts
|
|
7970
|
+
* pipe(true, Bool.xor(false)); // true
|
|
7971
|
+
* pipe(true, Bool.xor(true)); // false
|
|
7972
|
+
* ```
|
|
7973
|
+
*/
|
|
7974
|
+
xor,
|
|
7975
|
+
/**
|
|
7976
|
+
* Lazy logical AND combinator.
|
|
7977
|
+
* If `self` is `false`, the `that` computation is never evaluated.
|
|
7978
|
+
*
|
|
7979
|
+
* Data-last: `pipe(self, Bool.andLazy(that))`.
|
|
7980
|
+
*
|
|
7981
|
+
* @example
|
|
7982
|
+
* ```ts
|
|
7983
|
+
* pipe(
|
|
7984
|
+
* isCached,
|
|
7985
|
+
* Bool.andLazy(() => checkPermissions())
|
|
7986
|
+
* );
|
|
7987
|
+
* ```
|
|
7988
|
+
*/
|
|
7989
|
+
andLazy,
|
|
7990
|
+
/**
|
|
7991
|
+
* Lazy logical OR combinator.
|
|
7992
|
+
* If `self` is `true`, the `that` computation is never evaluated.
|
|
7993
|
+
*
|
|
7994
|
+
* Data-last: `pipe(self, Bool.orLazy(that))`.
|
|
7995
|
+
*
|
|
7996
|
+
* @example
|
|
7997
|
+
* ```ts
|
|
7998
|
+
* pipe(
|
|
7999
|
+
* isAdmin,
|
|
8000
|
+
* Bool.orLazy(() => hasAccess(userId))
|
|
8001
|
+
* );
|
|
8002
|
+
* ```
|
|
8003
|
+
*/
|
|
8004
|
+
orLazy,
|
|
8005
|
+
/**
|
|
8006
|
+
* N-ary AND aggregation across an array of booleans.
|
|
8007
|
+
* Returns `true` if every boolean is `true`, or for an empty array (vacuous truth).
|
|
8008
|
+
* Short-circuits on the first `false`.
|
|
8009
|
+
*
|
|
8010
|
+
* @example
|
|
8011
|
+
* ```ts
|
|
8012
|
+
* Bool.all([true, true, true]); // true
|
|
8013
|
+
* Bool.all([true, false, true]); // false
|
|
8014
|
+
* Bool.all([]); // true
|
|
8015
|
+
* ```
|
|
8016
|
+
*/
|
|
8017
|
+
all,
|
|
8018
|
+
/**
|
|
8019
|
+
* N-ary OR aggregation across an array of booleans.
|
|
8020
|
+
* Returns `true` if at least one boolean is `true`. Returns `false` for an empty array.
|
|
8021
|
+
* Short-circuits on the first `true`.
|
|
8022
|
+
*
|
|
8023
|
+
* @example
|
|
8024
|
+
* ```ts
|
|
8025
|
+
* Bool.any([false, true, false]); // true
|
|
8026
|
+
* Bool.any([false, false]); // false
|
|
8027
|
+
* Bool.any([]); // false
|
|
8028
|
+
* ```
|
|
8029
|
+
*/
|
|
8030
|
+
any,
|
|
8031
|
+
/**
|
|
8032
|
+
* Catamorphism for boolean: evaluates `onFalse()` when `false` and `onTrue()` when `true`.
|
|
8033
|
+
*
|
|
8034
|
+
* Positional ordering: `onFalse` first, `onTrue` second.
|
|
8035
|
+
* Aligned with `Result.fold(onErr, onOk)` and `Maybe.fold(onNone, onSome)`.
|
|
8036
|
+
*
|
|
8037
|
+
* @example
|
|
8038
|
+
* ```ts
|
|
8039
|
+
* pipe(
|
|
8040
|
+
* isDarkMode,
|
|
8041
|
+
* Bool.fold(
|
|
8042
|
+
* () => "light-theme",
|
|
8043
|
+
* () => "dark-theme"
|
|
8044
|
+
* )
|
|
8045
|
+
* );
|
|
8046
|
+
* ```
|
|
8047
|
+
*/
|
|
8048
|
+
fold,
|
|
8049
|
+
/**
|
|
8050
|
+
* Pattern matching on boolean using named cases `{ true, false }`.
|
|
8051
|
+
*
|
|
8052
|
+
* @example
|
|
8053
|
+
* ```ts
|
|
8054
|
+
* pipe(
|
|
8055
|
+
* isEnabled,
|
|
8056
|
+
* Bool.match({
|
|
8057
|
+
* true: () => "Feature Active",
|
|
8058
|
+
* false: () => "Feature Disabled",
|
|
8059
|
+
* })
|
|
8060
|
+
* );
|
|
8061
|
+
* ```
|
|
8062
|
+
*/
|
|
8063
|
+
match,
|
|
8064
|
+
// --- from ---
|
|
8065
|
+
from: {
|
|
8066
|
+
/**
|
|
8067
|
+
* Parses a string into a `Maybe<boolean>`.
|
|
8068
|
+
* Returns `Some(true)` for `"true"`, `Some(false)` for `"false"` (case-insensitive & trimmed),
|
|
8069
|
+
* and `None` for any other string.
|
|
8070
|
+
*
|
|
8071
|
+
* @example
|
|
8072
|
+
* ```ts
|
|
8073
|
+
* Bool.from.string("true"); // Some(true)
|
|
8074
|
+
* Bool.from.string("FALSE"); // Some(false)
|
|
8075
|
+
* Bool.from.string("yes"); // None
|
|
8076
|
+
* ```
|
|
8077
|
+
*/
|
|
8078
|
+
string: fromString,
|
|
8079
|
+
/**
|
|
8080
|
+
* Converts a number into a `Maybe<boolean>`.
|
|
8081
|
+
* Returns `Some(true)` for `1`, `Some(false)` for `0`, and `None` for any other number.
|
|
8082
|
+
*
|
|
8083
|
+
* @example
|
|
8084
|
+
* ```ts
|
|
8085
|
+
* Bool.from.number(1); // Some(true)
|
|
8086
|
+
* Bool.from.number(0); // Some(false)
|
|
8087
|
+
* Bool.from.number(42); // None
|
|
8088
|
+
* ```
|
|
8089
|
+
*/
|
|
8090
|
+
number: fromNumber,
|
|
8091
|
+
/**
|
|
8092
|
+
* Coerces any unknown value into a boolean via standard JS `Boolean(value)`.
|
|
8093
|
+
*
|
|
8094
|
+
* @example
|
|
8095
|
+
* ```ts
|
|
8096
|
+
* Bool.from.truthy("hello"); // true
|
|
8097
|
+
* Bool.from.truthy(0); // false
|
|
8098
|
+
* ```
|
|
8099
|
+
*/
|
|
8100
|
+
truthy: fromTruthy
|
|
8101
|
+
},
|
|
8102
|
+
// --- to ---
|
|
8103
|
+
to: {
|
|
8104
|
+
/**
|
|
8105
|
+
* Lifts a boolean condition into a `Maybe`.
|
|
8106
|
+
* Returns `Some(onTrue())` when `true`, and `None` when `false`.
|
|
8107
|
+
*
|
|
8108
|
+
* @example
|
|
8109
|
+
* ```ts
|
|
8110
|
+
* pipe(
|
|
8111
|
+
* user.isVerified,
|
|
8112
|
+
* Bool.to.Maybe(() => user.profile)
|
|
8113
|
+
* ); // Some(profile) or None
|
|
8114
|
+
* ```
|
|
8115
|
+
*/
|
|
8116
|
+
Maybe: toMaybe,
|
|
8117
|
+
/**
|
|
8118
|
+
* Lifts a boolean condition into a `Result`.
|
|
8119
|
+
* Returns `Ok(onOk())` when `true`, and `Err(onErr())` when `false`.
|
|
8120
|
+
*
|
|
8121
|
+
* @example
|
|
8122
|
+
* ```ts
|
|
8123
|
+
* pipe(
|
|
8124
|
+
* hasPermission,
|
|
8125
|
+
* Bool.to.Result(
|
|
8126
|
+
* () => "Permission denied",
|
|
8127
|
+
* () => sessionData
|
|
8128
|
+
* )
|
|
8129
|
+
* ); // Ok(sessionData) or Err("Permission denied")
|
|
8130
|
+
* ```
|
|
8131
|
+
*/
|
|
8132
|
+
Result: toResult2,
|
|
8133
|
+
/**
|
|
8134
|
+
* Converts a boolean to numeric `1` or `0`.
|
|
8135
|
+
*
|
|
8136
|
+
* @example
|
|
8137
|
+
* ```ts
|
|
8138
|
+
* Bool.to.number(true); // 1
|
|
8139
|
+
* Bool.to.number(false); // 0
|
|
8140
|
+
* ```
|
|
8141
|
+
*/
|
|
8142
|
+
number: toNumber,
|
|
8143
|
+
/**
|
|
8144
|
+
* Converts a boolean to literal string `"true"` or `"false"`.
|
|
8145
|
+
*
|
|
8146
|
+
* @example
|
|
8147
|
+
* ```ts
|
|
8148
|
+
* Bool.to.string(true); // "true"
|
|
8149
|
+
* Bool.to.string(false); // "false"
|
|
8150
|
+
* ```
|
|
8151
|
+
*/
|
|
8152
|
+
string: toString
|
|
8153
|
+
}
|
|
8154
|
+
};
|
|
8155
|
+
|
|
7673
8156
|
// src/Data/Dict.ts
|
|
7674
8157
|
var DictIs = {
|
|
7675
8158
|
empty: (m) => m.size === 0,
|
|
@@ -9059,6 +9542,7 @@ var Uniq = {
|
|
|
9059
9542
|
0 && (module.exports = {
|
|
9060
9543
|
Arr,
|
|
9061
9544
|
BigNum,
|
|
9545
|
+
Bool,
|
|
9062
9546
|
Brand,
|
|
9063
9547
|
Combinable,
|
|
9064
9548
|
Deferred,
|