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