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