@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.mjs
CHANGED
|
@@ -2588,22 +2588,120 @@ function interpretFn(op, options) {
|
|
|
2588
2588
|
}
|
|
2589
2589
|
}
|
|
2590
2590
|
var Op = {
|
|
2591
|
-
|
|
2591
|
+
make: {
|
|
2592
|
+
/**
|
|
2593
|
+
* Creates an Ok outcome with the given value.
|
|
2594
|
+
*
|
|
2595
|
+
* @example
|
|
2596
|
+
* ```ts
|
|
2597
|
+
* Op.make.ok(42); // { kind: "OpOk", value: 42 }
|
|
2598
|
+
* ```
|
|
2599
|
+
*/
|
|
2600
|
+
ok: makeOk,
|
|
2601
|
+
/**
|
|
2602
|
+
* Creates an Err outcome with the given error.
|
|
2603
|
+
*
|
|
2604
|
+
* @example
|
|
2605
|
+
* ```ts
|
|
2606
|
+
* Op.make.err("Something went wrong"); // { kind: "OpErr", error: "Something went wrong" }
|
|
2607
|
+
* ```
|
|
2608
|
+
*/
|
|
2609
|
+
err: makeErr,
|
|
2610
|
+
/**
|
|
2611
|
+
* Creates a Nil outcome with the given cancellation/drop reason.
|
|
2612
|
+
*
|
|
2613
|
+
* @example
|
|
2614
|
+
* ```ts
|
|
2615
|
+
* Op.make.nil("aborted"); // { kind: "OpNil", reason: "aborted" }
|
|
2616
|
+
* ```
|
|
2617
|
+
*/
|
|
2618
|
+
nil: makeNil
|
|
2619
|
+
},
|
|
2620
|
+
is: {
|
|
2621
|
+
/**
|
|
2622
|
+
* Type guard that checks if an Op state is Idle.
|
|
2623
|
+
*
|
|
2624
|
+
* @example
|
|
2625
|
+
* ```ts
|
|
2626
|
+
* if (Op.is.idle(manager.state)) {
|
|
2627
|
+
* console.log("Ready to execute");
|
|
2628
|
+
* }
|
|
2629
|
+
* ```
|
|
2630
|
+
*/
|
|
2631
|
+
idle: isIdle,
|
|
2632
|
+
/**
|
|
2633
|
+
* Type guard that checks if an Op state is Pending (actively executing).
|
|
2634
|
+
*
|
|
2635
|
+
* @example
|
|
2636
|
+
* ```ts
|
|
2637
|
+
* if (Op.is.pending(manager.state)) {
|
|
2638
|
+
* showSpinner();
|
|
2639
|
+
* }
|
|
2640
|
+
* ```
|
|
2641
|
+
*/
|
|
2642
|
+
pending: isPending,
|
|
2643
|
+
/**
|
|
2644
|
+
* Type guard that checks if an Op state is Queued (waiting in a concurrency queue).
|
|
2645
|
+
*
|
|
2646
|
+
* @example
|
|
2647
|
+
* ```ts
|
|
2648
|
+
* if (Op.is.queued(manager.state)) {
|
|
2649
|
+
* console.log("Position in queue:", manager.state.position);
|
|
2650
|
+
* }
|
|
2651
|
+
* ```
|
|
2652
|
+
*/
|
|
2653
|
+
queued: isQueued,
|
|
2654
|
+
/**
|
|
2655
|
+
* Type guard that checks if an Op state is Retrying after a failure.
|
|
2656
|
+
*
|
|
2657
|
+
* @example
|
|
2658
|
+
* ```ts
|
|
2659
|
+
* if (Op.is.retrying(manager.state)) {
|
|
2660
|
+
* console.log("Retry attempt:", manager.state.attempt);
|
|
2661
|
+
* }
|
|
2662
|
+
* ```
|
|
2663
|
+
*/
|
|
2664
|
+
retrying: isRetrying,
|
|
2665
|
+
/**
|
|
2666
|
+
* Type guard that checks if an Op state or outcome is Ok.
|
|
2667
|
+
*
|
|
2668
|
+
* @example
|
|
2669
|
+
* ```ts
|
|
2670
|
+
* if (Op.is.ok(outcome)) {
|
|
2671
|
+
* render(outcome.value);
|
|
2672
|
+
* }
|
|
2673
|
+
* ```
|
|
2674
|
+
*/
|
|
2675
|
+
ok: isOk,
|
|
2676
|
+
/**
|
|
2677
|
+
* Type guard that checks if an Op state or outcome is Err.
|
|
2678
|
+
*
|
|
2679
|
+
* @example
|
|
2680
|
+
* ```ts
|
|
2681
|
+
* if (Op.is.err(outcome)) {
|
|
2682
|
+
* showError(outcome.error);
|
|
2683
|
+
* }
|
|
2684
|
+
* ```
|
|
2685
|
+
*/
|
|
2686
|
+
err: isErr,
|
|
2687
|
+
/**
|
|
2688
|
+
* Type guard that checks if an Op state or outcome is Nil.
|
|
2689
|
+
*
|
|
2690
|
+
* @example
|
|
2691
|
+
* ```ts
|
|
2692
|
+
* if (Op.is.nil(outcome)) {
|
|
2693
|
+
* console.log("Skipped due to:", outcome.reason);
|
|
2694
|
+
* }
|
|
2695
|
+
* ```
|
|
2696
|
+
*/
|
|
2697
|
+
nil: isNil
|
|
2698
|
+
},
|
|
2592
2699
|
create: (factory, onError) => ({
|
|
2593
2700
|
_factory: (input, signal) => Deferred.from.Promise(
|
|
2594
2701
|
factory(signal)(input).then((value) => Result.make.ok(value)).catch((error) => signal.aborted ? null : Result.make.err(onError(error)))
|
|
2595
2702
|
)
|
|
2596
2703
|
}),
|
|
2597
2704
|
lift: (f) => Op.create((signal) => (input) => f(input, signal), (e) => e),
|
|
2598
|
-
ok: makeOk,
|
|
2599
|
-
err: makeErr,
|
|
2600
|
-
isIdle,
|
|
2601
|
-
isPending,
|
|
2602
|
-
isQueued,
|
|
2603
|
-
isRetrying,
|
|
2604
|
-
isOk,
|
|
2605
|
-
isErr,
|
|
2606
|
-
isNil,
|
|
2607
2705
|
match: (cases) => (outcome) => {
|
|
2608
2706
|
if (outcome.kind === "OpOk") {
|
|
2609
2707
|
return cases.ok(outcome.value);
|
|
@@ -4759,9 +4857,9 @@ var Stream = {
|
|
|
4759
4857
|
var makeSome2 = (value) => Task.resolve(Maybe.make.some(value));
|
|
4760
4858
|
var makeNone2 = () => Task.resolve(Maybe.make.none());
|
|
4761
4859
|
var mapTaskMaybe = (f) => (data) => Task.map(Maybe.map(f))(data);
|
|
4762
|
-
var chainTaskMaybe = (f) => (data) => Task.chain(
|
|
4763
|
-
|
|
4764
|
-
)
|
|
4860
|
+
var chainTaskMaybe = (f) => (data) => Task.chain((option) => Maybe.is.some(option) ? f(option.value) : Task.resolve(Maybe.make.none()))(
|
|
4861
|
+
data
|
|
4862
|
+
);
|
|
4765
4863
|
var TaskMaybe = {
|
|
4766
4864
|
/**
|
|
4767
4865
|
* Wraps a value in a Some inside a Task.
|
|
@@ -4794,8 +4892,6 @@ var TaskMaybe = {
|
|
|
4794
4892
|
*/
|
|
4795
4893
|
none: makeNone2
|
|
4796
4894
|
},
|
|
4797
|
-
some: makeSome2,
|
|
4798
|
-
none: makeNone2,
|
|
4799
4895
|
// --- from ---
|
|
4800
4896
|
from: {
|
|
4801
4897
|
/**
|
|
@@ -4931,7 +5027,7 @@ var TaskMaybe = {
|
|
|
4931
5027
|
*
|
|
4932
5028
|
* @example
|
|
4933
5029
|
* ```ts
|
|
4934
|
-
* pipe(Task.Maybe.some(42), Task.Maybe.bindTo("value")); // Task.Maybe({ value: 42 })
|
|
5030
|
+
* pipe(Task.Maybe.make.some(42), Task.Maybe.bindTo("value")); // Task.Maybe({ value: 42 })
|
|
4935
5031
|
* ```
|
|
4936
5032
|
*/
|
|
4937
5033
|
bindTo: (key) => (data) => mapTaskMaybe((a) => ({ [key]: a }))(data),
|
|
@@ -4941,8 +5037,8 @@ var TaskMaybe = {
|
|
|
4941
5037
|
* @example
|
|
4942
5038
|
* ```ts
|
|
4943
5039
|
* pipe(
|
|
4944
|
-
* Task.Maybe.some({ a: 1 }),
|
|
4945
|
-
* Task.Maybe.bind("b", ({ a }) => Task.Maybe.some(a + 1))
|
|
5040
|
+
* Task.Maybe.make.some({ a: 1 }),
|
|
5041
|
+
* Task.Maybe.bind("b", ({ a }) => Task.Maybe.make.some(a + 1))
|
|
4946
5042
|
* ); // Task.Maybe({ a: 1, b: 2 })
|
|
4947
5043
|
* ```
|
|
4948
5044
|
*/
|
|
@@ -4955,14 +5051,12 @@ var TaskMaybe = {
|
|
|
4955
5051
|
* @example
|
|
4956
5052
|
* ```ts
|
|
4957
5053
|
* pipe(
|
|
4958
|
-
* Task.Maybe.none(),
|
|
4959
|
-
* Task.Maybe.recover(() => Task.Maybe.some(42))
|
|
5054
|
+
* Task.Maybe.make.none(),
|
|
5055
|
+
* Task.Maybe.recover(() => Task.Maybe.make.some(42))
|
|
4960
5056
|
* ); // Task.Maybe(42)
|
|
4961
5057
|
* ```
|
|
4962
5058
|
*/
|
|
4963
|
-
recover: (fallback) => (data) => Task.chain((maybe) => Maybe.is.none(maybe) ? fallback() : Task.resolve(maybe))(
|
|
4964
|
-
data
|
|
4965
|
-
),
|
|
5059
|
+
recover: (fallback) => (data) => Task.chain((maybe) => Maybe.is.none(maybe) ? fallback() : Task.resolve(maybe))(data),
|
|
4966
5060
|
/**
|
|
4967
5061
|
* Combines a record of Task.Maybes into a single Task.Maybe of a record.
|
|
4968
5062
|
* Evaluates fields in parallel and returns None if any task resolves to None.
|
|
@@ -4970,8 +5064,8 @@ var TaskMaybe = {
|
|
|
4970
5064
|
* @example
|
|
4971
5065
|
* ```ts
|
|
4972
5066
|
* Task.Maybe.struct({
|
|
4973
|
-
* name: Task.Maybe.some("Alice"),
|
|
4974
|
-
* age: Task.Maybe.some(30)
|
|
5067
|
+
* name: Task.Maybe.make.some("Alice"),
|
|
5068
|
+
* age: Task.Maybe.make.some(30)
|
|
4975
5069
|
* }); // Task.Maybe({ name: "Alice", age: 30 })
|
|
4976
5070
|
* ```
|
|
4977
5071
|
*/
|
|
@@ -5032,8 +5126,6 @@ var TaskResult = {
|
|
|
5032
5126
|
*/
|
|
5033
5127
|
err: makeErr3
|
|
5034
5128
|
},
|
|
5035
|
-
ok: makeOk3,
|
|
5036
|
-
err: makeErr3,
|
|
5037
5129
|
// --- from ---
|
|
5038
5130
|
from: {
|
|
5039
5131
|
/**
|
|
@@ -5075,7 +5167,7 @@ var TaskResult = {
|
|
|
5075
5167
|
*
|
|
5076
5168
|
* @example
|
|
5077
5169
|
* ```ts
|
|
5078
|
-
* const taskResult = Task.Result.ok(42);
|
|
5170
|
+
* const taskResult = Task.Result.make.ok(42);
|
|
5079
5171
|
* const taskMaybe = pipe(taskResult, Task.Result.to.Maybe);
|
|
5080
5172
|
* ```
|
|
5081
5173
|
*/
|
|
@@ -5138,7 +5230,7 @@ var TaskResult = {
|
|
|
5138
5230
|
* fetchTask,
|
|
5139
5231
|
* Task.Result.recoverUnless(
|
|
5140
5232
|
* (e) => e === "fatal",
|
|
5141
|
-
* () => Task.Result.ok("fallback")
|
|
5233
|
+
* () => Task.Result.make.ok("fallback")
|
|
5142
5234
|
* )
|
|
5143
5235
|
* );
|
|
5144
5236
|
* ```
|
|
@@ -5201,7 +5293,7 @@ var TaskResult = {
|
|
|
5201
5293
|
*
|
|
5202
5294
|
* @example
|
|
5203
5295
|
* ```ts
|
|
5204
|
-
* pipe(Task.Result.ok(42), Task.Result.bindTo("value")); // Task.Result({ value: 42 })
|
|
5296
|
+
* pipe(Task.Result.make.ok(42), Task.Result.bindTo("value")); // Task.Result({ value: 42 })
|
|
5205
5297
|
* ```
|
|
5206
5298
|
*/
|
|
5207
5299
|
bindTo: (key) => (data) => mapTaskResult((a) => ({ [key]: a }))(data),
|
|
@@ -5211,8 +5303,8 @@ var TaskResult = {
|
|
|
5211
5303
|
* @example
|
|
5212
5304
|
* ```ts
|
|
5213
5305
|
* pipe(
|
|
5214
|
-
* Task.Result.ok({ a: 1 }),
|
|
5215
|
-
* Task.Result.bind("b", ({ a }) => Task.Result.ok(a + 1))
|
|
5306
|
+
* Task.Result.make.ok({ a: 1 }),
|
|
5307
|
+
* Task.Result.bind("b", ({ a }) => Task.Result.make.ok(a + 1))
|
|
5216
5308
|
* ); // Task.Result({ a: 1, b: 2 })
|
|
5217
5309
|
* ```
|
|
5218
5310
|
*/
|
|
@@ -5227,8 +5319,8 @@ var TaskResult = {
|
|
|
5227
5319
|
* @example
|
|
5228
5320
|
* ```ts
|
|
5229
5321
|
* Task.Result.struct({
|
|
5230
|
-
* name: Task.Result.ok("Alice"),
|
|
5231
|
-
* age: Task.Result.ok(30)
|
|
5322
|
+
* name: Task.Result.make.ok("Alice"),
|
|
5323
|
+
* age: Task.Result.make.ok(30)
|
|
5232
5324
|
* }); // Task.Result({ name: "Alice", age: 30 })
|
|
5233
5325
|
* ```
|
|
5234
5326
|
*/
|
|
@@ -5348,1553 +5440,1550 @@ var TaskResult = {
|
|
|
5348
5440
|
// src/internal/InternalTypes.ts
|
|
5349
5441
|
var isNonEmptyArr = (list) => list.length > 0;
|
|
5350
5442
|
|
|
5351
|
-
// src/Core/
|
|
5352
|
-
var makePassed = (value) =>
|
|
5353
|
-
var makeFailed = (error) =>
|
|
5354
|
-
var makeFailedAll = (errors) =>
|
|
5355
|
-
var
|
|
5443
|
+
// src/Core/Validation.ts
|
|
5444
|
+
var makePassed = (value) => ({ kind: "Passed", value });
|
|
5445
|
+
var makeFailed = (error) => ({ kind: "Failed", errors: [error] });
|
|
5446
|
+
var makeFailedAll = (errors) => ({ kind: "Failed", errors });
|
|
5447
|
+
var isPassed = (data) => data.kind === "Passed";
|
|
5448
|
+
var isFailed = (data) => data.kind === "Failed";
|
|
5449
|
+
function toResult(arg) {
|
|
5450
|
+
if (typeof arg === "function") {
|
|
5451
|
+
const combine = arg;
|
|
5452
|
+
return (val) => isPassed(val) ? Result.make.ok(val.value) : Result.make.err(combine(val.errors));
|
|
5453
|
+
}
|
|
5454
|
+
return isPassed(arg) ? Result.make.ok(arg.value) : Result.make.err(arg.errors);
|
|
5455
|
+
}
|
|
5456
|
+
var Validation = {
|
|
5356
5457
|
make: {
|
|
5357
5458
|
/**
|
|
5358
|
-
* Wraps a value in a passed
|
|
5459
|
+
* Wraps a value in a passed Validation.
|
|
5359
5460
|
*
|
|
5360
5461
|
* @example
|
|
5361
5462
|
* ```ts
|
|
5362
|
-
*
|
|
5363
|
-
* const res = await task(); // Passed(42)
|
|
5463
|
+
* Validation.make.passed(42); // Passed(42)
|
|
5364
5464
|
* ```
|
|
5365
5465
|
*/
|
|
5366
5466
|
passed: makePassed,
|
|
5367
5467
|
/**
|
|
5368
|
-
* Creates a failed
|
|
5468
|
+
* Creates a failed Validation from a single error.
|
|
5369
5469
|
*
|
|
5370
5470
|
* @example
|
|
5371
5471
|
* ```ts
|
|
5372
|
-
*
|
|
5373
|
-
* const res = await task(); // Failed(["invalid"])
|
|
5472
|
+
* Validation.make.failed("Invalid input");
|
|
5374
5473
|
* ```
|
|
5375
5474
|
*/
|
|
5376
5475
|
failed: makeFailed,
|
|
5377
5476
|
/**
|
|
5378
|
-
* Creates a failed
|
|
5477
|
+
* Creates a failed Validation from multiple errors.
|
|
5379
5478
|
*
|
|
5380
5479
|
* @example
|
|
5381
5480
|
* ```ts
|
|
5382
|
-
*
|
|
5383
|
-
* const res = await task(); // Failed(["err1", "err2"])
|
|
5481
|
+
* Validation.make.failedAll(["Invalid input"]);
|
|
5384
5482
|
* ```
|
|
5385
5483
|
*/
|
|
5386
5484
|
failedAll: makeFailedAll
|
|
5387
5485
|
},
|
|
5388
|
-
|
|
5389
|
-
failed: makeFailed,
|
|
5390
|
-
failedAll: makeFailedAll,
|
|
5391
|
-
// --- from ---
|
|
5392
|
-
from: {
|
|
5486
|
+
is: {
|
|
5393
5487
|
/**
|
|
5394
|
-
*
|
|
5488
|
+
* Type guard that checks if a Validation is passed.
|
|
5395
5489
|
*
|
|
5396
5490
|
* @example
|
|
5397
5491
|
* ```ts
|
|
5398
|
-
*
|
|
5492
|
+
* const v = Validation.make.passed(42);
|
|
5493
|
+
* if (Validation.is.passed(v)) {
|
|
5494
|
+
* console.log(v.value); // 42
|
|
5495
|
+
* }
|
|
5399
5496
|
* ```
|
|
5400
5497
|
*/
|
|
5401
|
-
|
|
5498
|
+
passed: isPassed,
|
|
5402
5499
|
/**
|
|
5403
|
-
*
|
|
5404
|
-
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
5405
|
-
* Otherwise, returns Passed.
|
|
5500
|
+
* Type guard that checks if a Validation is failed.
|
|
5406
5501
|
*
|
|
5407
5502
|
* @example
|
|
5408
5503
|
* ```ts
|
|
5409
|
-
*
|
|
5410
|
-
*
|
|
5504
|
+
* const v = Validation.make.failed("invalid");
|
|
5505
|
+
* if (Validation.is.failed(v)) {
|
|
5506
|
+
* console.log(v.errors); // ["invalid"]
|
|
5507
|
+
* }
|
|
5411
5508
|
* ```
|
|
5412
5509
|
*/
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5510
|
+
failed: isFailed
|
|
5511
|
+
},
|
|
5512
|
+
/**
|
|
5513
|
+
* Creates a Validation from a synchronous thunk that may throw.
|
|
5514
|
+
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
5515
|
+
*
|
|
5516
|
+
* @example
|
|
5517
|
+
* ```ts
|
|
5518
|
+
* const result = Validation.tryCatch(
|
|
5519
|
+
* () => JSON.parse(rawString),
|
|
5520
|
+
* { onError: (e) => `Parse error: ${e}` }
|
|
5521
|
+
* );
|
|
5522
|
+
* ```
|
|
5523
|
+
*/
|
|
5524
|
+
tryCatch: (f, options) => {
|
|
5525
|
+
try {
|
|
5526
|
+
return makePassed(f());
|
|
5527
|
+
} catch (error) {
|
|
5528
|
+
return makeFailed(options.onError(error));
|
|
5529
|
+
}
|
|
5530
|
+
},
|
|
5531
|
+
// --- from ---
|
|
5532
|
+
from: {
|
|
5416
5533
|
/**
|
|
5417
|
-
* Creates a
|
|
5418
|
-
*
|
|
5534
|
+
* Creates a Validation from a predicate applied to a value.
|
|
5535
|
+
* Returns Passed if the predicate passes, Failed from `onFalse` otherwise.
|
|
5419
5536
|
*
|
|
5420
5537
|
* @example
|
|
5421
5538
|
* ```ts
|
|
5422
|
-
*
|
|
5423
|
-
*
|
|
5539
|
+
* const validateName = Validation.from.Predicate(
|
|
5540
|
+
* (s: string) => s.length > 0,
|
|
5541
|
+
* () => "Name is required"
|
|
5542
|
+
* );
|
|
5543
|
+
*
|
|
5544
|
+
* validateName("Alice"); // Passed("Alice")
|
|
5545
|
+
* validateName(""); // Failed(["Name is required"])
|
|
5424
5546
|
* ```
|
|
5425
5547
|
*/
|
|
5426
|
-
|
|
5427
|
-
Maybe.is.none(maybe) ? Validation.make.failed(onNone()) : Validation.make.passed(maybe.value)
|
|
5428
|
-
),
|
|
5548
|
+
Predicate: (pred, onFalse) => (a) => pred(a) ? makePassed(a) : makeFailed(onFalse(a)),
|
|
5429
5549
|
/**
|
|
5430
|
-
* Creates a
|
|
5431
|
-
*
|
|
5550
|
+
* Creates a Validation from a nullable value.
|
|
5551
|
+
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
5552
|
+
* Otherwise, returns Passed.
|
|
5432
5553
|
*
|
|
5433
5554
|
* @example
|
|
5434
5555
|
* ```ts
|
|
5435
|
-
*
|
|
5436
|
-
*
|
|
5556
|
+
* pipe(null, Validation.from.nullable(() => "is null")); // Failed(["is null"])
|
|
5557
|
+
* pipe(42, Validation.from.nullable(() => "is null")); // Passed(42)
|
|
5437
5558
|
* ```
|
|
5438
5559
|
*/
|
|
5439
|
-
|
|
5440
|
-
},
|
|
5441
|
-
// --- to ---
|
|
5442
|
-
to: {
|
|
5560
|
+
nullable: (onNull) => (value) => value === null || value === void 0 ? makeFailed(onNull()) : makePassed(value),
|
|
5443
5561
|
/**
|
|
5444
|
-
*
|
|
5445
|
-
*
|
|
5562
|
+
* Creates a Validation from a Maybe.
|
|
5563
|
+
* If the Maybe is None, returns Failed with the error from onNone.
|
|
5564
|
+
* Otherwise, returns Passed.
|
|
5446
5565
|
*
|
|
5447
5566
|
* @example
|
|
5448
5567
|
* ```ts
|
|
5449
|
-
*
|
|
5568
|
+
* pipe(Maybe.make.none(), Validation.from.Maybe(() => "is none")); // Failed(["is none"])
|
|
5569
|
+
* pipe(Maybe.make.some(42), Validation.from.Maybe(() => "is none")); // Passed(42)
|
|
5450
5570
|
* ```
|
|
5451
5571
|
*/
|
|
5452
|
-
|
|
5572
|
+
Maybe: (onNone) => (maybe) => Maybe.is.none(maybe) ? makeFailed(onNone()) : makePassed(maybe.value),
|
|
5453
5573
|
/**
|
|
5454
|
-
* Converts a `
|
|
5455
|
-
*
|
|
5574
|
+
* Converts a `Result` to a `Validation`. `Ok` becomes `Passed`; `Err(e)` becomes `Failed([e])`.
|
|
5575
|
+
*
|
|
5576
|
+
* Useful when bridging from error-short-circuiting `Result` pipelines into
|
|
5577
|
+
* error-accumulating `Validation` pipelines.
|
|
5456
5578
|
*
|
|
5457
5579
|
* @example
|
|
5458
5580
|
* ```ts
|
|
5459
|
-
*
|
|
5581
|
+
* Validation.from.Result(Result.make.ok(42)); // Passed(42)
|
|
5582
|
+
* Validation.from.Result(Result.make.err("bad")); // Failed(["bad"])
|
|
5460
5583
|
* ```
|
|
5461
5584
|
*/
|
|
5462
|
-
|
|
5585
|
+
Result: (data) => data.kind === "Ok" ? makePassed(data.value) : makeFailed(data.error)
|
|
5463
5586
|
},
|
|
5464
5587
|
/**
|
|
5465
|
-
*
|
|
5466
|
-
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
5467
|
-
* The thunk optionally receives an `AbortSignal` forwarded from the call site.
|
|
5588
|
+
* Transforms the success value inside a Validation.
|
|
5468
5589
|
*
|
|
5469
5590
|
* @example
|
|
5470
5591
|
* ```ts
|
|
5471
|
-
*
|
|
5472
|
-
*
|
|
5473
|
-
* { onError: (e) => `Failed to load config: ${e}` }
|
|
5474
|
-
* );
|
|
5592
|
+
* pipe(Validation.make.passed(5), Validation.map(n => n * 2)); // Passed(10)
|
|
5593
|
+
* pipe(Validation.make.failed("oops"), Validation.map(n => n * 2)); // Failed(["oops"])
|
|
5475
5594
|
* ```
|
|
5476
5595
|
*/
|
|
5477
|
-
|
|
5478
|
-
// oxlint-disable-next-line require-await
|
|
5479
|
-
globalThis.Promise.resolve().then(async () => f(signal)).then(Validation.make.passed).catch(
|
|
5480
|
-
(error) => Validation.make.failed(options.onError(error))
|
|
5481
|
-
)
|
|
5482
|
-
),
|
|
5483
|
-
/**
|
|
5484
|
-
* Transforms the success value inside a Task.Validation.
|
|
5485
|
-
*/
|
|
5486
|
-
map: (f) => (data) => Task.map(Validation.map(f))(data),
|
|
5596
|
+
map: (f) => (data) => isPassed(data) ? makePassed(f(data.value)) : data,
|
|
5487
5597
|
/**
|
|
5488
|
-
*
|
|
5489
|
-
* Task.Validation. Both Tasks run in parallel and errors from both sides
|
|
5490
|
-
* are accumulated.
|
|
5598
|
+
* Transforms the error list inside a Validation.
|
|
5491
5599
|
*
|
|
5492
5600
|
* @example
|
|
5493
5601
|
* ```ts
|
|
5494
|
-
* pipe(
|
|
5495
|
-
* Task.Validation.passed((name: string) => (age: number) => ({ name, age })),
|
|
5496
|
-
* Task.Validation.ap(validateName(name)),
|
|
5497
|
-
* Task.Validation.ap(validateAge(age))
|
|
5498
|
-
* )();
|
|
5602
|
+
* pipe(Validation.make.failed("oops"), Validation.mapError(e => e.toUpperCase())); // Failed(["OOPS"])
|
|
5499
5603
|
* ```
|
|
5500
5604
|
*/
|
|
5501
|
-
|
|
5502
|
-
Promise.all([Deferred.to.Promise(data(signal)), Deferred.to.Promise(arg(signal))]).then(
|
|
5503
|
-
([vf, va]) => Validation.ap(va)(vf)
|
|
5504
|
-
)
|
|
5505
|
-
),
|
|
5506
|
-
/**
|
|
5507
|
-
* Extracts a value from a Task.Validation by providing handlers for both cases.
|
|
5508
|
-
*/
|
|
5509
|
-
fold: (onFailed, onPassed) => (data) => Task.map(Validation.fold(onFailed, onPassed))(data),
|
|
5605
|
+
mapError: (f) => (data) => isFailed(data) ? makeFailedAll(data.errors.map(f)) : data,
|
|
5510
5606
|
/**
|
|
5511
|
-
*
|
|
5607
|
+
* Applies a function wrapped in a Validation to a value wrapped in a Validation.
|
|
5608
|
+
* Accumulates errors from both sides.
|
|
5512
5609
|
*
|
|
5513
5610
|
* @example
|
|
5514
5611
|
* ```ts
|
|
5612
|
+
* const add = (a: number) => (b: number) => a + b;
|
|
5515
5613
|
* pipe(
|
|
5516
|
-
*
|
|
5517
|
-
*
|
|
5518
|
-
*
|
|
5519
|
-
*
|
|
5520
|
-
*
|
|
5521
|
-
*
|
|
5614
|
+
* Validation.make.passed(add),
|
|
5615
|
+
* Validation.ap(Validation.make.passed(5)),
|
|
5616
|
+
* Validation.ap(Validation.make.passed(3))
|
|
5617
|
+
* ); // Passed(8)
|
|
5618
|
+
*
|
|
5619
|
+
* pipe(
|
|
5620
|
+
* Validation.make.passed(add),
|
|
5621
|
+
* Validation.ap(Validation.make.failed<string>("bad a")),
|
|
5622
|
+
* Validation.ap(Validation.make.failed<string>("bad b"))
|
|
5623
|
+
* ); // Failed(["bad a", "bad b"])
|
|
5522
5624
|
* ```
|
|
5523
5625
|
*/
|
|
5524
|
-
|
|
5525
|
-
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
/**
|
|
5531
|
-
* Executes a side effect on the success value without changing the Task.Validation.
|
|
5532
|
-
* Useful for logging or debugging.
|
|
5533
|
-
*/
|
|
5534
|
-
tap: (f) => (data) => Task.map(Validation.tap(f))(data),
|
|
5535
|
-
/**
|
|
5536
|
-
* Recovers from a Failed state by providing a fallback Task.Validation.
|
|
5537
|
-
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
5538
|
-
* The fallback can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
5539
|
-
*/
|
|
5540
|
-
recover: (fallback) => (data) => Task.chain(
|
|
5541
|
-
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : fallback(validation.errors)
|
|
5542
|
-
)(data),
|
|
5626
|
+
ap: (arg) => (data) => {
|
|
5627
|
+
if (isPassed(data)) {
|
|
5628
|
+
return isPassed(arg) ? makePassed(data.value(arg.value)) : makeFailedAll(arg.errors);
|
|
5629
|
+
}
|
|
5630
|
+
return isPassed(arg) ? makeFailedAll(data.errors) : makeFailedAll([...data.errors, ...arg.errors]);
|
|
5631
|
+
},
|
|
5543
5632
|
/**
|
|
5544
|
-
*
|
|
5545
|
-
*
|
|
5633
|
+
* Applies a function wrapped in a Validation to a value wrapped in a Validation,
|
|
5634
|
+
* using a custom error concatenator function when both sides fail.
|
|
5546
5635
|
*
|
|
5547
5636
|
* @example
|
|
5548
5637
|
* ```ts
|
|
5549
|
-
*
|
|
5550
|
-
*
|
|
5551
|
-
*
|
|
5552
|
-
* (errors) => errors.includes("fatal"),
|
|
5553
|
-
* (errors) => Task.Validation.passed("fallback")
|
|
5554
|
-
* )
|
|
5555
|
-
* );
|
|
5638
|
+
* const concat = (e1: NonEmptyArr<string>, e2: NonEmptyArr<string>): NonEmptyArr<string> =>
|
|
5639
|
+
* [...e1, ...e2];
|
|
5640
|
+
* pipe(fnVal, Validation.apCustom(concat)(argVal));
|
|
5556
5641
|
* ```
|
|
5557
5642
|
*/
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5643
|
+
apCustom: (concat2) => (arg) => (data) => {
|
|
5644
|
+
if (isPassed(data)) {
|
|
5645
|
+
return isPassed(arg) ? makePassed(data.value(arg.value)) : makeFailedAll(arg.errors);
|
|
5646
|
+
}
|
|
5647
|
+
return isPassed(arg) ? makeFailedAll(data.errors) : makeFailedAll(concat2(data.errors, arg.errors));
|
|
5648
|
+
},
|
|
5561
5649
|
/**
|
|
5562
|
-
*
|
|
5563
|
-
* If both are Passed, returns Passed with both values. If either fails, accumulates
|
|
5564
|
-
* errors from both sides.
|
|
5650
|
+
* Extracts the value from a Validation by providing handlers for both cases.
|
|
5565
5651
|
*
|
|
5566
5652
|
* @example
|
|
5567
5653
|
* ```ts
|
|
5568
|
-
*
|
|
5569
|
-
*
|
|
5570
|
-
*
|
|
5571
|
-
*
|
|
5654
|
+
* pipe(
|
|
5655
|
+
* Validation.make.passed(42),
|
|
5656
|
+
* Validation.fold(
|
|
5657
|
+
* errors => `Errors: ${errors.join(", ")}`,
|
|
5658
|
+
* value => `Value: ${value}`
|
|
5659
|
+
* )
|
|
5660
|
+
* );
|
|
5572
5661
|
* ```
|
|
5573
5662
|
*/
|
|
5574
|
-
|
|
5575
|
-
Promise.all([Deferred.to.Promise(first(signal)), Deferred.to.Promise(second(signal))]).then(
|
|
5576
|
-
([va, vb]) => Validation.product(va, vb)
|
|
5577
|
-
)
|
|
5578
|
-
),
|
|
5663
|
+
fold: (onFailed, onPassed) => (data) => isPassed(data) ? onPassed(data.value) : onFailed(data.errors),
|
|
5579
5664
|
/**
|
|
5580
|
-
*
|
|
5581
|
-
* If all are Passed, returns Passed with all values as an array.
|
|
5582
|
-
* If any fail, returns Failed with all accumulated errors.
|
|
5665
|
+
* Pattern matches on a Validation, returning the result of the matching case.
|
|
5583
5666
|
*
|
|
5584
5667
|
* @example
|
|
5585
5668
|
* ```ts
|
|
5586
|
-
*
|
|
5587
|
-
*
|
|
5588
|
-
*
|
|
5589
|
-
*
|
|
5590
|
-
*
|
|
5669
|
+
* pipe(
|
|
5670
|
+
* validation,
|
|
5671
|
+
* Validation.match({
|
|
5672
|
+
* passed: value => `Got ${value}`,
|
|
5673
|
+
* failed: errors => `Failed: ${errors.join(", ")}`
|
|
5674
|
+
* })
|
|
5675
|
+
* );
|
|
5591
5676
|
* ```
|
|
5592
5677
|
*/
|
|
5593
|
-
|
|
5594
|
-
Promise.all(data.map((t) => Deferred.to.Promise(t(signal)))).then((results) => {
|
|
5595
|
-
const [first, ...rest] = results;
|
|
5596
|
-
return Validation.productAll([first, ...rest]);
|
|
5597
|
-
})
|
|
5598
|
-
),
|
|
5678
|
+
match: (cases) => (data) => isPassed(data) ? cases.passed(data.value) : cases.failed(data.errors),
|
|
5599
5679
|
/**
|
|
5600
|
-
*
|
|
5680
|
+
* Returns the success value or a default value if the Validation is failed.
|
|
5681
|
+
* The default can be a different type, widening the result to `A | B`.
|
|
5601
5682
|
*
|
|
5602
5683
|
* @example
|
|
5603
5684
|
* ```ts
|
|
5604
|
-
* pipe(
|
|
5605
|
-
*
|
|
5606
|
-
*
|
|
5607
|
-
* ); // Task.Validation(Failed(["OOPS"]))
|
|
5685
|
+
* pipe(Validation.make.passed(5), Validation.getOrElse(() => 0)); // 5
|
|
5686
|
+
* pipe(Validation.make.failed("oops"), Validation.getOrElse(() => 0)); // 0
|
|
5687
|
+
* pipe(Validation.make.failed("oops"), Validation.getOrElse(() => null)); // null — typed as number | null
|
|
5608
5688
|
* ```
|
|
5609
5689
|
*/
|
|
5610
|
-
|
|
5690
|
+
getOrElse: (defaultValue) => (data) => isPassed(data) ? data.value : defaultValue(),
|
|
5611
5691
|
/**
|
|
5612
|
-
* Executes a side effect on the
|
|
5692
|
+
* Executes a side effect on the success value without changing the Validation.
|
|
5613
5693
|
*
|
|
5614
5694
|
* @example
|
|
5615
5695
|
* ```ts
|
|
5616
5696
|
* pipe(
|
|
5617
|
-
*
|
|
5618
|
-
*
|
|
5697
|
+
* Validation.make.passed(5),
|
|
5698
|
+
* Validation.tap(n => console.log("Value:", n)),
|
|
5699
|
+
* Validation.map(n => n * 2)
|
|
5619
5700
|
* );
|
|
5620
5701
|
* ```
|
|
5621
5702
|
*/
|
|
5622
|
-
|
|
5703
|
+
tap: (f) => (data) => {
|
|
5704
|
+
if (isPassed(data)) {
|
|
5705
|
+
f(data.value);
|
|
5706
|
+
}
|
|
5707
|
+
return data;
|
|
5708
|
+
},
|
|
5623
5709
|
/**
|
|
5624
|
-
*
|
|
5625
|
-
*
|
|
5710
|
+
* Executes a side effect on the accumulated errors without changing the Validation.
|
|
5711
|
+
* Useful for logging or reporting validation failures.
|
|
5626
5712
|
*
|
|
5627
5713
|
* @example
|
|
5628
5714
|
* ```ts
|
|
5629
|
-
*
|
|
5630
|
-
*
|
|
5631
|
-
*
|
|
5632
|
-
*
|
|
5715
|
+
* pipe(
|
|
5716
|
+
* Validation.make.failed("Name required"),
|
|
5717
|
+
* Validation.tapError(errors => console.error("validation failed:", errors)),
|
|
5718
|
+
* Validation.map(toUser)
|
|
5719
|
+
* );
|
|
5633
5720
|
* ```
|
|
5634
5721
|
*/
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
for (let i = 0; i < keys3.length; i++) {
|
|
5642
|
-
const res = results[i];
|
|
5643
|
-
if (Validation.is.passed(res)) {
|
|
5644
|
-
record[keys3[i]] = res.value;
|
|
5645
|
-
} else {
|
|
5646
|
-
errors.push(...res.errors);
|
|
5647
|
-
}
|
|
5648
|
-
}
|
|
5649
|
-
return isNonEmptyArr(errors) ? Validation.make.failedAll(errors) : Validation.make.passed(record);
|
|
5650
|
-
});
|
|
5651
|
-
})()),
|
|
5722
|
+
tapError: (f) => (data) => {
|
|
5723
|
+
if (isFailed(data)) {
|
|
5724
|
+
f(data.errors);
|
|
5725
|
+
}
|
|
5726
|
+
return data;
|
|
5727
|
+
},
|
|
5652
5728
|
/**
|
|
5653
|
-
*
|
|
5654
|
-
*
|
|
5655
|
-
*
|
|
5656
|
-
* @example
|
|
5657
|
-
* ```ts
|
|
5658
|
-
* const validate = Task.Validation.memoize(validateFormTask);
|
|
5659
|
-
* ```
|
|
5729
|
+
* Recovers from a Failed state by providing a fallback Validation.
|
|
5730
|
+
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
5731
|
+
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
5660
5732
|
*/
|
|
5661
|
-
|
|
5662
|
-
};
|
|
5663
|
-
|
|
5664
|
-
// src/Core/Task.ts
|
|
5665
|
-
var toPromise2 = (task, signal) => Deferred.to.Promise(task(signal));
|
|
5666
|
-
var fromPromise2 = (f) => (signal) => Deferred.from.Promise(f(signal));
|
|
5667
|
-
var getMs2 = (duration) => Duration.to.milliseconds(duration);
|
|
5668
|
-
var resolveTask = (value) => () => Deferred.from.Promise(globalThis.Promise.resolve(value));
|
|
5669
|
-
var syncTask = (f) => () => Deferred.from.Promise(globalThis.Promise.resolve(f()));
|
|
5670
|
-
var Task = {
|
|
5733
|
+
recover: (fallback) => (data) => isPassed(data) ? data : fallback(data.errors),
|
|
5671
5734
|
/**
|
|
5672
|
-
*
|
|
5735
|
+
* Recovers from a Failed state unless `isBlocked` returns true for any of the accumulated errors.
|
|
5736
|
+
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
5673
5737
|
*
|
|
5674
5738
|
* @example
|
|
5675
5739
|
* ```ts
|
|
5676
|
-
*
|
|
5677
|
-
*
|
|
5740
|
+
* pipe(
|
|
5741
|
+
* Validation.make.failed("field-error"),
|
|
5742
|
+
* Validation.recoverUnless(e => e === "fatal", () => Validation.make.passed(0))
|
|
5743
|
+
* ); // Passed(0)
|
|
5678
5744
|
* ```
|
|
5679
5745
|
*/
|
|
5680
|
-
|
|
5681
|
-
// ---
|
|
5682
|
-
|
|
5746
|
+
recoverUnless: (isBlocked, fallback) => (data) => isFailed(data) && !data.errors.some(isBlocked) ? fallback() : data,
|
|
5747
|
+
// --- to ---
|
|
5748
|
+
to: {
|
|
5683
5749
|
/**
|
|
5684
|
-
*
|
|
5685
|
-
*
|
|
5750
|
+
* Converts a Validation to a Result.
|
|
5751
|
+
* Passed becomes Ok.
|
|
5752
|
+
* Direct call converts Failed to Err with accumulated error list `NonEmptyArr<E>`.
|
|
5753
|
+
* Curried call converts Failed to Err with combined error `E2` via `combineErrors`.
|
|
5686
5754
|
*
|
|
5687
5755
|
* @example
|
|
5688
5756
|
* ```ts
|
|
5689
|
-
*
|
|
5690
|
-
*
|
|
5757
|
+
* Validation.to.Result(Validation.make.passed(42)); // Ok(42)
|
|
5758
|
+
* Validation.to.Result(Validation.make.failed("oops")); // Err(["oops"])
|
|
5759
|
+
* pipe(Validation.make.failed("oops"), Validation.to.Result(errors => errors.join(", "))); // Err("oops")
|
|
5691
5760
|
* ```
|
|
5692
5761
|
*/
|
|
5693
|
-
|
|
5762
|
+
Result: toResult,
|
|
5763
|
+
/**
|
|
5764
|
+
* Converts a Validation to a Maybe. `Passed` becomes `Some`; `Failed` becomes `None`
|
|
5765
|
+
* (errors are discarded).
|
|
5766
|
+
*
|
|
5767
|
+
* @example
|
|
5768
|
+
* ```ts
|
|
5769
|
+
* Validation.to.Maybe(Validation.make.passed(42)); // Some(42)
|
|
5770
|
+
* Validation.to.Maybe(Validation.make.failed("bad")); // None
|
|
5771
|
+
* ```
|
|
5772
|
+
*/
|
|
5773
|
+
Maybe: (data) => isPassed(data) ? Maybe.make.some(data.value) : Maybe.make.none()
|
|
5694
5774
|
},
|
|
5695
5775
|
/**
|
|
5696
|
-
*
|
|
5697
|
-
*
|
|
5776
|
+
* Combines two independent Validation instances into a tuple.
|
|
5777
|
+
* If both are Passed, returns Passed with both values as a tuple.
|
|
5778
|
+
* If either is Failed, accumulates errors from both sides.
|
|
5698
5779
|
*
|
|
5699
5780
|
* @example
|
|
5700
5781
|
* ```ts
|
|
5701
|
-
*
|
|
5702
|
-
*
|
|
5703
|
-
*
|
|
5704
|
-
* );
|
|
5782
|
+
* Validation.product(
|
|
5783
|
+
* Validation.make.passed("alice"),
|
|
5784
|
+
* Validation.make.passed(30)
|
|
5785
|
+
* ); // Passed(["alice", 30])
|
|
5786
|
+
*
|
|
5787
|
+
* Validation.product(
|
|
5788
|
+
* Validation.make.failed("Name required"),
|
|
5789
|
+
* Validation.make.failed("Age must be >= 0")
|
|
5790
|
+
* ); // Failed(["Name required", "Age must be >= 0"])
|
|
5705
5791
|
* ```
|
|
5706
5792
|
*/
|
|
5707
|
-
|
|
5793
|
+
product: (first, second) => {
|
|
5794
|
+
if (isPassed(first)) {
|
|
5795
|
+
return isPassed(second) ? makePassed([first.value, second.value]) : makeFailedAll(second.errors);
|
|
5796
|
+
}
|
|
5797
|
+
return isPassed(second) ? makeFailedAll(first.errors) : makeFailedAll([...first.errors, ...second.errors]);
|
|
5798
|
+
},
|
|
5708
5799
|
/**
|
|
5709
|
-
*
|
|
5800
|
+
* Combines a non-empty list of Validation instances, accumulating all errors.
|
|
5801
|
+
* If all are Passed, returns Passed with all values collected into an array.
|
|
5802
|
+
* If any are Failed, returns Failed with all accumulated errors.
|
|
5710
5803
|
*
|
|
5711
5804
|
* @example
|
|
5712
5805
|
* ```ts
|
|
5713
|
-
*
|
|
5714
|
-
*
|
|
5715
|
-
*
|
|
5716
|
-
*
|
|
5806
|
+
* Validation.productAll([
|
|
5807
|
+
* validateName(name),
|
|
5808
|
+
* validateEmail(email),
|
|
5809
|
+
* validateAge(age)
|
|
5810
|
+
* ]);
|
|
5811
|
+
* // Passed([name, email, age]) or Failed([...all errors])
|
|
5717
5812
|
* ```
|
|
5718
5813
|
*/
|
|
5719
|
-
|
|
5814
|
+
productAll: (data) => {
|
|
5815
|
+
const values3 = [];
|
|
5816
|
+
const errors = [];
|
|
5817
|
+
for (const v of data) {
|
|
5818
|
+
if (isPassed(v)) {
|
|
5819
|
+
values3.push(v.value);
|
|
5820
|
+
} else {
|
|
5821
|
+
errors.push(...v.errors);
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
return isNonEmptyArr(errors) ? makeFailedAll(errors) : makePassed(values3);
|
|
5825
|
+
},
|
|
5720
5826
|
/**
|
|
5721
|
-
*
|
|
5827
|
+
* Combines a record of Validations into a single Validation of a record.
|
|
5828
|
+
* Accumulates all failed branches' errors.
|
|
5722
5829
|
*
|
|
5723
5830
|
* @example
|
|
5724
5831
|
* ```ts
|
|
5725
|
-
*
|
|
5726
|
-
*
|
|
5727
|
-
*
|
|
5832
|
+
* Validation.struct({
|
|
5833
|
+
* name: Validation.make.passed("Alice"),
|
|
5834
|
+
* age: Validation.make.passed(30)
|
|
5835
|
+
* }); // Passed({ name: "Alice", age: 30 })
|
|
5728
5836
|
*
|
|
5729
|
-
*
|
|
5730
|
-
*
|
|
5731
|
-
*
|
|
5732
|
-
* )
|
|
5837
|
+
* Validation.struct({
|
|
5838
|
+
* name: Validation.make.failed("Name required"),
|
|
5839
|
+
* age: Validation.make.failed("Age must be >= 0")
|
|
5840
|
+
* }); // Failed(["Name required", "Age must be >= 0"])
|
|
5733
5841
|
* ```
|
|
5734
5842
|
*/
|
|
5735
|
-
|
|
5843
|
+
struct: (fields) => {
|
|
5844
|
+
const record = {};
|
|
5845
|
+
const errors = [];
|
|
5846
|
+
for (const key in fields) {
|
|
5847
|
+
if (Object.hasOwn(fields, key)) {
|
|
5848
|
+
const val = fields[key];
|
|
5849
|
+
if (isPassed(val)) {
|
|
5850
|
+
record[key] = val.value;
|
|
5851
|
+
} else {
|
|
5852
|
+
errors.push(...val.errors);
|
|
5853
|
+
}
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
return isNonEmptyArr(errors) ? makeFailedAll(errors) : makePassed(record);
|
|
5857
|
+
}
|
|
5858
|
+
};
|
|
5859
|
+
|
|
5860
|
+
// src/Core/TaskValidation.ts
|
|
5861
|
+
var makePassed2 = (value) => Task.resolve(Validation.make.passed(value));
|
|
5862
|
+
var makeFailed2 = (error) => Task.resolve(Validation.make.failed(error));
|
|
5863
|
+
var makeFailedAll2 = (errors) => Task.resolve(Validation.make.failedAll(errors));
|
|
5864
|
+
var TaskValidation = {
|
|
5865
|
+
make: {
|
|
5866
|
+
/**
|
|
5867
|
+
* Wraps a value in a passed Task.Validation.
|
|
5868
|
+
*
|
|
5869
|
+
* @example
|
|
5870
|
+
* ```ts
|
|
5871
|
+
* const task = Task.Validation.make.passed(42);
|
|
5872
|
+
* const res = await task(); // Passed(42)
|
|
5873
|
+
* ```
|
|
5874
|
+
*/
|
|
5875
|
+
passed: makePassed2,
|
|
5876
|
+
/**
|
|
5877
|
+
* Creates a failed Task.Validation with a single error.
|
|
5878
|
+
*
|
|
5879
|
+
* @example
|
|
5880
|
+
* ```ts
|
|
5881
|
+
* const task = Task.Validation.make.failed("invalid");
|
|
5882
|
+
* const res = await task(); // Failed(["invalid"])
|
|
5883
|
+
* ```
|
|
5884
|
+
*/
|
|
5885
|
+
failed: makeFailed2,
|
|
5886
|
+
/**
|
|
5887
|
+
* Creates a failed Task.Validation from multiple errors.
|
|
5888
|
+
*
|
|
5889
|
+
* @example
|
|
5890
|
+
* ```ts
|
|
5891
|
+
* const task = Task.Validation.make.failedAll(["err1", "err2"]);
|
|
5892
|
+
* const res = await task(); // Failed(["err1", "err2"])
|
|
5893
|
+
* ```
|
|
5894
|
+
*/
|
|
5895
|
+
failedAll: makeFailedAll2
|
|
5896
|
+
},
|
|
5897
|
+
// --- from ---
|
|
5898
|
+
from: {
|
|
5899
|
+
/**
|
|
5900
|
+
* Lifts a Validation into a Task.Validation.
|
|
5901
|
+
*
|
|
5902
|
+
* @example
|
|
5903
|
+
* ```ts
|
|
5904
|
+
* Task.Validation.from.Validation(Validation.make.passed(42));
|
|
5905
|
+
* ```
|
|
5906
|
+
*/
|
|
5907
|
+
Validation: (validation) => Task.resolve(validation),
|
|
5908
|
+
/**
|
|
5909
|
+
* Creates a Task.Validation from a nullable value.
|
|
5910
|
+
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
5911
|
+
* Otherwise, returns Passed.
|
|
5912
|
+
*
|
|
5913
|
+
* @example
|
|
5914
|
+
* ```ts
|
|
5915
|
+
* Task.Validation.from.nullable(() => "missing")(42); // resolves to Passed(42)
|
|
5916
|
+
* Task.Validation.from.nullable(() => "missing")(null); // resolves to Failed(["missing"])
|
|
5917
|
+
* ```
|
|
5918
|
+
*/
|
|
5919
|
+
nullable: (onNull) => (value) => Task.resolve(
|
|
5920
|
+
value === null || value === void 0 ? Validation.make.failed(onNull()) : Validation.make.passed(value)
|
|
5921
|
+
),
|
|
5922
|
+
/**
|
|
5923
|
+
* Creates a Task.Validation from a Maybe.
|
|
5924
|
+
* Some becomes Passed, None becomes Failed with the error from onNone.
|
|
5925
|
+
*
|
|
5926
|
+
* @example
|
|
5927
|
+
* ```ts
|
|
5928
|
+
* Task.Validation.from.Maybe(() => "empty")(Maybe.make.some(42)); // resolves to Passed(42)
|
|
5929
|
+
* Task.Validation.from.Maybe(() => "empty")(Maybe.make.none()); // resolves to Failed(["empty"])
|
|
5930
|
+
* ```
|
|
5931
|
+
*/
|
|
5932
|
+
Maybe: (onNone) => (maybe) => Task.resolve(
|
|
5933
|
+
Maybe.is.none(maybe) ? Validation.make.failed(onNone()) : Validation.make.passed(maybe.value)
|
|
5934
|
+
),
|
|
5935
|
+
/**
|
|
5936
|
+
* Creates a Task.Validation from a Result.
|
|
5937
|
+
* Ok becomes Passed, Err(e) becomes Failed([e]).
|
|
5938
|
+
*
|
|
5939
|
+
* @example
|
|
5940
|
+
* ```ts
|
|
5941
|
+
* Task.Validation.from.Result(Result.make.ok(42)); // resolves to Passed(42)
|
|
5942
|
+
* Task.Validation.from.Result(Result.make.err("bad")); // resolves to Failed(["bad"])
|
|
5943
|
+
* ```
|
|
5944
|
+
*/
|
|
5945
|
+
Result: (result) => Task.resolve(Validation.from.Result(result))
|
|
5946
|
+
},
|
|
5947
|
+
// --- to ---
|
|
5948
|
+
to: {
|
|
5949
|
+
/**
|
|
5950
|
+
* Converts a `Task.Validation` to a `Task.Result`, combining accumulated errors using `combineErrors`.
|
|
5951
|
+
* `Passed(a)` becomes `Ok(a)`; `Failed(errors)` becomes `Err(combineErrors(errors))`.
|
|
5952
|
+
*
|
|
5953
|
+
* @example
|
|
5954
|
+
* ```ts
|
|
5955
|
+
* Task.Validation.to.Result((errors) => errors.join(", "))(validationTask);
|
|
5956
|
+
* ```
|
|
5957
|
+
*/
|
|
5958
|
+
Result: (combineErrors) => (data) => Task.map(Validation.to.Result(combineErrors))(data),
|
|
5959
|
+
/**
|
|
5960
|
+
* Converts a `Task.Validation` to a `Task.Maybe`.
|
|
5961
|
+
* `Passed(a)` becomes `Some(a)`; `Failed(errors)` becomes `None` (errors are discarded).
|
|
5962
|
+
*
|
|
5963
|
+
* @example
|
|
5964
|
+
* ```ts
|
|
5965
|
+
* Task.Validation.to.Maybe(validationTask);
|
|
5966
|
+
* ```
|
|
5967
|
+
*/
|
|
5968
|
+
Maybe: (data) => Task.map(Validation.to.Maybe)(data)
|
|
5969
|
+
},
|
|
5736
5970
|
/**
|
|
5737
|
-
*
|
|
5738
|
-
*
|
|
5971
|
+
* Creates a Task.Validation from a Promise-returning thunk that may throw or reject.
|
|
5972
|
+
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
5973
|
+
* The thunk optionally receives an `AbortSignal` forwarded from the call site.
|
|
5739
5974
|
*
|
|
5740
5975
|
* @example
|
|
5741
5976
|
* ```ts
|
|
5742
|
-
* const
|
|
5743
|
-
*
|
|
5744
|
-
*
|
|
5745
|
-
*
|
|
5746
|
-
* Task.ap(Task.resolve(3))
|
|
5747
|
-
* )(); // Deferred<8>
|
|
5977
|
+
* const loadConfig = Task.Validation.tryCatch(
|
|
5978
|
+
* (signal) => configStore.get("default", { signal }),
|
|
5979
|
+
* { onError: (e) => `Failed to load config: ${e}` }
|
|
5980
|
+
* );
|
|
5748
5981
|
* ```
|
|
5749
5982
|
*/
|
|
5750
|
-
|
|
5983
|
+
tryCatch: (f, options) => (signal) => Deferred.from.Promise(
|
|
5984
|
+
// oxlint-disable-next-line require-await
|
|
5985
|
+
globalThis.Promise.resolve().then(async () => f(signal)).then(Validation.make.passed).catch(
|
|
5986
|
+
(error) => Validation.make.failed(options.onError(error))
|
|
5987
|
+
)
|
|
5988
|
+
),
|
|
5751
5989
|
/**
|
|
5752
|
-
*
|
|
5753
|
-
|
|
5990
|
+
* Transforms the success value inside a Task.Validation.
|
|
5991
|
+
*/
|
|
5992
|
+
map: (f) => (data) => Task.map(Validation.map(f))(data),
|
|
5993
|
+
/**
|
|
5994
|
+
* Applies a function wrapped in a Task.Validation to a value wrapped in a
|
|
5995
|
+
* Task.Validation. Both Tasks run in parallel and errors from both sides
|
|
5996
|
+
* are accumulated.
|
|
5754
5997
|
*
|
|
5755
5998
|
* @example
|
|
5756
5999
|
* ```ts
|
|
5757
6000
|
* pipe(
|
|
5758
|
-
*
|
|
5759
|
-
* Task.
|
|
5760
|
-
* Task.
|
|
5761
|
-
* );
|
|
6001
|
+
* Task.Validation.make.passed((name: string) => (age: number) => ({ name, age })),
|
|
6002
|
+
* Task.Validation.ap(validateName(name)),
|
|
6003
|
+
* Task.Validation.ap(validateAge(age))
|
|
6004
|
+
* )();
|
|
5762
6005
|
* ```
|
|
5763
6006
|
*/
|
|
5764
|
-
|
|
5765
|
-
(signal)
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
})
|
|
6007
|
+
ap: (arg) => (data) => (signal) => Deferred.from.Promise(
|
|
6008
|
+
Promise.all([Deferred.to.Promise(data(signal)), Deferred.to.Promise(arg(signal))]).then(
|
|
6009
|
+
([vf, va]) => Validation.ap(va)(vf)
|
|
6010
|
+
)
|
|
5769
6011
|
),
|
|
5770
6012
|
/**
|
|
5771
|
-
*
|
|
6013
|
+
* Extracts a value from a Task.Validation by providing handlers for both cases.
|
|
6014
|
+
*/
|
|
6015
|
+
fold: (onFailed, onPassed) => (data) => Task.map(Validation.fold(onFailed, onPassed))(data),
|
|
6016
|
+
/**
|
|
6017
|
+
* Pattern matches on a Task.Validation, returning a Task of the result.
|
|
5772
6018
|
*
|
|
5773
6019
|
* @example
|
|
5774
6020
|
* ```ts
|
|
5775
|
-
*
|
|
5776
|
-
*
|
|
6021
|
+
* pipe(
|
|
6022
|
+
* validateForm(input),
|
|
6023
|
+
* Task.Validation.match({
|
|
6024
|
+
* passed: data => save(data),
|
|
6025
|
+
* failed: errors => showErrors(errors)
|
|
6026
|
+
* })
|
|
6027
|
+
* )();
|
|
5777
6028
|
* ```
|
|
5778
6029
|
*/
|
|
5779
|
-
|
|
5780
|
-
(signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))
|
|
5781
|
-
),
|
|
6030
|
+
match: (cases) => (data) => Task.map(Validation.match(cases))(data),
|
|
5782
6031
|
/**
|
|
5783
|
-
*
|
|
5784
|
-
*
|
|
6032
|
+
* Returns the success value or a default value if the Task.Validation is failed.
|
|
6033
|
+
* The default can be a different type, widening the result to `Task<A | B>`.
|
|
6034
|
+
*/
|
|
6035
|
+
getOrElse: (defaultValue) => (data) => Task.map(Validation.getOrElse(defaultValue))(data),
|
|
6036
|
+
/**
|
|
6037
|
+
* Executes a side effect on the success value without changing the Task.Validation.
|
|
6038
|
+
* Useful for logging or debugging.
|
|
6039
|
+
*/
|
|
6040
|
+
tap: (f) => (data) => Task.map(Validation.tap(f))(data),
|
|
6041
|
+
/**
|
|
6042
|
+
* Recovers from a Failed state by providing a fallback Task.Validation.
|
|
6043
|
+
* The fallback receives the accumulated error list so callers can inspect which errors occurred.
|
|
6044
|
+
* The fallback can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
6045
|
+
*/
|
|
6046
|
+
recover: (fallback) => (data) => Task.chain(
|
|
6047
|
+
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : fallback(validation.errors)
|
|
6048
|
+
)(data),
|
|
6049
|
+
/**
|
|
6050
|
+
* Recovers from a Failed state unless the predicate `isBlocked` returns true for the accumulated errors.
|
|
6051
|
+
* The fallback receives the accumulated errors and can produce a different success type, widening the result to `Task.Validation<E, A | B>`.
|
|
5785
6052
|
*
|
|
5786
6053
|
* @example
|
|
5787
6054
|
* ```ts
|
|
5788
6055
|
* pipe(
|
|
5789
|
-
*
|
|
5790
|
-
* Task.
|
|
5791
|
-
*
|
|
6056
|
+
* validationTask,
|
|
6057
|
+
* Task.Validation.recoverUnless(
|
|
6058
|
+
* (errors) => errors.includes("fatal"),
|
|
6059
|
+
* (errors) => Task.Validation.make.passed("fallback")
|
|
6060
|
+
* )
|
|
6061
|
+
* );
|
|
5792
6062
|
* ```
|
|
5793
6063
|
*/
|
|
5794
|
-
|
|
5795
|
-
(
|
|
5796
|
-
|
|
5797
|
-
const onAbort = () => {
|
|
5798
|
-
clearTimeout(timerId);
|
|
5799
|
-
res(toPromise2(data, signal));
|
|
5800
|
-
};
|
|
5801
|
-
if (signal) {
|
|
5802
|
-
if (signal.aborted) {
|
|
5803
|
-
return res(toPromise2(data, signal));
|
|
5804
|
-
}
|
|
5805
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5806
|
-
}
|
|
5807
|
-
timerId = setTimeout(() => {
|
|
5808
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5809
|
-
res(toPromise2(data, signal));
|
|
5810
|
-
}, getMs2(duration));
|
|
5811
|
-
})
|
|
5812
|
-
),
|
|
6064
|
+
recoverUnless: (isBlocked, fallback) => (data) => Task.chain(
|
|
6065
|
+
(validation) => Validation.is.passed(validation) ? Task.resolve(validation) : isBlocked(validation.errors) ? Task.resolve(validation) : fallback(validation.errors)
|
|
6066
|
+
)(data),
|
|
5813
6067
|
/**
|
|
5814
|
-
* Runs
|
|
5815
|
-
*
|
|
6068
|
+
* Runs two Task.Validations concurrently and combines their results into a tuple.
|
|
6069
|
+
* If both are Passed, returns Passed with both values. If either fails, accumulates
|
|
6070
|
+
* errors from both sides.
|
|
5816
6071
|
*
|
|
5817
6072
|
* @example
|
|
5818
6073
|
* ```ts
|
|
5819
|
-
*
|
|
5820
|
-
*
|
|
5821
|
-
*
|
|
5822
|
-
* )(); //
|
|
6074
|
+
* await Task.Validation.product(
|
|
6075
|
+
* validateName(form.name),
|
|
6076
|
+
* validateAge(form.age),
|
|
6077
|
+
* )(); // Passed(["Alice", 30]) or Failed([...errors])
|
|
5823
6078
|
* ```
|
|
5824
6079
|
*/
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
const results = [];
|
|
5831
|
-
const wait = () => new Promise((r) => {
|
|
5832
|
-
let timerId;
|
|
5833
|
-
const onAbort = () => {
|
|
5834
|
-
clearTimeout(timerId);
|
|
5835
|
-
r();
|
|
5836
|
-
};
|
|
5837
|
-
if (signal) {
|
|
5838
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5839
|
-
}
|
|
5840
|
-
timerId = setTimeout(() => {
|
|
5841
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5842
|
-
r();
|
|
5843
|
-
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
5844
|
-
});
|
|
5845
|
-
const run = (left) => {
|
|
5846
|
-
if (signal?.aborted) {
|
|
5847
|
-
return Promise.resolve(results);
|
|
5848
|
-
}
|
|
5849
|
-
return toPromise2(task, signal).then((a) => {
|
|
5850
|
-
results.push(a);
|
|
5851
|
-
if (left <= 1 || signal?.aborted) {
|
|
5852
|
-
return results;
|
|
5853
|
-
}
|
|
5854
|
-
return wait().then(() => run(left - 1));
|
|
5855
|
-
});
|
|
5856
|
-
};
|
|
5857
|
-
return run(times);
|
|
5858
|
-
}),
|
|
6080
|
+
product: (first, second) => (signal) => Deferred.from.Promise(
|
|
6081
|
+
Promise.all([Deferred.to.Promise(first(signal)), Deferred.to.Promise(second(signal))]).then(
|
|
6082
|
+
([va, vb]) => Validation.product(va, vb)
|
|
6083
|
+
)
|
|
6084
|
+
),
|
|
5859
6085
|
/**
|
|
5860
|
-
* Runs
|
|
5861
|
-
*
|
|
5862
|
-
*
|
|
5863
|
-
* regardless of whether the predicate was satisfied.
|
|
6086
|
+
* Runs all Task.Validations concurrently and collects results.
|
|
6087
|
+
* If all are Passed, returns Passed with all values as an array.
|
|
6088
|
+
* If any fail, returns Failed with all accumulated errors.
|
|
5864
6089
|
*
|
|
5865
6090
|
* @example
|
|
5866
6091
|
* ```ts
|
|
5867
|
-
*
|
|
5868
|
-
*
|
|
5869
|
-
*
|
|
5870
|
-
*
|
|
6092
|
+
* await Task.Validation.productAll([
|
|
6093
|
+
* validateName(form.name),
|
|
6094
|
+
* validateEmail(form.email),
|
|
6095
|
+
* validateAge(form.age),
|
|
6096
|
+
* ])(); // Passed([name, email, age]) or Failed([...all errors])
|
|
5871
6097
|
* ```
|
|
5872
6098
|
*/
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
r();
|
|
5880
|
-
};
|
|
5881
|
-
if (signal) {
|
|
5882
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
5883
|
-
}
|
|
5884
|
-
timerId = setTimeout(() => {
|
|
5885
|
-
signal?.removeEventListener("abort", onAbort);
|
|
5886
|
-
r();
|
|
5887
|
-
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
5888
|
-
});
|
|
5889
|
-
const run = (attempt, lastValue) => {
|
|
5890
|
-
if (signal?.aborted && lastValue !== void 0) {
|
|
5891
|
-
return Promise.resolve(lastValue);
|
|
5892
|
-
}
|
|
5893
|
-
return toPromise2(task, signal).then((a) => {
|
|
5894
|
-
if (predicate(a)) {
|
|
5895
|
-
return a;
|
|
5896
|
-
}
|
|
5897
|
-
if (maxAttempts !== void 0 && attempt >= maxAttempts) {
|
|
5898
|
-
return a;
|
|
5899
|
-
}
|
|
5900
|
-
if (signal?.aborted) {
|
|
5901
|
-
return a;
|
|
5902
|
-
}
|
|
5903
|
-
return wait().then(() => run(attempt + 1, a));
|
|
5904
|
-
});
|
|
5905
|
-
};
|
|
5906
|
-
return run(1);
|
|
5907
|
-
}),
|
|
6099
|
+
productAll: (data) => (signal) => Deferred.from.Promise(
|
|
6100
|
+
Promise.all(data.map((t) => Deferred.to.Promise(t(signal)))).then((results) => {
|
|
6101
|
+
const [first, ...rest] = results;
|
|
6102
|
+
return Validation.productAll([first, ...rest]);
|
|
6103
|
+
})
|
|
6104
|
+
),
|
|
5908
6105
|
/**
|
|
5909
|
-
*
|
|
5910
|
-
* immediately. When one resolves, the other tasks are cancelled (aborted)
|
|
5911
|
-
* downstream.
|
|
6106
|
+
* Transforms all accumulated errors inside a Task.Validation.
|
|
5912
6107
|
*
|
|
5913
6108
|
* @example
|
|
5914
6109
|
* ```ts
|
|
5915
|
-
*
|
|
5916
|
-
*
|
|
5917
|
-
*
|
|
5918
|
-
*
|
|
6110
|
+
* pipe(
|
|
6111
|
+
* Task.Validation.make.failed("oops"),
|
|
6112
|
+
* Task.Validation.mapError(e => e.toUpperCase())
|
|
6113
|
+
* ); // Task.Validation(Failed(["OOPS"]))
|
|
5919
6114
|
* ```
|
|
5920
6115
|
*/
|
|
5921
|
-
|
|
5922
|
-
if (tasks.length === 0) {
|
|
5923
|
-
return () => Deferred.from.Promise(new Promise(() => {
|
|
5924
|
-
}));
|
|
5925
|
-
}
|
|
5926
|
-
return fromPromise2((outerSignal) => {
|
|
5927
|
-
const controllers = tasks.map(() => new AbortController());
|
|
5928
|
-
const onOuterAbort = () => {
|
|
5929
|
-
for (const ctrl of controllers) {
|
|
5930
|
-
ctrl.abort();
|
|
5931
|
-
}
|
|
5932
|
-
};
|
|
5933
|
-
if (outerSignal) {
|
|
5934
|
-
if (outerSignal.aborted) {
|
|
5935
|
-
onOuterAbort();
|
|
5936
|
-
} else {
|
|
5937
|
-
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
5938
|
-
}
|
|
5939
|
-
}
|
|
5940
|
-
const promises = tasks.map((task, idx) => {
|
|
5941
|
-
const ctrl = controllers[idx];
|
|
5942
|
-
return toPromise2(task, ctrl.signal).then((result) => {
|
|
5943
|
-
for (let i = 0; i < controllers.length; i++) {
|
|
5944
|
-
if (i !== idx) {
|
|
5945
|
-
controllers[i].abort();
|
|
5946
|
-
}
|
|
5947
|
-
}
|
|
5948
|
-
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
5949
|
-
return result;
|
|
5950
|
-
});
|
|
5951
|
-
});
|
|
5952
|
-
return Promise.race(promises);
|
|
5953
|
-
});
|
|
5954
|
-
},
|
|
6116
|
+
mapError: (f) => (data) => Task.map(Validation.mapError(f))(data),
|
|
5955
6117
|
/**
|
|
5956
|
-
*
|
|
5957
|
-
* Forward-propagates the call site's AbortSignal to all subtasks concurrently.
|
|
6118
|
+
* Executes a side effect on the accumulated errors without changing the Task.Validation.
|
|
5958
6119
|
*
|
|
5959
6120
|
* @example
|
|
5960
6121
|
* ```ts
|
|
5961
|
-
*
|
|
5962
|
-
*
|
|
6122
|
+
* pipe(
|
|
6123
|
+
* Task.Validation.make.failed("invalid name"),
|
|
6124
|
+
* Task.Validation.tapError(errs => logger.error(errs))
|
|
6125
|
+
* );
|
|
5963
6126
|
* ```
|
|
5964
6127
|
*/
|
|
5965
|
-
|
|
6128
|
+
tapError: (f) => (data) => Task.map(Validation.tapError(f))(data),
|
|
5966
6129
|
/**
|
|
5967
|
-
*
|
|
5968
|
-
*
|
|
6130
|
+
* Combines a record of Task.Validations into a single Task.Validation of a record.
|
|
6131
|
+
* Evaluates fields in parallel and accumulates all validation errors.
|
|
5969
6132
|
*
|
|
5970
6133
|
* @example
|
|
5971
6134
|
* ```ts
|
|
5972
|
-
*
|
|
5973
|
-
*
|
|
5974
|
-
*
|
|
5975
|
-
*
|
|
5976
|
-
* // log = [1, 2, 3] — tasks ran in order
|
|
6135
|
+
* Task.Validation.struct({
|
|
6136
|
+
* name: Task.Validation.make.passed("Alice"),
|
|
6137
|
+
* age: Task.Validation.make.passed(30)
|
|
6138
|
+
* }); // Task.Validation({ name: "Alice", age: 30 })
|
|
5977
6139
|
* ```
|
|
5978
6140
|
*/
|
|
5979
|
-
|
|
5980
|
-
const
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
6141
|
+
struct: (fields) => (signal) => Deferred.from.Promise((() => {
|
|
6142
|
+
const keys3 = Object.keys(fields);
|
|
6143
|
+
const promises = keys3.map((key) => Deferred.to.Promise(fields[key](signal)));
|
|
6144
|
+
return Promise.all(promises).then((results) => {
|
|
6145
|
+
const record = {};
|
|
6146
|
+
const errors = [];
|
|
6147
|
+
for (let i = 0; i < keys3.length; i++) {
|
|
6148
|
+
const res = results[i];
|
|
6149
|
+
if (Validation.is.passed(res)) {
|
|
6150
|
+
record[keys3[i]] = res.value;
|
|
6151
|
+
} else {
|
|
6152
|
+
errors.push(...res.errors);
|
|
6153
|
+
}
|
|
5984
6154
|
}
|
|
5985
|
-
|
|
5986
|
-
}
|
|
5987
|
-
|
|
5988
|
-
}),
|
|
6155
|
+
return isNonEmptyArr(errors) ? Validation.make.failedAll(errors) : Validation.make.passed(record);
|
|
6156
|
+
});
|
|
6157
|
+
})()),
|
|
5989
6158
|
/**
|
|
5990
|
-
*
|
|
5991
|
-
*
|
|
5992
|
-
* `AbortSignal` that fires when the deadline passes, so asynchronous operations
|
|
5993
|
-
* that accept a signal are cancelled rather than left dangling.
|
|
6159
|
+
* Creates a memoized version of a Task.Validation. The task is executed at most once on first call,
|
|
6160
|
+
* and its resolved Validation is cached for all subsequent calls.
|
|
5994
6161
|
*
|
|
5995
6162
|
* @example
|
|
5996
6163
|
* ```ts
|
|
5997
|
-
*
|
|
5998
|
-
* heavyComputation,
|
|
5999
|
-
* Task.timeout({ duration: Duration.seconds(5), onTimeout: () => "timed out" }),
|
|
6000
|
-
* Task.Result.chain(processResult)
|
|
6001
|
-
* );
|
|
6164
|
+
* const validate = Task.Validation.memoize(validateFormTask);
|
|
6002
6165
|
* ```
|
|
6003
6166
|
*/
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
cleanUp = () => {
|
|
6015
|
-
clearTimeout(timerId);
|
|
6016
|
-
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
6017
|
-
};
|
|
6018
|
-
if (outerSignal) {
|
|
6019
|
-
if (outerSignal.aborted) {
|
|
6020
|
-
controller.abort();
|
|
6021
|
-
} else {
|
|
6022
|
-
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
6023
|
-
}
|
|
6024
|
-
}
|
|
6025
|
-
return Promise.race([
|
|
6026
|
-
toPromise2(task, controller.signal).then((a) => {
|
|
6027
|
-
cleanUp();
|
|
6028
|
-
return Result.make.ok(a);
|
|
6029
|
-
}),
|
|
6030
|
-
new Promise((res) => {
|
|
6031
|
-
timerId = setTimeout(() => {
|
|
6032
|
-
controller.abort();
|
|
6033
|
-
cleanUp();
|
|
6034
|
-
res(Result.make.err(onTimeout()));
|
|
6035
|
-
}, getMs2(duration));
|
|
6036
|
-
})
|
|
6037
|
-
]);
|
|
6038
|
-
}),
|
|
6167
|
+
memoize: (task) => Task.memoize(task)
|
|
6168
|
+
};
|
|
6169
|
+
|
|
6170
|
+
// src/Core/Task.ts
|
|
6171
|
+
var toPromise2 = (task, signal) => Deferred.to.Promise(task(signal));
|
|
6172
|
+
var fromPromise2 = (f) => (signal) => Deferred.from.Promise(f(signal));
|
|
6173
|
+
var getMs2 = (duration) => Duration.to.milliseconds(duration);
|
|
6174
|
+
var resolveTask = (value) => () => Deferred.from.Promise(globalThis.Promise.resolve(value));
|
|
6175
|
+
var syncTask = (f) => () => Deferred.from.Promise(globalThis.Promise.resolve(f()));
|
|
6176
|
+
var Task = {
|
|
6039
6177
|
/**
|
|
6040
|
-
* Creates a Task
|
|
6041
|
-
* current in-flight call immediately. Unlike a one-shot abort, calling `task()`
|
|
6042
|
-
* again after `abort()` starts a fresh call with a new signal.
|
|
6043
|
-
*
|
|
6044
|
-
* Each invocation of `task()` automatically cancels the previous in-flight call,
|
|
6045
|
-
* making it safe to call repeatedly (e.g. on user input) without leaking promises.
|
|
6046
|
-
*
|
|
6047
|
-
* If an outer signal is also present (passed at the call site), aborting it
|
|
6048
|
-
* propagates into the internal controller.
|
|
6178
|
+
* Creates a Task that immediately resolves to the given value.
|
|
6049
6179
|
*
|
|
6050
6180
|
* @example
|
|
6051
6181
|
* ```ts
|
|
6052
|
-
* const
|
|
6053
|
-
*
|
|
6054
|
-
* );
|
|
6055
|
-
*
|
|
6056
|
-
* onUnmount(abort);
|
|
6057
|
-
* await poll();
|
|
6182
|
+
* const task = Task.resolve(42);
|
|
6183
|
+
* const value = await task(); // 42
|
|
6058
6184
|
* ```
|
|
6059
6185
|
*/
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
return Deferred.from.Promise(factory(controller.signal));
|
|
6075
|
-
};
|
|
6076
|
-
return { task, abort };
|
|
6186
|
+
resolve: resolveTask,
|
|
6187
|
+
// --- from ---
|
|
6188
|
+
from: {
|
|
6189
|
+
/**
|
|
6190
|
+
* Creates a Task from a lazy synchronous thunk.
|
|
6191
|
+
* Unlike `Task.resolve(f())`, `from.sync` does not evaluate `f` until the Task is called.
|
|
6192
|
+
*
|
|
6193
|
+
* @example
|
|
6194
|
+
* ```ts
|
|
6195
|
+
* const t = Task.from.sync(() => Date.now()); // Date.now() not called yet
|
|
6196
|
+
* const ts = await t(); // called here, every time
|
|
6197
|
+
* ```
|
|
6198
|
+
*/
|
|
6199
|
+
sync: syncTask
|
|
6077
6200
|
},
|
|
6078
6201
|
/**
|
|
6079
|
-
*
|
|
6202
|
+
* Wraps a Promise-returning thunk that may throw or reject,
|
|
6203
|
+
* trapping errors with a fallback function and returning a guaranteed `Task<A>`.
|
|
6080
6204
|
*
|
|
6081
6205
|
* @example
|
|
6082
6206
|
* ```ts
|
|
6083
|
-
* const
|
|
6084
|
-
*
|
|
6085
|
-
*
|
|
6086
|
-
* Task.run(),
|
|
6207
|
+
* const loadConfig = Task.tryCatch(
|
|
6208
|
+
* () => configStore.get("default"),
|
|
6209
|
+
* { onError: () => DEFAULT_CONFIG }
|
|
6087
6210
|
* );
|
|
6088
6211
|
* ```
|
|
6089
6212
|
*/
|
|
6090
|
-
|
|
6213
|
+
tryCatch: (f, options) => fromPromise2((signal) => globalThis.Promise.resolve().then(() => f(signal)).catch((err2) => options.onError(err2))),
|
|
6091
6214
|
/**
|
|
6092
|
-
*
|
|
6093
|
-
* Initiates the pipeline accumulator record.
|
|
6215
|
+
* Transforms the value inside a Task.
|
|
6094
6216
|
*
|
|
6095
6217
|
* @example
|
|
6096
6218
|
* ```ts
|
|
6097
|
-
* pipe(
|
|
6219
|
+
* pipe(
|
|
6220
|
+
* Task.resolve(5),
|
|
6221
|
+
* Task.map(n => n * 2)
|
|
6222
|
+
* )(); // Deferred<10>
|
|
6098
6223
|
* ```
|
|
6099
6224
|
*/
|
|
6100
|
-
|
|
6225
|
+
map: (f) => (data) => fromPromise2((signal) => toPromise2(data, signal).then(f)),
|
|
6101
6226
|
/**
|
|
6102
|
-
*
|
|
6227
|
+
* Chains Task computations. Passes the resolved value of the first Task to f.
|
|
6103
6228
|
*
|
|
6104
6229
|
* @example
|
|
6105
6230
|
* ```ts
|
|
6231
|
+
* const readUserId: Task<string> = Task.resolve(session.userId);
|
|
6232
|
+
* const loadPrefs = (id: string): Task<Preferences> =>
|
|
6233
|
+
* Task.resolve(prefsCache.get(id));
|
|
6234
|
+
*
|
|
6106
6235
|
* pipe(
|
|
6107
|
-
*
|
|
6108
|
-
* Task.
|
|
6109
|
-
* ); //
|
|
6236
|
+
* readUserId,
|
|
6237
|
+
* Task.chain(loadPrefs)
|
|
6238
|
+
* )(); // Deferred<Preferences>
|
|
6110
6239
|
* ```
|
|
6111
6240
|
*/
|
|
6112
|
-
|
|
6113
|
-
(signal) => toPromise2(data, signal).then(
|
|
6114
|
-
(a) => toPromise2(f(a), signal).then((b) => ({ ...a, [key]: b }))
|
|
6115
|
-
)
|
|
6116
|
-
),
|
|
6241
|
+
chain: (f) => (data) => fromPromise2((signal) => toPromise2(data, signal).then((a) => toPromise2(f(a), signal))),
|
|
6117
6242
|
/**
|
|
6118
|
-
*
|
|
6119
|
-
*
|
|
6243
|
+
* Applies a function wrapped in a Task to a value wrapped in a Task.
|
|
6244
|
+
* Both Tasks run in parallel.
|
|
6120
6245
|
*
|
|
6121
6246
|
* @example
|
|
6122
6247
|
* ```ts
|
|
6123
|
-
* const
|
|
6124
|
-
*
|
|
6125
|
-
*
|
|
6248
|
+
* const add = (a: number) => (b: number) => a + b;
|
|
6249
|
+
* pipe(
|
|
6250
|
+
* Task.resolve(add),
|
|
6251
|
+
* Task.ap(Task.resolve(5)),
|
|
6252
|
+
* Task.ap(Task.resolve(3))
|
|
6253
|
+
* )(); // Deferred<8>
|
|
6126
6254
|
* ```
|
|
6127
6255
|
*/
|
|
6128
|
-
|
|
6129
|
-
let cached = null;
|
|
6130
|
-
return (signal) => {
|
|
6131
|
-
if (cached === null) {
|
|
6132
|
-
cached = task(signal);
|
|
6133
|
-
}
|
|
6134
|
-
return cached;
|
|
6135
|
-
};
|
|
6136
|
-
},
|
|
6256
|
+
ap: (arg) => (data) => fromPromise2((signal) => Promise.all([toPromise2(data, signal), toPromise2(arg, signal)]).then(([f, a]) => f(a))),
|
|
6137
6257
|
/**
|
|
6138
|
-
*
|
|
6258
|
+
* Executes a side effect on the value without changing the Task.
|
|
6259
|
+
* Useful for logging or debugging.
|
|
6139
6260
|
*
|
|
6140
6261
|
* @example
|
|
6141
6262
|
* ```ts
|
|
6142
|
-
*
|
|
6143
|
-
*
|
|
6144
|
-
* Task.
|
|
6263
|
+
* pipe(
|
|
6264
|
+
* loadConfig,
|
|
6265
|
+
* Task.tap(cfg => console.log("Config:", cfg)),
|
|
6266
|
+
* Task.map(buildReport)
|
|
6145
6267
|
* );
|
|
6146
6268
|
* ```
|
|
6147
6269
|
*/
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
return res;
|
|
6155
|
-
})
|
|
6156
|
-
);
|
|
6157
|
-
},
|
|
6270
|
+
tap: (f) => (data) => fromPromise2(
|
|
6271
|
+
(signal) => toPromise2(data, signal).then((a) => {
|
|
6272
|
+
f(a);
|
|
6273
|
+
return a;
|
|
6274
|
+
})
|
|
6275
|
+
),
|
|
6158
6276
|
/**
|
|
6159
|
-
*
|
|
6277
|
+
* Runs multiple Tasks in parallel and collects their results.
|
|
6160
6278
|
*
|
|
6161
6279
|
* @example
|
|
6162
6280
|
* ```ts
|
|
6163
|
-
*
|
|
6164
|
-
*
|
|
6281
|
+
* Task.all([loadConfig, detectLocale, loadTheme])();
|
|
6282
|
+
* // Deferred<[Config, string, Theme]>
|
|
6165
6283
|
* ```
|
|
6166
6284
|
*/
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
return fn;
|
|
6171
|
-
},
|
|
6172
|
-
Maybe: TaskMaybe,
|
|
6173
|
-
Result: TaskResult,
|
|
6174
|
-
Validation: TaskValidation
|
|
6175
|
-
};
|
|
6176
|
-
|
|
6177
|
-
// src/Core/These.ts
|
|
6178
|
-
var makeFirst = (value) => ({ kind: "First", first: value });
|
|
6179
|
-
var makeSecond = (value) => ({ kind: "Second", second: value });
|
|
6180
|
-
var makeBoth = (f, s) => ({ kind: "Both", first: f, second: s });
|
|
6181
|
-
var isFirst = (data) => data.kind === "First";
|
|
6182
|
-
var isSecond = (data) => data.kind === "Second";
|
|
6183
|
-
var isBoth = (data) => data.kind === "Both";
|
|
6184
|
-
var hasFirst = (data) => data.kind === "First" || data.kind === "Both";
|
|
6185
|
-
var hasSecond = (data) => data.kind === "Second" || data.kind === "Both";
|
|
6186
|
-
var These = {
|
|
6187
|
-
make: {
|
|
6188
|
-
/**
|
|
6189
|
-
* Creates a These holding only a first value.
|
|
6190
|
-
*
|
|
6191
|
-
* @example
|
|
6192
|
-
* ```ts
|
|
6193
|
-
* These.make.first(42); // { kind: "First", first: 42 }
|
|
6194
|
-
* ```
|
|
6195
|
-
*/
|
|
6196
|
-
first: makeFirst,
|
|
6197
|
-
/**
|
|
6198
|
-
* Creates a These holding only a second value.
|
|
6199
|
-
*
|
|
6200
|
-
* @example
|
|
6201
|
-
* ```ts
|
|
6202
|
-
* These.make.second("warning"); // { kind: "Second", second: "warning" }
|
|
6203
|
-
* ```
|
|
6204
|
-
*/
|
|
6205
|
-
second: makeSecond,
|
|
6206
|
-
/**
|
|
6207
|
-
* Creates a These holding both a first and a second value simultaneously.
|
|
6208
|
-
*
|
|
6209
|
-
* @example
|
|
6210
|
-
* ```ts
|
|
6211
|
-
* These.make.both(42, "Deprecated API used"); // { kind: "Both", first: 42, second: "Deprecated API used" }
|
|
6212
|
-
* ```
|
|
6213
|
-
*/
|
|
6214
|
-
both: makeBoth
|
|
6215
|
-
},
|
|
6216
|
-
is: {
|
|
6217
|
-
/**
|
|
6218
|
-
* Type guard — checks if a These holds only a first value.
|
|
6219
|
-
*
|
|
6220
|
-
* @example
|
|
6221
|
-
* ```ts
|
|
6222
|
-
* const val = These.make.first(42);
|
|
6223
|
-
* if (These.is.first(val)) {
|
|
6224
|
-
* console.log(val.first); // 42
|
|
6225
|
-
* }
|
|
6226
|
-
* ```
|
|
6227
|
-
*/
|
|
6228
|
-
first: isFirst,
|
|
6229
|
-
/**
|
|
6230
|
-
* Type guard — checks if a These holds only a second value.
|
|
6231
|
-
*
|
|
6232
|
-
* @example
|
|
6233
|
-
* ```ts
|
|
6234
|
-
* const val = These.make.second("warning");
|
|
6235
|
-
* if (These.is.second(val)) {
|
|
6236
|
-
* console.log(val.second); // "warning"
|
|
6237
|
-
* }
|
|
6238
|
-
* ```
|
|
6239
|
-
*/
|
|
6240
|
-
second: isSecond,
|
|
6241
|
-
/**
|
|
6242
|
-
* Type guard — checks if a These holds both values simultaneously.
|
|
6243
|
-
*
|
|
6244
|
-
* @example
|
|
6245
|
-
* ```ts
|
|
6246
|
-
* const val = These.make.both(42, "warning");
|
|
6247
|
-
* if (These.is.both(val)) {
|
|
6248
|
-
* console.log(val.first, val.second); // 42 "warning"
|
|
6249
|
-
* }
|
|
6250
|
-
* ```
|
|
6251
|
-
*/
|
|
6252
|
-
both: isBoth
|
|
6253
|
-
},
|
|
6285
|
+
all: (tasks) => fromPromise2(
|
|
6286
|
+
(signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))
|
|
6287
|
+
),
|
|
6254
6288
|
/**
|
|
6255
|
-
*
|
|
6289
|
+
* Delays the execution of a Task by the specified duration.
|
|
6290
|
+
* Useful for debouncing or rate limiting.
|
|
6256
6291
|
*
|
|
6257
6292
|
* @example
|
|
6258
6293
|
* ```ts
|
|
6259
|
-
*
|
|
6260
|
-
*
|
|
6261
|
-
*
|
|
6294
|
+
* pipe(
|
|
6295
|
+
* Task.resolve(42),
|
|
6296
|
+
* Task.delay(Duration.seconds(1))
|
|
6297
|
+
* )(); // Resolves after 1 second
|
|
6262
6298
|
* ```
|
|
6263
6299
|
*/
|
|
6264
|
-
|
|
6300
|
+
delay: (duration) => (data) => fromPromise2(
|
|
6301
|
+
(signal) => new Promise((res) => {
|
|
6302
|
+
let timerId;
|
|
6303
|
+
const onAbort = () => {
|
|
6304
|
+
clearTimeout(timerId);
|
|
6305
|
+
res(toPromise2(data, signal));
|
|
6306
|
+
};
|
|
6307
|
+
if (signal) {
|
|
6308
|
+
if (signal.aborted) {
|
|
6309
|
+
return res(toPromise2(data, signal));
|
|
6310
|
+
}
|
|
6311
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6312
|
+
}
|
|
6313
|
+
timerId = setTimeout(() => {
|
|
6314
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6315
|
+
res(toPromise2(data, signal));
|
|
6316
|
+
}, getMs2(duration));
|
|
6317
|
+
})
|
|
6318
|
+
),
|
|
6319
|
+
/**
|
|
6320
|
+
* Runs a Task a fixed number of times sequentially, collecting all results into an array.
|
|
6321
|
+
* An optional delay duration can be inserted between runs.
|
|
6322
|
+
*
|
|
6323
|
+
* @example
|
|
6324
|
+
* ```ts
|
|
6325
|
+
* pipe(
|
|
6326
|
+
* pollSensor,
|
|
6327
|
+
* Task.repeat({ times: 5, delay: Duration.seconds(1) })
|
|
6328
|
+
* )(); // Task<Reading[]> — 5 readings, one per second
|
|
6329
|
+
* ```
|
|
6330
|
+
*/
|
|
6331
|
+
repeat: (options) => (task) => fromPromise2((signal) => {
|
|
6332
|
+
const { times, delay: delayDuration } = options;
|
|
6333
|
+
if (times <= 0) {
|
|
6334
|
+
return Promise.resolve([]);
|
|
6335
|
+
}
|
|
6336
|
+
const results = [];
|
|
6337
|
+
const wait = () => new Promise((r) => {
|
|
6338
|
+
let timerId;
|
|
6339
|
+
const onAbort = () => {
|
|
6340
|
+
clearTimeout(timerId);
|
|
6341
|
+
r();
|
|
6342
|
+
};
|
|
6343
|
+
if (signal) {
|
|
6344
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6345
|
+
}
|
|
6346
|
+
timerId = setTimeout(() => {
|
|
6347
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6348
|
+
r();
|
|
6349
|
+
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
6350
|
+
});
|
|
6351
|
+
const run = (left) => {
|
|
6352
|
+
if (signal?.aborted) {
|
|
6353
|
+
return Promise.resolve(results);
|
|
6354
|
+
}
|
|
6355
|
+
return toPromise2(task, signal).then((a) => {
|
|
6356
|
+
results.push(a);
|
|
6357
|
+
if (left <= 1 || signal?.aborted) {
|
|
6358
|
+
return results;
|
|
6359
|
+
}
|
|
6360
|
+
return wait().then(() => run(left - 1));
|
|
6361
|
+
});
|
|
6362
|
+
};
|
|
6363
|
+
return run(times);
|
|
6364
|
+
}),
|
|
6265
6365
|
/**
|
|
6266
|
-
*
|
|
6366
|
+
* Runs a Task repeatedly until the result satisfies a predicate, returning that result.
|
|
6367
|
+
* An optional delay duration can be inserted between runs.
|
|
6368
|
+
* An optional `maxAttempts` cap stops the loop after N calls — the last value is returned
|
|
6369
|
+
* regardless of whether the predicate was satisfied.
|
|
6267
6370
|
*
|
|
6268
6371
|
* @example
|
|
6269
6372
|
* ```ts
|
|
6270
|
-
*
|
|
6271
|
-
*
|
|
6272
|
-
*
|
|
6373
|
+
* pipe(
|
|
6374
|
+
* checkStatus,
|
|
6375
|
+
* Task.repeatUntil({ when: (s) => s === "ready", delay: Duration.milliseconds(500) })
|
|
6376
|
+
* )(); // polls every 500ms until status is "ready"
|
|
6273
6377
|
* ```
|
|
6274
6378
|
*/
|
|
6275
|
-
|
|
6379
|
+
repeatUntil: (options) => (task) => fromPromise2((signal) => {
|
|
6380
|
+
const { when: predicate, delay: delayDuration, maxAttempts } = options;
|
|
6381
|
+
const wait = () => new Promise((r) => {
|
|
6382
|
+
let timerId;
|
|
6383
|
+
const onAbort = () => {
|
|
6384
|
+
clearTimeout(timerId);
|
|
6385
|
+
r();
|
|
6386
|
+
};
|
|
6387
|
+
if (signal) {
|
|
6388
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
6389
|
+
}
|
|
6390
|
+
timerId = setTimeout(() => {
|
|
6391
|
+
signal?.removeEventListener("abort", onAbort);
|
|
6392
|
+
r();
|
|
6393
|
+
}, delayDuration ? getMs2(delayDuration) : 0);
|
|
6394
|
+
});
|
|
6395
|
+
const run = (attempt, lastValue) => {
|
|
6396
|
+
if (signal?.aborted && lastValue !== void 0) {
|
|
6397
|
+
return Promise.resolve(lastValue);
|
|
6398
|
+
}
|
|
6399
|
+
return toPromise2(task, signal).then((a) => {
|
|
6400
|
+
if (predicate(a)) {
|
|
6401
|
+
return a;
|
|
6402
|
+
}
|
|
6403
|
+
if (maxAttempts !== void 0 && attempt >= maxAttempts) {
|
|
6404
|
+
return a;
|
|
6405
|
+
}
|
|
6406
|
+
if (signal?.aborted) {
|
|
6407
|
+
return a;
|
|
6408
|
+
}
|
|
6409
|
+
return wait().then(() => run(attempt + 1, a));
|
|
6410
|
+
});
|
|
6411
|
+
};
|
|
6412
|
+
return run(1);
|
|
6413
|
+
}),
|
|
6276
6414
|
/**
|
|
6277
|
-
*
|
|
6415
|
+
* Resolves with the value of the first Task to complete. All Tasks start
|
|
6416
|
+
* immediately. When one resolves, the other tasks are cancelled (aborted)
|
|
6417
|
+
* downstream.
|
|
6278
6418
|
*
|
|
6279
6419
|
* @example
|
|
6280
6420
|
* ```ts
|
|
6281
|
-
*
|
|
6282
|
-
*
|
|
6283
|
-
*
|
|
6421
|
+
* const fast = Task.resolve("fast");
|
|
6422
|
+
* const slow = Task.delay(Duration.milliseconds(200))(Task.resolve("slow"));
|
|
6423
|
+
*
|
|
6424
|
+
* await Task.race([fast, slow])(); // "fast"
|
|
6284
6425
|
* ```
|
|
6285
6426
|
*/
|
|
6286
|
-
|
|
6287
|
-
if (
|
|
6288
|
-
return
|
|
6289
|
-
|
|
6290
|
-
if (isFirst(data)) {
|
|
6291
|
-
return makeFirst(f(data.first));
|
|
6427
|
+
race: (tasks) => {
|
|
6428
|
+
if (tasks.length === 0) {
|
|
6429
|
+
return () => Deferred.from.Promise(new Promise(() => {
|
|
6430
|
+
}));
|
|
6292
6431
|
}
|
|
6293
|
-
return
|
|
6432
|
+
return fromPromise2((outerSignal) => {
|
|
6433
|
+
const controllers = tasks.map(() => new AbortController());
|
|
6434
|
+
const onOuterAbort = () => {
|
|
6435
|
+
for (const ctrl of controllers) {
|
|
6436
|
+
ctrl.abort();
|
|
6437
|
+
}
|
|
6438
|
+
};
|
|
6439
|
+
if (outerSignal) {
|
|
6440
|
+
if (outerSignal.aborted) {
|
|
6441
|
+
onOuterAbort();
|
|
6442
|
+
} else {
|
|
6443
|
+
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
const promises = tasks.map((task, idx) => {
|
|
6447
|
+
const ctrl = controllers[idx];
|
|
6448
|
+
return toPromise2(task, ctrl.signal).then((result) => {
|
|
6449
|
+
for (let i = 0; i < controllers.length; i++) {
|
|
6450
|
+
if (i !== idx) {
|
|
6451
|
+
controllers[i].abort();
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
6455
|
+
return result;
|
|
6456
|
+
});
|
|
6457
|
+
});
|
|
6458
|
+
return Promise.race(promises);
|
|
6459
|
+
});
|
|
6294
6460
|
},
|
|
6295
6461
|
/**
|
|
6296
|
-
*
|
|
6462
|
+
* Runs an array of Tasks concurrently and collects their results in an array.
|
|
6463
|
+
* Forward-propagates the call site's AbortSignal to all subtasks concurrently.
|
|
6297
6464
|
*
|
|
6298
6465
|
* @example
|
|
6299
6466
|
* ```ts
|
|
6300
|
-
*
|
|
6301
|
-
*
|
|
6467
|
+
* Task.sequence([loadConfig, detectLocale, loadTheme])();
|
|
6468
|
+
* // Deferred<[Config, string, Theme]>
|
|
6302
6469
|
* ```
|
|
6303
6470
|
*/
|
|
6304
|
-
|
|
6305
|
-
if (isFirst(data)) {
|
|
6306
|
-
return data;
|
|
6307
|
-
}
|
|
6308
|
-
if (isSecond(data)) {
|
|
6309
|
-
return makeSecond(f(data.second));
|
|
6310
|
-
}
|
|
6311
|
-
return makeBoth(data.first, f(data.second));
|
|
6312
|
-
},
|
|
6471
|
+
sequence: (tasks) => fromPromise2((signal) => Promise.all(tasks.map((t) => toPromise2(t, signal)))),
|
|
6313
6472
|
/**
|
|
6314
|
-
*
|
|
6473
|
+
* Runs an array of Tasks one at a time in order, collecting all results.
|
|
6474
|
+
* Each Task starts only after the previous one resolves.
|
|
6315
6475
|
*
|
|
6316
6476
|
* @example
|
|
6317
6477
|
* ```ts
|
|
6318
|
-
*
|
|
6319
|
-
*
|
|
6320
|
-
*
|
|
6321
|
-
* )
|
|
6478
|
+
* let log: number[] = [];
|
|
6479
|
+
* const makeTask = (n: number) => Task.resolve(n);
|
|
6480
|
+
*
|
|
6481
|
+
* await Task.sequential([makeTask(1), makeTask(2), makeTask(3)])();
|
|
6482
|
+
* // log = [1, 2, 3] — tasks ran in order
|
|
6322
6483
|
* ```
|
|
6323
6484
|
*/
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6485
|
+
sequential: (tasks) => fromPromise2(async (signal) => {
|
|
6486
|
+
const results = [];
|
|
6487
|
+
for (const task of tasks) {
|
|
6488
|
+
if (signal?.aborted) {
|
|
6489
|
+
break;
|
|
6490
|
+
}
|
|
6491
|
+
results.push(await toPromise2(task, signal));
|
|
6330
6492
|
}
|
|
6331
|
-
return
|
|
6332
|
-
},
|
|
6493
|
+
return results;
|
|
6494
|
+
}),
|
|
6333
6495
|
/**
|
|
6334
|
-
*
|
|
6335
|
-
*
|
|
6496
|
+
* Converts a `Task<A>` into a `Task<Result<E, A>>`, resolving to `Err` if the
|
|
6497
|
+
* Task does not complete within the given duration. The inner Task receives an
|
|
6498
|
+
* `AbortSignal` that fires when the deadline passes, so asynchronous operations
|
|
6499
|
+
* that accept a signal are cancelled rather than left dangling.
|
|
6336
6500
|
*
|
|
6337
6501
|
* @example
|
|
6338
6502
|
* ```ts
|
|
6339
|
-
*
|
|
6340
|
-
*
|
|
6341
|
-
*
|
|
6342
|
-
*
|
|
6343
|
-
*
|
|
6503
|
+
* pipe(
|
|
6504
|
+
* heavyComputation,
|
|
6505
|
+
* Task.timeout({ duration: Duration.seconds(5), onTimeout: () => "timed out" }),
|
|
6506
|
+
* Task.Result.chain(processResult)
|
|
6507
|
+
* );
|
|
6344
6508
|
* ```
|
|
6345
6509
|
*/
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6510
|
+
timeout: (options) => (task) => fromPromise2((outerSignal) => {
|
|
6511
|
+
const { duration, onTimeout } = options;
|
|
6512
|
+
const controller = new AbortController();
|
|
6513
|
+
let timerId;
|
|
6514
|
+
let cleanUp = () => {
|
|
6515
|
+
};
|
|
6516
|
+
const onOuterAbort = () => {
|
|
6517
|
+
cleanUp();
|
|
6518
|
+
controller.abort();
|
|
6519
|
+
};
|
|
6520
|
+
cleanUp = () => {
|
|
6521
|
+
clearTimeout(timerId);
|
|
6522
|
+
outerSignal?.removeEventListener("abort", onOuterAbort);
|
|
6523
|
+
};
|
|
6524
|
+
if (outerSignal) {
|
|
6525
|
+
if (outerSignal.aborted) {
|
|
6526
|
+
controller.abort();
|
|
6527
|
+
} else {
|
|
6528
|
+
outerSignal.addEventListener("abort", onOuterAbort, { once: true });
|
|
6529
|
+
}
|
|
6349
6530
|
}
|
|
6350
|
-
return
|
|
6351
|
-
|
|
6531
|
+
return Promise.race([
|
|
6532
|
+
toPromise2(task, controller.signal).then((a) => {
|
|
6533
|
+
cleanUp();
|
|
6534
|
+
return Result.make.ok(a);
|
|
6535
|
+
}),
|
|
6536
|
+
new Promise((res) => {
|
|
6537
|
+
timerId = setTimeout(() => {
|
|
6538
|
+
controller.abort();
|
|
6539
|
+
cleanUp();
|
|
6540
|
+
res(Result.make.err(onTimeout()));
|
|
6541
|
+
}, getMs2(duration));
|
|
6542
|
+
})
|
|
6543
|
+
]);
|
|
6544
|
+
}),
|
|
6352
6545
|
/**
|
|
6353
|
-
*
|
|
6354
|
-
*
|
|
6546
|
+
* Creates a Task paired with an `abort` handle. Calling `abort()` cancels the
|
|
6547
|
+
* current in-flight call immediately. Unlike a one-shot abort, calling `task()`
|
|
6548
|
+
* again after `abort()` starts a fresh call with a new signal.
|
|
6549
|
+
*
|
|
6550
|
+
* Each invocation of `task()` automatically cancels the previous in-flight call,
|
|
6551
|
+
* making it safe to call repeatedly (e.g. on user input) without leaking promises.
|
|
6552
|
+
*
|
|
6553
|
+
* If an outer signal is also present (passed at the call site), aborting it
|
|
6554
|
+
* propagates into the internal controller.
|
|
6355
6555
|
*
|
|
6356
6556
|
* @example
|
|
6357
6557
|
* ```ts
|
|
6358
|
-
* const
|
|
6558
|
+
* const { task: poll, abort } = Task.abortable(
|
|
6559
|
+
* (signal) => waitForEvent(bus, "ready", { signal }),
|
|
6560
|
+
* );
|
|
6359
6561
|
*
|
|
6360
|
-
*
|
|
6361
|
-
*
|
|
6362
|
-
* pipe(These.make.first(5), These.chainSecond(shout)); // First(5)
|
|
6562
|
+
* onUnmount(abort);
|
|
6563
|
+
* await poll();
|
|
6363
6564
|
* ```
|
|
6364
6565
|
*/
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6566
|
+
abortable: (factory) => {
|
|
6567
|
+
let currentController = null;
|
|
6568
|
+
const abort = () => currentController?.abort();
|
|
6569
|
+
const task = (outerSignal) => {
|
|
6570
|
+
currentController?.abort();
|
|
6571
|
+
currentController = new AbortController();
|
|
6572
|
+
const controller = currentController;
|
|
6573
|
+
if (outerSignal) {
|
|
6574
|
+
if (outerSignal.aborted) {
|
|
6575
|
+
controller.abort(outerSignal.reason);
|
|
6576
|
+
} else {
|
|
6577
|
+
outerSignal.addEventListener("abort", () => controller.abort(outerSignal.reason), { once: true });
|
|
6578
|
+
}
|
|
6579
|
+
}
|
|
6580
|
+
return Deferred.from.Promise(factory(controller.signal));
|
|
6581
|
+
};
|
|
6582
|
+
return { task, abort };
|
|
6370
6583
|
},
|
|
6371
6584
|
/**
|
|
6372
|
-
*
|
|
6585
|
+
* Executes a task with an optional signal. Use as a terminal step in a `pipe` chain.
|
|
6373
6586
|
*
|
|
6374
6587
|
* @example
|
|
6375
6588
|
* ```ts
|
|
6376
|
-
* pipe(
|
|
6377
|
-
*
|
|
6378
|
-
*
|
|
6379
|
-
*
|
|
6380
|
-
* b => `Second: ${b}`,
|
|
6381
|
-
* (a, b) => `Both: ${a} / ${b}`
|
|
6382
|
-
* )
|
|
6589
|
+
* const name = await pipe(
|
|
6590
|
+
* loadConfig,
|
|
6591
|
+
* Task.map(config => config.name),
|
|
6592
|
+
* Task.run(),
|
|
6383
6593
|
* );
|
|
6384
6594
|
* ```
|
|
6385
6595
|
*/
|
|
6386
|
-
|
|
6387
|
-
if (isSecond(data)) {
|
|
6388
|
-
return onSecond(data.second);
|
|
6389
|
-
}
|
|
6390
|
-
if (isFirst(data)) {
|
|
6391
|
-
return onFirst(data.first);
|
|
6392
|
-
}
|
|
6393
|
-
return onBoth(data.first, data.second);
|
|
6394
|
-
},
|
|
6596
|
+
run: (signal) => (task) => task(signal),
|
|
6395
6597
|
/**
|
|
6396
|
-
*
|
|
6598
|
+
* Converts a Task value into an object containing a single property.
|
|
6599
|
+
* Initiates the pipeline accumulator record.
|
|
6397
6600
|
*
|
|
6398
6601
|
* @example
|
|
6399
6602
|
* ```ts
|
|
6400
|
-
* pipe(
|
|
6401
|
-
* these,
|
|
6402
|
-
* These.match({
|
|
6403
|
-
* first: a => `First: ${a}`,
|
|
6404
|
-
* second: b => `Second: ${b}`,
|
|
6405
|
-
* both: (a, b) => `Both: ${a} / ${b}`
|
|
6406
|
-
* })
|
|
6407
|
-
* );
|
|
6603
|
+
* pipe(Task.resolve(42), Task.bindTo("value")); // Task({ value: 42 })
|
|
6408
6604
|
* ```
|
|
6409
6605
|
*/
|
|
6410
|
-
|
|
6411
|
-
if (isSecond(data)) {
|
|
6412
|
-
return cases.second(data.second);
|
|
6413
|
-
}
|
|
6414
|
-
if (isFirst(data)) {
|
|
6415
|
-
return cases.first(data.first);
|
|
6416
|
-
}
|
|
6417
|
-
return cases.both(data.first, data.second);
|
|
6418
|
-
},
|
|
6606
|
+
bindTo: (key) => (data) => fromPromise2((signal) => toPromise2(data, signal).then((a) => ({ [key]: a }))),
|
|
6419
6607
|
/**
|
|
6420
|
-
*
|
|
6421
|
-
* The default can be a different type, widening the result to `A | C`.
|
|
6608
|
+
* Evaluates a new Task using the current accumulator and attaches the output to a new key.
|
|
6422
6609
|
*
|
|
6423
6610
|
* @example
|
|
6424
6611
|
* ```ts
|
|
6425
|
-
* pipe(
|
|
6426
|
-
*
|
|
6427
|
-
*
|
|
6428
|
-
*
|
|
6612
|
+
* pipe(
|
|
6613
|
+
* Task.resolve({ a: 1 }),
|
|
6614
|
+
* Task.bind("b", ({ a }) => Task.resolve(a + 1))
|
|
6615
|
+
* ); // Task({ a: 1, b: 2 })
|
|
6429
6616
|
* ```
|
|
6430
6617
|
*/
|
|
6431
|
-
|
|
6618
|
+
bind: (key, f) => (data) => fromPromise2(
|
|
6619
|
+
(signal) => toPromise2(data, signal).then(
|
|
6620
|
+
(a) => toPromise2(f(a), signal).then((b) => ({ ...a, [key]: b }))
|
|
6621
|
+
)
|
|
6622
|
+
),
|
|
6432
6623
|
/**
|
|
6433
|
-
*
|
|
6434
|
-
*
|
|
6624
|
+
* Creates a memoized version of a Task. The task is executed at most once on first call,
|
|
6625
|
+
* and its resolved value is cached for all subsequent calls.
|
|
6435
6626
|
*
|
|
6436
6627
|
* @example
|
|
6437
6628
|
* ```ts
|
|
6438
|
-
*
|
|
6439
|
-
*
|
|
6440
|
-
*
|
|
6441
|
-
* pipe(These.make.first(5), These.getSecondOrElse(() => null)); // null — typed as string | null
|
|
6629
|
+
* const loadToken = Task.memoize(loadAuthToken);
|
|
6630
|
+
* const token1 = await loadToken(); // loads token
|
|
6631
|
+
* const token2 = await loadToken(); // returns cached token immediately
|
|
6442
6632
|
* ```
|
|
6443
6633
|
*/
|
|
6444
|
-
|
|
6634
|
+
memoize: (task) => {
|
|
6635
|
+
let cached = null;
|
|
6636
|
+
return (signal) => {
|
|
6637
|
+
if (cached === null) {
|
|
6638
|
+
cached = task(signal);
|
|
6639
|
+
}
|
|
6640
|
+
return cached;
|
|
6641
|
+
};
|
|
6642
|
+
},
|
|
6445
6643
|
/**
|
|
6446
|
-
*
|
|
6447
|
-
* Useful for logging or debugging.
|
|
6644
|
+
* Monitors progress of a Task by calling `onProgress(0)` before execution and `onProgress(1)` upon completion.
|
|
6448
6645
|
*
|
|
6449
6646
|
* @example
|
|
6450
6647
|
* ```ts
|
|
6451
|
-
*
|
|
6648
|
+
* const taskWithProgress = pipe(
|
|
6649
|
+
* readTask,
|
|
6650
|
+
* Task.withProgress((ratio) => console.log(`Progress: ${ratio * 100}%`))
|
|
6651
|
+
* );
|
|
6452
6652
|
* ```
|
|
6453
6653
|
*/
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6654
|
+
withProgress: (onProgress) => (task) => (signal) => {
|
|
6655
|
+
onProgress(0);
|
|
6656
|
+
const d = task(signal);
|
|
6657
|
+
return Deferred.from.Promise(
|
|
6658
|
+
Deferred.to.Promise(d).then((res) => {
|
|
6659
|
+
onProgress(1);
|
|
6660
|
+
return res;
|
|
6661
|
+
})
|
|
6662
|
+
);
|
|
6459
6663
|
},
|
|
6460
6664
|
/**
|
|
6461
|
-
*
|
|
6462
|
-
* - First(a) → Second(a)
|
|
6463
|
-
* - Second(b) → First(b)
|
|
6464
|
-
* - Both(a, b) → Both(b, a)
|
|
6665
|
+
* Attaches a read-only `.label` property to a Task, preserving the literal string generic type for IDE tooltips.
|
|
6465
6666
|
*
|
|
6466
6667
|
* @example
|
|
6467
6668
|
* ```ts
|
|
6468
|
-
*
|
|
6469
|
-
*
|
|
6470
|
-
* These.swap(These.make.both(5, "warn")); // Both("warn", 5)
|
|
6669
|
+
* const labeledTask = pipe(readTask, Task.withLabel("readUser"));
|
|
6670
|
+
* console.log(labeledTask.label); // "readUser"
|
|
6471
6671
|
* ```
|
|
6472
6672
|
*/
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
}
|
|
6673
|
+
withLabel: (label) => (task) => {
|
|
6674
|
+
const fn = ((signal) => task(signal));
|
|
6675
|
+
Object.defineProperty(fn, "label", { value: label, writable: false, enumerable: true, configurable: true });
|
|
6676
|
+
return fn;
|
|
6677
|
+
},
|
|
6678
|
+
Maybe: TaskMaybe,
|
|
6679
|
+
Result: TaskResult,
|
|
6680
|
+
Validation: TaskValidation
|
|
6482
6681
|
};
|
|
6483
6682
|
|
|
6484
|
-
// src/Core/
|
|
6485
|
-
var
|
|
6486
|
-
var
|
|
6487
|
-
var
|
|
6488
|
-
var
|
|
6489
|
-
var
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
}
|
|
6495
|
-
return isPassed(arg) ? Result.make.ok(arg.value) : Result.make.err(arg.errors);
|
|
6496
|
-
}
|
|
6497
|
-
var Validation = {
|
|
6683
|
+
// src/Core/These.ts
|
|
6684
|
+
var makeFirst = (value) => ({ kind: "First", first: value });
|
|
6685
|
+
var makeSecond = (value) => ({ kind: "Second", second: value });
|
|
6686
|
+
var makeBoth = (f, s) => ({ kind: "Both", first: f, second: s });
|
|
6687
|
+
var isFirst = (data) => data.kind === "First";
|
|
6688
|
+
var isSecond = (data) => data.kind === "Second";
|
|
6689
|
+
var isBoth = (data) => data.kind === "Both";
|
|
6690
|
+
var hasFirst = (data) => data.kind === "First" || data.kind === "Both";
|
|
6691
|
+
var hasSecond = (data) => data.kind === "Second" || data.kind === "Both";
|
|
6692
|
+
var These = {
|
|
6498
6693
|
make: {
|
|
6499
6694
|
/**
|
|
6500
|
-
*
|
|
6695
|
+
* Creates a These holding only a first value.
|
|
6501
6696
|
*
|
|
6502
6697
|
* @example
|
|
6503
6698
|
* ```ts
|
|
6504
|
-
*
|
|
6699
|
+
* These.make.first(42); // { kind: "First", first: 42 }
|
|
6505
6700
|
* ```
|
|
6506
6701
|
*/
|
|
6507
|
-
|
|
6702
|
+
first: makeFirst,
|
|
6508
6703
|
/**
|
|
6509
|
-
* Creates a
|
|
6704
|
+
* Creates a These holding only a second value.
|
|
6510
6705
|
*
|
|
6511
6706
|
* @example
|
|
6512
6707
|
* ```ts
|
|
6513
|
-
*
|
|
6708
|
+
* These.make.second("warning"); // { kind: "Second", second: "warning" }
|
|
6514
6709
|
* ```
|
|
6515
6710
|
*/
|
|
6516
|
-
|
|
6711
|
+
second: makeSecond,
|
|
6517
6712
|
/**
|
|
6518
|
-
* Creates a
|
|
6713
|
+
* Creates a These holding both a first and a second value simultaneously.
|
|
6519
6714
|
*
|
|
6520
6715
|
* @example
|
|
6521
6716
|
* ```ts
|
|
6522
|
-
*
|
|
6717
|
+
* These.make.both(42, "Deprecated API used"); // { kind: "Both", first: 42, second: "Deprecated API used" }
|
|
6523
6718
|
* ```
|
|
6524
6719
|
*/
|
|
6525
|
-
|
|
6720
|
+
both: makeBoth
|
|
6526
6721
|
},
|
|
6527
6722
|
is: {
|
|
6528
6723
|
/**
|
|
6529
|
-
* Type guard
|
|
6724
|
+
* Type guard — checks if a These holds only a first value.
|
|
6530
6725
|
*
|
|
6531
6726
|
* @example
|
|
6532
6727
|
* ```ts
|
|
6533
|
-
* const
|
|
6534
|
-
* if (
|
|
6535
|
-
* console.log(
|
|
6728
|
+
* const val = These.make.first(42);
|
|
6729
|
+
* if (These.is.first(val)) {
|
|
6730
|
+
* console.log(val.first); // 42
|
|
6536
6731
|
* }
|
|
6537
6732
|
* ```
|
|
6538
6733
|
*/
|
|
6539
|
-
|
|
6734
|
+
first: isFirst,
|
|
6540
6735
|
/**
|
|
6541
|
-
* Type guard
|
|
6736
|
+
* Type guard — checks if a These holds only a second value.
|
|
6542
6737
|
*
|
|
6543
6738
|
* @example
|
|
6544
6739
|
* ```ts
|
|
6545
|
-
* const
|
|
6546
|
-
* if (
|
|
6547
|
-
* console.log(
|
|
6740
|
+
* const val = These.make.second("warning");
|
|
6741
|
+
* if (These.is.second(val)) {
|
|
6742
|
+
* console.log(val.second); // "warning"
|
|
6548
6743
|
* }
|
|
6549
6744
|
* ```
|
|
6550
6745
|
*/
|
|
6551
|
-
|
|
6552
|
-
},
|
|
6553
|
-
/**
|
|
6554
|
-
* Creates a Validation from a synchronous thunk that may throw.
|
|
6555
|
-
* Catches any errors and transforms them using the `onError` function into a Failed validation.
|
|
6556
|
-
*
|
|
6557
|
-
* @example
|
|
6558
|
-
* ```ts
|
|
6559
|
-
* const result = Validation.tryCatch(
|
|
6560
|
-
* () => JSON.parse(rawString),
|
|
6561
|
-
* { onError: (e) => `Parse error: ${e}` }
|
|
6562
|
-
* );
|
|
6563
|
-
* ```
|
|
6564
|
-
*/
|
|
6565
|
-
tryCatch: (f, options) => {
|
|
6566
|
-
try {
|
|
6567
|
-
return makePassed2(f());
|
|
6568
|
-
} catch (error) {
|
|
6569
|
-
return makeFailed2(options.onError(error));
|
|
6570
|
-
}
|
|
6571
|
-
},
|
|
6572
|
-
// --- from ---
|
|
6573
|
-
from: {
|
|
6574
|
-
/**
|
|
6575
|
-
* Creates a Validation from a predicate applied to a value.
|
|
6576
|
-
* Returns Passed if the predicate passes, Failed from `onFalse` otherwise.
|
|
6577
|
-
*
|
|
6578
|
-
* @example
|
|
6579
|
-
* ```ts
|
|
6580
|
-
* const validateName = Validation.from.Predicate(
|
|
6581
|
-
* (s: string) => s.length > 0,
|
|
6582
|
-
* () => "Name is required"
|
|
6583
|
-
* );
|
|
6584
|
-
*
|
|
6585
|
-
* validateName("Alice"); // Passed("Alice")
|
|
6586
|
-
* validateName(""); // Failed(["Name is required"])
|
|
6587
|
-
* ```
|
|
6588
|
-
*/
|
|
6589
|
-
Predicate: (pred, onFalse) => (a) => pred(a) ? makePassed2(a) : makeFailed2(onFalse(a)),
|
|
6590
|
-
/**
|
|
6591
|
-
* Creates a Validation from a nullable value.
|
|
6592
|
-
* If the value is null or undefined, returns Failed with the error from onNull.
|
|
6593
|
-
* Otherwise, returns Passed.
|
|
6594
|
-
*
|
|
6595
|
-
* @example
|
|
6596
|
-
* ```ts
|
|
6597
|
-
* pipe(null, Validation.from.nullable(() => "is null")); // Failed(["is null"])
|
|
6598
|
-
* pipe(42, Validation.from.nullable(() => "is null")); // Passed(42)
|
|
6599
|
-
* ```
|
|
6600
|
-
*/
|
|
6601
|
-
nullable: (onNull) => (value) => value === null || value === void 0 ? makeFailed2(onNull()) : makePassed2(value),
|
|
6602
|
-
/**
|
|
6603
|
-
* Creates a Validation from a Maybe.
|
|
6604
|
-
* If the Maybe is None, returns Failed with the error from onNone.
|
|
6605
|
-
* Otherwise, returns Passed.
|
|
6606
|
-
*
|
|
6607
|
-
* @example
|
|
6608
|
-
* ```ts
|
|
6609
|
-
* pipe(Maybe.make.none(), Validation.from.Maybe(() => "is none")); // Failed(["is none"])
|
|
6610
|
-
* pipe(Maybe.make.some(42), Validation.from.Maybe(() => "is none")); // Passed(42)
|
|
6611
|
-
* ```
|
|
6612
|
-
*/
|
|
6613
|
-
Maybe: (onNone) => (maybe) => Maybe.is.none(maybe) ? makeFailed2(onNone()) : makePassed2(maybe.value),
|
|
6746
|
+
second: isSecond,
|
|
6614
6747
|
/**
|
|
6615
|
-
*
|
|
6616
|
-
*
|
|
6617
|
-
* Useful when bridging from error-short-circuiting `Result` pipelines into
|
|
6618
|
-
* error-accumulating `Validation` pipelines.
|
|
6748
|
+
* Type guard — checks if a These holds both values simultaneously.
|
|
6619
6749
|
*
|
|
6620
6750
|
* @example
|
|
6621
6751
|
* ```ts
|
|
6622
|
-
*
|
|
6623
|
-
*
|
|
6752
|
+
* const val = These.make.both(42, "warning");
|
|
6753
|
+
* if (These.is.both(val)) {
|
|
6754
|
+
* console.log(val.first, val.second); // 42 "warning"
|
|
6755
|
+
* }
|
|
6624
6756
|
* ```
|
|
6625
6757
|
*/
|
|
6626
|
-
|
|
6758
|
+
both: isBoth
|
|
6627
6759
|
},
|
|
6628
6760
|
/**
|
|
6629
|
-
*
|
|
6761
|
+
* Returns true if the These contains a first value (First or Both).
|
|
6630
6762
|
*
|
|
6631
6763
|
* @example
|
|
6632
6764
|
* ```ts
|
|
6633
|
-
*
|
|
6634
|
-
*
|
|
6765
|
+
* These.hasFirst(These.make.first(42)); // true
|
|
6766
|
+
* These.hasFirst(These.make.both(42, "warn"));// true
|
|
6767
|
+
* These.hasFirst(These.make.second("warn")); // false
|
|
6635
6768
|
* ```
|
|
6636
6769
|
*/
|
|
6637
|
-
|
|
6770
|
+
hasFirst,
|
|
6638
6771
|
/**
|
|
6639
|
-
*
|
|
6772
|
+
* Returns true if the These contains a second value (Second or Both).
|
|
6640
6773
|
*
|
|
6641
6774
|
* @example
|
|
6642
6775
|
* ```ts
|
|
6643
|
-
*
|
|
6776
|
+
* These.hasSecond(These.make.second("warn")); // true
|
|
6777
|
+
* These.hasSecond(These.make.both(42, "warn"));// true
|
|
6778
|
+
* These.hasSecond(These.make.first(42)); // false
|
|
6644
6779
|
* ```
|
|
6645
6780
|
*/
|
|
6646
|
-
|
|
6781
|
+
hasSecond,
|
|
6647
6782
|
/**
|
|
6648
|
-
*
|
|
6649
|
-
* Accumulates errors from both sides.
|
|
6783
|
+
* Transforms the first value, leaving the second unchanged.
|
|
6650
6784
|
*
|
|
6651
6785
|
* @example
|
|
6652
6786
|
* ```ts
|
|
6653
|
-
*
|
|
6654
|
-
* pipe(
|
|
6655
|
-
*
|
|
6656
|
-
* Validation.ap(Validation.make.passed(5)),
|
|
6657
|
-
* Validation.ap(Validation.make.passed(3))
|
|
6658
|
-
* ); // Passed(8)
|
|
6659
|
-
*
|
|
6660
|
-
* pipe(
|
|
6661
|
-
* Validation.make.passed(add),
|
|
6662
|
-
* Validation.ap(Validation.make.failed<string>("bad a")),
|
|
6663
|
-
* Validation.ap(Validation.make.failed<string>("bad b"))
|
|
6664
|
-
* ); // Failed(["bad a", "bad b"])
|
|
6787
|
+
* pipe(These.make.first(5), These.mapFirst(n => n * 2)); // First(10)
|
|
6788
|
+
* pipe(These.make.both(5, "warn"), These.mapFirst(n => n * 2)); // Both(10, "warn")
|
|
6789
|
+
* pipe(These.make.second("warn"), These.mapFirst(n => n * 2)); // Second("warn")
|
|
6665
6790
|
* ```
|
|
6666
6791
|
*/
|
|
6667
|
-
|
|
6668
|
-
if (
|
|
6669
|
-
return
|
|
6792
|
+
mapFirst: (f) => (data) => {
|
|
6793
|
+
if (isSecond(data)) {
|
|
6794
|
+
return data;
|
|
6795
|
+
}
|
|
6796
|
+
if (isFirst(data)) {
|
|
6797
|
+
return makeFirst(f(data.first));
|
|
6670
6798
|
}
|
|
6671
|
-
return
|
|
6799
|
+
return makeBoth(f(data.first), data.second);
|
|
6672
6800
|
},
|
|
6673
6801
|
/**
|
|
6674
|
-
*
|
|
6675
|
-
* using a custom error concatenator function when both sides fail.
|
|
6802
|
+
* Transforms the second value, leaving the first unchanged.
|
|
6676
6803
|
*
|
|
6677
6804
|
* @example
|
|
6678
6805
|
* ```ts
|
|
6679
|
-
*
|
|
6680
|
-
*
|
|
6681
|
-
* pipe(fnVal, Validation.apCustom(concat)(argVal));
|
|
6806
|
+
* pipe(These.make.second("warn"), These.mapSecond(e => e.toUpperCase())); // Second("WARN")
|
|
6807
|
+
* pipe(These.make.both(5, "warn"), These.mapSecond(e => e.toUpperCase())); // Both(5, "WARN")
|
|
6682
6808
|
* ```
|
|
6683
6809
|
*/
|
|
6684
|
-
|
|
6685
|
-
if (
|
|
6686
|
-
return
|
|
6810
|
+
mapSecond: (f) => (data) => {
|
|
6811
|
+
if (isFirst(data)) {
|
|
6812
|
+
return data;
|
|
6813
|
+
}
|
|
6814
|
+
if (isSecond(data)) {
|
|
6815
|
+
return makeSecond(f(data.second));
|
|
6687
6816
|
}
|
|
6688
|
-
return
|
|
6817
|
+
return makeBoth(data.first, f(data.second));
|
|
6689
6818
|
},
|
|
6690
6819
|
/**
|
|
6691
|
-
*
|
|
6820
|
+
* Transforms both the first and second values independently.
|
|
6692
6821
|
*
|
|
6693
6822
|
* @example
|
|
6694
6823
|
* ```ts
|
|
6695
6824
|
* pipe(
|
|
6696
|
-
*
|
|
6697
|
-
*
|
|
6698
|
-
*
|
|
6699
|
-
* value => `Value: ${value}`
|
|
6700
|
-
* )
|
|
6701
|
-
* );
|
|
6825
|
+
* These.make.both(5, "warn"),
|
|
6826
|
+
* These.mapBoth(n => n * 2, e => e.toUpperCase())
|
|
6827
|
+
* ); // Both(10, "WARN")
|
|
6702
6828
|
* ```
|
|
6703
6829
|
*/
|
|
6704
|
-
|
|
6830
|
+
mapBoth: (onFirst, onSecond) => (data) => {
|
|
6831
|
+
if (isSecond(data)) {
|
|
6832
|
+
return makeSecond(onSecond(data.second));
|
|
6833
|
+
}
|
|
6834
|
+
if (isFirst(data)) {
|
|
6835
|
+
return makeFirst(onFirst(data.first));
|
|
6836
|
+
}
|
|
6837
|
+
return makeBoth(onFirst(data.first), onSecond(data.second));
|
|
6838
|
+
},
|
|
6705
6839
|
/**
|
|
6706
|
-
*
|
|
6840
|
+
* Chains These computations by passing the first value to f.
|
|
6841
|
+
* Second propagates unchanged; First and Both apply f to the first value.
|
|
6707
6842
|
*
|
|
6708
6843
|
* @example
|
|
6709
6844
|
* ```ts
|
|
6710
|
-
*
|
|
6711
|
-
*
|
|
6712
|
-
*
|
|
6713
|
-
*
|
|
6714
|
-
*
|
|
6715
|
-
* })
|
|
6716
|
-
* );
|
|
6845
|
+
* const double = (n: number): These<number, string> => These.make.first(n * 2);
|
|
6846
|
+
*
|
|
6847
|
+
* pipe(These.make.first(5), These.chainFirst(double)); // First(10)
|
|
6848
|
+
* pipe(These.make.both(5, "warn"), These.chainFirst(double)); // First(10)
|
|
6849
|
+
* pipe(These.make.second("warn"), These.chainFirst(double)); // Second("warn")
|
|
6717
6850
|
* ```
|
|
6718
6851
|
*/
|
|
6719
|
-
|
|
6852
|
+
chainFirst: (f) => (data) => {
|
|
6853
|
+
if (isSecond(data)) {
|
|
6854
|
+
return data;
|
|
6855
|
+
}
|
|
6856
|
+
return f(data.first);
|
|
6857
|
+
},
|
|
6720
6858
|
/**
|
|
6721
|
-
*
|
|
6722
|
-
*
|
|
6859
|
+
* Chains These computations by passing the second value to f.
|
|
6860
|
+
* First propagates unchanged; Second and Both apply f to the second value.
|
|
6723
6861
|
*
|
|
6724
6862
|
* @example
|
|
6725
6863
|
* ```ts
|
|
6726
|
-
*
|
|
6727
|
-
*
|
|
6728
|
-
* pipe(
|
|
6864
|
+
* const shout = (s: string): These<number, string> => These.make.second(s.toUpperCase());
|
|
6865
|
+
*
|
|
6866
|
+
* pipe(These.make.second("warn"), These.chainSecond(shout)); // Second("WARN")
|
|
6867
|
+
* pipe(These.make.both(5, "warn"), These.chainSecond(shout)); // Second("WARN")
|
|
6868
|
+
* pipe(These.make.first(5), These.chainSecond(shout)); // First(5)
|
|
6729
6869
|
* ```
|
|
6730
6870
|
*/
|
|
6731
|
-
|
|
6871
|
+
chainSecond: (f) => (data) => {
|
|
6872
|
+
if (isFirst(data)) {
|
|
6873
|
+
return data;
|
|
6874
|
+
}
|
|
6875
|
+
return f(data.second);
|
|
6876
|
+
},
|
|
6732
6877
|
/**
|
|
6733
|
-
*
|
|
6878
|
+
* Extracts a value from a These by providing handlers for all three cases.
|
|
6734
6879
|
*
|
|
6735
6880
|
* @example
|
|
6736
6881
|
* ```ts
|
|
6737
6882
|
* pipe(
|
|
6738
|
-
*
|
|
6739
|
-
*
|
|
6740
|
-
*
|
|
6883
|
+
* these,
|
|
6884
|
+
* These.fold(
|
|
6885
|
+
* a => `First: ${a}`,
|
|
6886
|
+
* b => `Second: ${b}`,
|
|
6887
|
+
* (a, b) => `Both: ${a} / ${b}`
|
|
6888
|
+
* )
|
|
6741
6889
|
* );
|
|
6742
6890
|
* ```
|
|
6743
6891
|
*/
|
|
6744
|
-
|
|
6745
|
-
if (
|
|
6746
|
-
|
|
6892
|
+
fold: (onFirst, onSecond, onBoth) => (data) => {
|
|
6893
|
+
if (isSecond(data)) {
|
|
6894
|
+
return onSecond(data.second);
|
|
6747
6895
|
}
|
|
6748
|
-
|
|
6896
|
+
if (isFirst(data)) {
|
|
6897
|
+
return onFirst(data.first);
|
|
6898
|
+
}
|
|
6899
|
+
return onBoth(data.first, data.second);
|
|
6749
6900
|
},
|
|
6750
6901
|
/**
|
|
6751
|
-
*
|
|
6752
|
-
* Useful for logging or reporting validation failures.
|
|
6902
|
+
* Pattern matches on a These, returning the result of the matching case.
|
|
6753
6903
|
*
|
|
6754
6904
|
* @example
|
|
6755
6905
|
* ```ts
|
|
6756
6906
|
* pipe(
|
|
6757
|
-
*
|
|
6758
|
-
*
|
|
6759
|
-
*
|
|
6907
|
+
* these,
|
|
6908
|
+
* These.match({
|
|
6909
|
+
* first: a => `First: ${a}`,
|
|
6910
|
+
* second: b => `Second: ${b}`,
|
|
6911
|
+
* both: (a, b) => `Both: ${a} / ${b}`
|
|
6912
|
+
* })
|
|
6760
6913
|
* );
|
|
6761
6914
|
* ```
|
|
6762
6915
|
*/
|
|
6763
|
-
|
|
6764
|
-
if (
|
|
6765
|
-
|
|
6916
|
+
match: (cases) => (data) => {
|
|
6917
|
+
if (isSecond(data)) {
|
|
6918
|
+
return cases.second(data.second);
|
|
6766
6919
|
}
|
|
6767
|
-
|
|
6920
|
+
if (isFirst(data)) {
|
|
6921
|
+
return cases.first(data.first);
|
|
6922
|
+
}
|
|
6923
|
+
return cases.both(data.first, data.second);
|
|
6768
6924
|
},
|
|
6769
6925
|
/**
|
|
6770
|
-
*
|
|
6771
|
-
* The
|
|
6772
|
-
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
6773
|
-
*/
|
|
6774
|
-
recover: (fallback) => (data) => isPassed(data) ? data : fallback(data.errors),
|
|
6775
|
-
/**
|
|
6776
|
-
* Recovers from a Failed state unless `isBlocked` returns true for any of the accumulated errors.
|
|
6777
|
-
* The fallback can produce a different success type, widening the result to `Validation<E, A | B>`.
|
|
6926
|
+
* Returns the first value, or a default if the These has no first value.
|
|
6927
|
+
* The default can be a different type, widening the result to `A | C`.
|
|
6778
6928
|
*
|
|
6779
6929
|
* @example
|
|
6780
6930
|
* ```ts
|
|
6781
|
-
* pipe(
|
|
6782
|
-
*
|
|
6783
|
-
*
|
|
6784
|
-
* );
|
|
6931
|
+
* pipe(These.make.first(5), These.getFirstOrElse(() => 0)); // 5
|
|
6932
|
+
* pipe(These.make.both(5, "warn"), These.getFirstOrElse(() => 0)); // 5
|
|
6933
|
+
* pipe(These.make.second("warn"), These.getFirstOrElse(() => 0)); // 0
|
|
6934
|
+
* pipe(These.make.second("warn"), These.getFirstOrElse(() => null)); // null — typed as number | null
|
|
6785
6935
|
* ```
|
|
6786
6936
|
*/
|
|
6787
|
-
|
|
6788
|
-
// --- to ---
|
|
6789
|
-
to: {
|
|
6790
|
-
/**
|
|
6791
|
-
* Converts a Validation to a Result.
|
|
6792
|
-
* Passed becomes Ok.
|
|
6793
|
-
* Direct call converts Failed to Err with accumulated error list `NonEmptyArr<E>`.
|
|
6794
|
-
* Curried call converts Failed to Err with combined error `E2` via `combineErrors`.
|
|
6795
|
-
*
|
|
6796
|
-
* @example
|
|
6797
|
-
* ```ts
|
|
6798
|
-
* Validation.to.Result(Validation.make.passed(42)); // Ok(42)
|
|
6799
|
-
* Validation.to.Result(Validation.make.failed("oops")); // Err(["oops"])
|
|
6800
|
-
* pipe(Validation.make.failed("oops"), Validation.to.Result(errors => errors.join(", "))); // Err("oops")
|
|
6801
|
-
* ```
|
|
6802
|
-
*/
|
|
6803
|
-
Result: toResult,
|
|
6804
|
-
/**
|
|
6805
|
-
* Converts a Validation to a Maybe. `Passed` becomes `Some`; `Failed` becomes `None`
|
|
6806
|
-
* (errors are discarded).
|
|
6807
|
-
*
|
|
6808
|
-
* @example
|
|
6809
|
-
* ```ts
|
|
6810
|
-
* Validation.to.Maybe(Validation.make.passed(42)); // Some(42)
|
|
6811
|
-
* Validation.to.Maybe(Validation.make.failed("bad")); // None
|
|
6812
|
-
* ```
|
|
6813
|
-
*/
|
|
6814
|
-
Maybe: (data) => isPassed(data) ? Maybe.make.some(data.value) : Maybe.make.none()
|
|
6815
|
-
},
|
|
6937
|
+
getFirstOrElse: (defaultValue) => (data) => hasFirst(data) ? data.first : defaultValue(),
|
|
6816
6938
|
/**
|
|
6817
|
-
*
|
|
6818
|
-
*
|
|
6819
|
-
* If either is Failed, accumulates errors from both sides.
|
|
6939
|
+
* Returns the second value, or a default if the These has no second value.
|
|
6940
|
+
* The default can be a different type, widening the result to `B | D`.
|
|
6820
6941
|
*
|
|
6821
6942
|
* @example
|
|
6822
6943
|
* ```ts
|
|
6823
|
-
*
|
|
6824
|
-
*
|
|
6825
|
-
*
|
|
6826
|
-
* );
|
|
6827
|
-
*
|
|
6828
|
-
* Validation.product(
|
|
6829
|
-
* Validation.make.failed("Name required"),
|
|
6830
|
-
* Validation.make.failed("Age must be >= 0")
|
|
6831
|
-
* ); // Failed(["Name required", "Age must be >= 0"])
|
|
6944
|
+
* pipe(These.make.second("warn"), These.getSecondOrElse(() => "none")); // "warn"
|
|
6945
|
+
* pipe(These.make.both(5, "warn"), These.getSecondOrElse(() => "none")); // "warn"
|
|
6946
|
+
* pipe(These.make.first(5), These.getSecondOrElse(() => "none")); // "none"
|
|
6947
|
+
* pipe(These.make.first(5), These.getSecondOrElse(() => null)); // null — typed as string | null
|
|
6832
6948
|
* ```
|
|
6833
6949
|
*/
|
|
6834
|
-
|
|
6835
|
-
if (isPassed(first)) {
|
|
6836
|
-
return isPassed(second) ? makePassed2([first.value, second.value]) : makeFailedAll2(second.errors);
|
|
6837
|
-
}
|
|
6838
|
-
return isPassed(second) ? makeFailedAll2(first.errors) : makeFailedAll2([...first.errors, ...second.errors]);
|
|
6839
|
-
},
|
|
6950
|
+
getSecondOrElse: (defaultValue) => (data) => hasSecond(data) ? data.second : defaultValue(),
|
|
6840
6951
|
/**
|
|
6841
|
-
*
|
|
6842
|
-
*
|
|
6843
|
-
* If any are Failed, returns Failed with all accumulated errors.
|
|
6952
|
+
* Runs a side effect on the first value without changing the These.
|
|
6953
|
+
* Useful for logging or debugging.
|
|
6844
6954
|
*
|
|
6845
|
-
* @example
|
|
6846
|
-
* ```ts
|
|
6847
|
-
*
|
|
6848
|
-
*
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
* ```
|
|
6854
|
-
*/
|
|
6855
|
-
productAll: (data) => {
|
|
6856
|
-
const values3 = [];
|
|
6857
|
-
const errors = [];
|
|
6858
|
-
for (const v of data) {
|
|
6859
|
-
if (isPassed(v)) {
|
|
6860
|
-
values3.push(v.value);
|
|
6861
|
-
} else {
|
|
6862
|
-
errors.push(...v.errors);
|
|
6863
|
-
}
|
|
6955
|
+
* @example
|
|
6956
|
+
* ```ts
|
|
6957
|
+
* pipe(These.make.first(5), These.tap(console.log)); // logs 5, returns First(5)
|
|
6958
|
+
* ```
|
|
6959
|
+
*/
|
|
6960
|
+
tap: (f) => (data) => {
|
|
6961
|
+
if (hasFirst(data)) {
|
|
6962
|
+
f(data.first);
|
|
6864
6963
|
}
|
|
6865
|
-
return
|
|
6964
|
+
return data;
|
|
6866
6965
|
},
|
|
6867
6966
|
/**
|
|
6868
|
-
*
|
|
6869
|
-
*
|
|
6967
|
+
* Swaps the roles of first and second values.
|
|
6968
|
+
* - First(a) → Second(a)
|
|
6969
|
+
* - Second(b) → First(b)
|
|
6970
|
+
* - Both(a, b) → Both(b, a)
|
|
6870
6971
|
*
|
|
6871
6972
|
* @example
|
|
6872
6973
|
* ```ts
|
|
6873
|
-
*
|
|
6874
|
-
*
|
|
6875
|
-
*
|
|
6876
|
-
* }); // Passed({ name: "Alice", age: 30 })
|
|
6877
|
-
*
|
|
6878
|
-
* Validation.struct({
|
|
6879
|
-
* name: Validation.make.failed("Name required"),
|
|
6880
|
-
* age: Validation.make.failed("Age must be >= 0")
|
|
6881
|
-
* }); // Failed(["Name required", "Age must be >= 0"])
|
|
6974
|
+
* These.swap(These.make.first(5)); // Second(5)
|
|
6975
|
+
* These.swap(These.make.second("warn")); // First("warn")
|
|
6976
|
+
* These.swap(These.make.both(5, "warn")); // Both("warn", 5)
|
|
6882
6977
|
* ```
|
|
6883
6978
|
*/
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
if (isPassed(val)) {
|
|
6891
|
-
record[key] = val.value;
|
|
6892
|
-
} else {
|
|
6893
|
-
errors.push(...val.errors);
|
|
6894
|
-
}
|
|
6895
|
-
}
|
|
6979
|
+
swap: (data) => {
|
|
6980
|
+
if (isSecond(data)) {
|
|
6981
|
+
return makeFirst(data.second);
|
|
6982
|
+
}
|
|
6983
|
+
if (isFirst(data)) {
|
|
6984
|
+
return makeSecond(data.first);
|
|
6896
6985
|
}
|
|
6897
|
-
return
|
|
6986
|
+
return makeBoth(data.second, data.first);
|
|
6898
6987
|
}
|
|
6899
6988
|
};
|
|
6900
6989
|
|
|
@@ -7433,6 +7522,60 @@ var Arr = {
|
|
|
7433
7522
|
|
|
7434
7523
|
// src/Data/BigNum.ts
|
|
7435
7524
|
var BigNum = {
|
|
7525
|
+
is: {
|
|
7526
|
+
/**
|
|
7527
|
+
* Returns `true` when the bigint is equal to zero (`0n`).
|
|
7528
|
+
*
|
|
7529
|
+
* @example
|
|
7530
|
+
* ```ts
|
|
7531
|
+
* BigNum.is.zero(0n); // true
|
|
7532
|
+
* BigNum.is.zero(5n); // false
|
|
7533
|
+
* ```
|
|
7534
|
+
*/
|
|
7535
|
+
zero: (b) => b === 0n,
|
|
7536
|
+
/**
|
|
7537
|
+
* Returns `true` when the bigint is an even integer.
|
|
7538
|
+
*
|
|
7539
|
+
* @example
|
|
7540
|
+
* ```ts
|
|
7541
|
+
* BigNum.is.even(4n); // true
|
|
7542
|
+
* BigNum.is.even(3n); // false
|
|
7543
|
+
* ```
|
|
7544
|
+
*/
|
|
7545
|
+
even: (b) => b % 2n === 0n,
|
|
7546
|
+
/**
|
|
7547
|
+
* Returns `true` when the bigint is an odd integer.
|
|
7548
|
+
*
|
|
7549
|
+
* @example
|
|
7550
|
+
* ```ts
|
|
7551
|
+
* BigNum.is.odd(3n); // true
|
|
7552
|
+
* BigNum.is.odd(4n); // false
|
|
7553
|
+
* ```
|
|
7554
|
+
*/
|
|
7555
|
+
odd: (b) => b % 2n !== 0n,
|
|
7556
|
+
/**
|
|
7557
|
+
* Returns `true` when the bigint is strictly greater than zero (`0n`).
|
|
7558
|
+
*
|
|
7559
|
+
* @example
|
|
7560
|
+
* ```ts
|
|
7561
|
+
* BigNum.is.positive(5n); // true
|
|
7562
|
+
* BigNum.is.positive(0n); // false
|
|
7563
|
+
* BigNum.is.positive(-5n); // false
|
|
7564
|
+
* ```
|
|
7565
|
+
*/
|
|
7566
|
+
positive: (b) => b > 0n,
|
|
7567
|
+
/**
|
|
7568
|
+
* Returns `true` when the bigint is strictly less than zero (`0n`).
|
|
7569
|
+
*
|
|
7570
|
+
* @example
|
|
7571
|
+
* ```ts
|
|
7572
|
+
* BigNum.is.negative(-5n); // true
|
|
7573
|
+
* BigNum.is.negative(0n); // false
|
|
7574
|
+
* BigNum.is.negative(5n); // false
|
|
7575
|
+
* ```
|
|
7576
|
+
*/
|
|
7577
|
+
negative: (b) => b < 0n
|
|
7578
|
+
},
|
|
7436
7579
|
// --- from ---
|
|
7437
7580
|
from: {
|
|
7438
7581
|
/**
|
|
@@ -7582,6 +7725,345 @@ var BigNum = {
|
|
|
7582
7725
|
max: (b) => (a) => a > b ? a : b
|
|
7583
7726
|
};
|
|
7584
7727
|
|
|
7728
|
+
// src/Data/Bool.ts
|
|
7729
|
+
var isBoolean = (u) => typeof u === "boolean";
|
|
7730
|
+
var isTrue = (u) => u === true;
|
|
7731
|
+
var isFalse = (u) => u === false;
|
|
7732
|
+
var isTruthy = (u) => Boolean(u);
|
|
7733
|
+
var isFalsy = (u) => !u;
|
|
7734
|
+
var not2 = (b) => !b;
|
|
7735
|
+
var and2 = (that) => (self) => self && that;
|
|
7736
|
+
var or2 = (that) => (self) => self || that;
|
|
7737
|
+
var xor = (that) => (self) => self !== that;
|
|
7738
|
+
var andLazy = (that) => (self) => self && that();
|
|
7739
|
+
var orLazy = (that) => (self) => self || that();
|
|
7740
|
+
var all = (booleans) => {
|
|
7741
|
+
for (let i = 0; i < booleans.length; i++) {
|
|
7742
|
+
if (!booleans[i]) {
|
|
7743
|
+
return false;
|
|
7744
|
+
}
|
|
7745
|
+
}
|
|
7746
|
+
return true;
|
|
7747
|
+
};
|
|
7748
|
+
var any = (booleans) => {
|
|
7749
|
+
for (let i = 0; i < booleans.length; i++) {
|
|
7750
|
+
if (booleans[i]) {
|
|
7751
|
+
return true;
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
return false;
|
|
7755
|
+
};
|
|
7756
|
+
var fold = (onFalse, onTrue) => (b) => b ? onTrue() : onFalse();
|
|
7757
|
+
var match = (cases) => (b) => b ? cases.true() : cases.false();
|
|
7758
|
+
var fromString = (s) => {
|
|
7759
|
+
const trimmed = s.trim().toLowerCase();
|
|
7760
|
+
if (trimmed === "true") {
|
|
7761
|
+
return Maybe.make.some(true);
|
|
7762
|
+
}
|
|
7763
|
+
if (trimmed === "false") {
|
|
7764
|
+
return Maybe.make.some(false);
|
|
7765
|
+
}
|
|
7766
|
+
return Maybe.make.none();
|
|
7767
|
+
};
|
|
7768
|
+
var fromNumber = (n) => {
|
|
7769
|
+
if (n === 1) {
|
|
7770
|
+
return Maybe.make.some(true);
|
|
7771
|
+
}
|
|
7772
|
+
if (n === 0) {
|
|
7773
|
+
return Maybe.make.some(false);
|
|
7774
|
+
}
|
|
7775
|
+
return Maybe.make.none();
|
|
7776
|
+
};
|
|
7777
|
+
var fromTruthy = (value) => Boolean(value);
|
|
7778
|
+
var toMaybe = (onTrue) => (b) => b ? Maybe.make.some(onTrue()) : Maybe.make.none();
|
|
7779
|
+
var toResult2 = (onErr, onOk) => (b) => b ? Result.make.ok(onOk()) : Result.make.err(onErr());
|
|
7780
|
+
var toNumber = (b) => b ? 1 : 0;
|
|
7781
|
+
var toString = (b) => b ? "true" : "false";
|
|
7782
|
+
var Bool = {
|
|
7783
|
+
is: {
|
|
7784
|
+
/**
|
|
7785
|
+
* Type guard — checks if a value is a primitive boolean.
|
|
7786
|
+
*
|
|
7787
|
+
* @example
|
|
7788
|
+
* ```ts
|
|
7789
|
+
* Bool.is.boolean(true); // true
|
|
7790
|
+
* Bool.is.boolean(false); // true
|
|
7791
|
+
* Bool.is.boolean("true"); // false
|
|
7792
|
+
* Bool.is.boolean(null); // false
|
|
7793
|
+
* ```
|
|
7794
|
+
*/
|
|
7795
|
+
boolean: isBoolean,
|
|
7796
|
+
/**
|
|
7797
|
+
* Narrowing guard — checks if a value is strictly `true`.
|
|
7798
|
+
*
|
|
7799
|
+
* @example
|
|
7800
|
+
* ```ts
|
|
7801
|
+
* Bool.is.true(true); // true
|
|
7802
|
+
* Bool.is.true(false); // false
|
|
7803
|
+
* ```
|
|
7804
|
+
*/
|
|
7805
|
+
true: isTrue,
|
|
7806
|
+
/**
|
|
7807
|
+
* Narrowing guard — checks if a value is strictly `false`.
|
|
7808
|
+
*
|
|
7809
|
+
* @example
|
|
7810
|
+
* ```ts
|
|
7811
|
+
* Bool.is.false(false); // true
|
|
7812
|
+
* Bool.is.false(true); // false
|
|
7813
|
+
* ```
|
|
7814
|
+
*/
|
|
7815
|
+
false: isFalse,
|
|
7816
|
+
/**
|
|
7817
|
+
* Type guard — checks if a value is truthy (not `false`, `0`, `0n`, `""`, `null`, `undefined`, or `NaN`).
|
|
7818
|
+
*
|
|
7819
|
+
* @example
|
|
7820
|
+
* ```ts
|
|
7821
|
+
* Bool.is.truthy("hello"); // true
|
|
7822
|
+
* Bool.is.truthy(42); // true
|
|
7823
|
+
* Bool.is.truthy(0); // false
|
|
7824
|
+
* Bool.is.truthy(null); // false
|
|
7825
|
+
* ```
|
|
7826
|
+
*/
|
|
7827
|
+
truthy: isTruthy,
|
|
7828
|
+
/**
|
|
7829
|
+
* Type guard — checks if a value is falsy (`false`, `0`, `0n`, `""`, `null`, `undefined`, or `NaN`).
|
|
7830
|
+
*
|
|
7831
|
+
* @example
|
|
7832
|
+
* ```ts
|
|
7833
|
+
* Bool.is.falsy(""); // true
|
|
7834
|
+
* Bool.is.falsy(null); // true
|
|
7835
|
+
* Bool.is.falsy("content"); // false
|
|
7836
|
+
* ```
|
|
7837
|
+
*/
|
|
7838
|
+
falsy: isFalsy
|
|
7839
|
+
},
|
|
7840
|
+
/**
|
|
7841
|
+
* Unary boolean negation: inverts the given boolean value.
|
|
7842
|
+
*
|
|
7843
|
+
* @example
|
|
7844
|
+
* ```ts
|
|
7845
|
+
* Bool.not(true); // false
|
|
7846
|
+
* Bool.not(false); // true
|
|
7847
|
+
* ```
|
|
7848
|
+
*/
|
|
7849
|
+
not: not2,
|
|
7850
|
+
/**
|
|
7851
|
+
* Logical AND combinator. Returns `true` only if both `self` and `that` are `true`.
|
|
7852
|
+
*
|
|
7853
|
+
* Data-last: `pipe(self, Bool.and(that))`.
|
|
7854
|
+
*
|
|
7855
|
+
* @example
|
|
7856
|
+
* ```ts
|
|
7857
|
+
* pipe(true, Bool.and(true)); // true
|
|
7858
|
+
* pipe(true, Bool.and(false)); // false
|
|
7859
|
+
* ```
|
|
7860
|
+
*/
|
|
7861
|
+
and: and2,
|
|
7862
|
+
/**
|
|
7863
|
+
* Logical OR combinator. Returns `true` if either `self` or `that` is `true`.
|
|
7864
|
+
*
|
|
7865
|
+
* Data-last: `pipe(self, Bool.or(that))`.
|
|
7866
|
+
*
|
|
7867
|
+
* @example
|
|
7868
|
+
* ```ts
|
|
7869
|
+
* pipe(false, Bool.or(true)); // true
|
|
7870
|
+
* pipe(false, Bool.or(false)); // false
|
|
7871
|
+
* ```
|
|
7872
|
+
*/
|
|
7873
|
+
or: or2,
|
|
7874
|
+
/**
|
|
7875
|
+
* Logical XOR (exclusive OR) combinator. Returns `true` if exactly one of `self` and `that` is `true`.
|
|
7876
|
+
*
|
|
7877
|
+
* Data-last: `pipe(self, Bool.xor(that))`.
|
|
7878
|
+
*
|
|
7879
|
+
* @example
|
|
7880
|
+
* ```ts
|
|
7881
|
+
* pipe(true, Bool.xor(false)); // true
|
|
7882
|
+
* pipe(true, Bool.xor(true)); // false
|
|
7883
|
+
* ```
|
|
7884
|
+
*/
|
|
7885
|
+
xor,
|
|
7886
|
+
/**
|
|
7887
|
+
* Lazy logical AND combinator.
|
|
7888
|
+
* If `self` is `false`, the `that` computation is never evaluated.
|
|
7889
|
+
*
|
|
7890
|
+
* Data-last: `pipe(self, Bool.andLazy(that))`.
|
|
7891
|
+
*
|
|
7892
|
+
* @example
|
|
7893
|
+
* ```ts
|
|
7894
|
+
* pipe(
|
|
7895
|
+
* isCached,
|
|
7896
|
+
* Bool.andLazy(() => checkPermissions())
|
|
7897
|
+
* );
|
|
7898
|
+
* ```
|
|
7899
|
+
*/
|
|
7900
|
+
andLazy,
|
|
7901
|
+
/**
|
|
7902
|
+
* Lazy logical OR combinator.
|
|
7903
|
+
* If `self` is `true`, the `that` computation is never evaluated.
|
|
7904
|
+
*
|
|
7905
|
+
* Data-last: `pipe(self, Bool.orLazy(that))`.
|
|
7906
|
+
*
|
|
7907
|
+
* @example
|
|
7908
|
+
* ```ts
|
|
7909
|
+
* pipe(
|
|
7910
|
+
* isAdmin,
|
|
7911
|
+
* Bool.orLazy(() => hasAccess(userId))
|
|
7912
|
+
* );
|
|
7913
|
+
* ```
|
|
7914
|
+
*/
|
|
7915
|
+
orLazy,
|
|
7916
|
+
/**
|
|
7917
|
+
* N-ary AND aggregation across an array of booleans.
|
|
7918
|
+
* Returns `true` if every boolean is `true`, or for an empty array (vacuous truth).
|
|
7919
|
+
* Short-circuits on the first `false`.
|
|
7920
|
+
*
|
|
7921
|
+
* @example
|
|
7922
|
+
* ```ts
|
|
7923
|
+
* Bool.all([true, true, true]); // true
|
|
7924
|
+
* Bool.all([true, false, true]); // false
|
|
7925
|
+
* Bool.all([]); // true
|
|
7926
|
+
* ```
|
|
7927
|
+
*/
|
|
7928
|
+
all,
|
|
7929
|
+
/**
|
|
7930
|
+
* N-ary OR aggregation across an array of booleans.
|
|
7931
|
+
* Returns `true` if at least one boolean is `true`. Returns `false` for an empty array.
|
|
7932
|
+
* Short-circuits on the first `true`.
|
|
7933
|
+
*
|
|
7934
|
+
* @example
|
|
7935
|
+
* ```ts
|
|
7936
|
+
* Bool.any([false, true, false]); // true
|
|
7937
|
+
* Bool.any([false, false]); // false
|
|
7938
|
+
* Bool.any([]); // false
|
|
7939
|
+
* ```
|
|
7940
|
+
*/
|
|
7941
|
+
any,
|
|
7942
|
+
/**
|
|
7943
|
+
* Catamorphism for boolean: evaluates `onFalse()` when `false` and `onTrue()` when `true`.
|
|
7944
|
+
*
|
|
7945
|
+
* Positional ordering: `onFalse` first, `onTrue` second.
|
|
7946
|
+
* Aligned with `Result.fold(onErr, onOk)` and `Maybe.fold(onNone, onSome)`.
|
|
7947
|
+
*
|
|
7948
|
+
* @example
|
|
7949
|
+
* ```ts
|
|
7950
|
+
* pipe(
|
|
7951
|
+
* isDarkMode,
|
|
7952
|
+
* Bool.fold(
|
|
7953
|
+
* () => "light-theme",
|
|
7954
|
+
* () => "dark-theme"
|
|
7955
|
+
* )
|
|
7956
|
+
* );
|
|
7957
|
+
* ```
|
|
7958
|
+
*/
|
|
7959
|
+
fold,
|
|
7960
|
+
/**
|
|
7961
|
+
* Pattern matching on boolean using named cases `{ true, false }`.
|
|
7962
|
+
*
|
|
7963
|
+
* @example
|
|
7964
|
+
* ```ts
|
|
7965
|
+
* pipe(
|
|
7966
|
+
* isEnabled,
|
|
7967
|
+
* Bool.match({
|
|
7968
|
+
* true: () => "Feature Active",
|
|
7969
|
+
* false: () => "Feature Disabled",
|
|
7970
|
+
* })
|
|
7971
|
+
* );
|
|
7972
|
+
* ```
|
|
7973
|
+
*/
|
|
7974
|
+
match,
|
|
7975
|
+
// --- from ---
|
|
7976
|
+
from: {
|
|
7977
|
+
/**
|
|
7978
|
+
* Parses a string into a `Maybe<boolean>`.
|
|
7979
|
+
* Returns `Some(true)` for `"true"`, `Some(false)` for `"false"` (case-insensitive & trimmed),
|
|
7980
|
+
* and `None` for any other string.
|
|
7981
|
+
*
|
|
7982
|
+
* @example
|
|
7983
|
+
* ```ts
|
|
7984
|
+
* Bool.from.string("true"); // Some(true)
|
|
7985
|
+
* Bool.from.string("FALSE"); // Some(false)
|
|
7986
|
+
* Bool.from.string("yes"); // None
|
|
7987
|
+
* ```
|
|
7988
|
+
*/
|
|
7989
|
+
string: fromString,
|
|
7990
|
+
/**
|
|
7991
|
+
* Converts a number into a `Maybe<boolean>`.
|
|
7992
|
+
* Returns `Some(true)` for `1`, `Some(false)` for `0`, and `None` for any other number.
|
|
7993
|
+
*
|
|
7994
|
+
* @example
|
|
7995
|
+
* ```ts
|
|
7996
|
+
* Bool.from.number(1); // Some(true)
|
|
7997
|
+
* Bool.from.number(0); // Some(false)
|
|
7998
|
+
* Bool.from.number(42); // None
|
|
7999
|
+
* ```
|
|
8000
|
+
*/
|
|
8001
|
+
number: fromNumber,
|
|
8002
|
+
/**
|
|
8003
|
+
* Coerces any unknown value into a boolean via standard JS `Boolean(value)`.
|
|
8004
|
+
*
|
|
8005
|
+
* @example
|
|
8006
|
+
* ```ts
|
|
8007
|
+
* Bool.from.truthy("hello"); // true
|
|
8008
|
+
* Bool.from.truthy(0); // false
|
|
8009
|
+
* ```
|
|
8010
|
+
*/
|
|
8011
|
+
truthy: fromTruthy
|
|
8012
|
+
},
|
|
8013
|
+
// --- to ---
|
|
8014
|
+
to: {
|
|
8015
|
+
/**
|
|
8016
|
+
* Lifts a boolean condition into a `Maybe`.
|
|
8017
|
+
* Returns `Some(onTrue())` when `true`, and `None` when `false`.
|
|
8018
|
+
*
|
|
8019
|
+
* @example
|
|
8020
|
+
* ```ts
|
|
8021
|
+
* pipe(
|
|
8022
|
+
* user.isVerified,
|
|
8023
|
+
* Bool.to.Maybe(() => user.profile)
|
|
8024
|
+
* ); // Some(profile) or None
|
|
8025
|
+
* ```
|
|
8026
|
+
*/
|
|
8027
|
+
Maybe: toMaybe,
|
|
8028
|
+
/**
|
|
8029
|
+
* Lifts a boolean condition into a `Result`.
|
|
8030
|
+
* Returns `Ok(onOk())` when `true`, and `Err(onErr())` when `false`.
|
|
8031
|
+
*
|
|
8032
|
+
* @example
|
|
8033
|
+
* ```ts
|
|
8034
|
+
* pipe(
|
|
8035
|
+
* hasPermission,
|
|
8036
|
+
* Bool.to.Result(
|
|
8037
|
+
* () => "Permission denied",
|
|
8038
|
+
* () => sessionData
|
|
8039
|
+
* )
|
|
8040
|
+
* ); // Ok(sessionData) or Err("Permission denied")
|
|
8041
|
+
* ```
|
|
8042
|
+
*/
|
|
8043
|
+
Result: toResult2,
|
|
8044
|
+
/**
|
|
8045
|
+
* Converts a boolean to numeric `1` or `0`.
|
|
8046
|
+
*
|
|
8047
|
+
* @example
|
|
8048
|
+
* ```ts
|
|
8049
|
+
* Bool.to.number(true); // 1
|
|
8050
|
+
* Bool.to.number(false); // 0
|
|
8051
|
+
* ```
|
|
8052
|
+
*/
|
|
8053
|
+
number: toNumber,
|
|
8054
|
+
/**
|
|
8055
|
+
* Converts a boolean to literal string `"true"` or `"false"`.
|
|
8056
|
+
*
|
|
8057
|
+
* @example
|
|
8058
|
+
* ```ts
|
|
8059
|
+
* Bool.to.string(true); // "true"
|
|
8060
|
+
* Bool.to.string(false); // "false"
|
|
8061
|
+
* ```
|
|
8062
|
+
*/
|
|
8063
|
+
string: toString
|
|
8064
|
+
}
|
|
8065
|
+
};
|
|
8066
|
+
|
|
7585
8067
|
// src/Data/Dict.ts
|
|
7586
8068
|
var DictIs = {
|
|
7587
8069
|
empty: (m) => m.size === 0,
|
|
@@ -8970,6 +9452,7 @@ var Uniq = {
|
|
|
8970
9452
|
export {
|
|
8971
9453
|
Arr,
|
|
8972
9454
|
BigNum,
|
|
9455
|
+
Bool,
|
|
8973
9456
|
Brand,
|
|
8974
9457
|
Combinable,
|
|
8975
9458
|
Deferred,
|