@nlozgachev/pipelined 0.19.0 → 0.21.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/core.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as Maybe, W as WithValue, a as WithLog, R as Result, b as WithKind, c as WithError, T as Task, d as WithErrors, e as WithFirst, f as WithSecond } from './Task-DUdIQm-Q.js';
2
- export { D as Deferred, E as Err, N as None, O as Ok, S as Some } from './Task-DUdIQm-Q.js';
1
+ import { M as Maybe, W as WithValue, a as WithLog, D as Deferred, R as Result, b as WithKind, c as WithError, d as RetryOptions, e as TimeoutOptions, f as WithMs, g as WithTrailing, h as WithRetry, i as WithTimeout, j as WithLeading, k as WithMaxWait, l as WithN, m as WithOverflow, n as WithKey, o as WithPerKey, p as WithMinInterval, q as WithCooldown, r as WithMaxSize, s as WithDedupe, t as WithConcurrency, u as WithSize, T as Task, v as WithErrors, w as WithFirst, x as WithSecond } from './Task-GSGtQO1m.js';
2
+ export { E as Err, N as None, O as Ok, S as Some } from './Task-GSGtQO1m.js';
3
3
  import { N as NonEmptyList } from './NonEmptyList-BlGFjor5.js';
4
4
 
5
5
  /** Keys of T for which undefined is assignable (i.e. optional fields). */
@@ -399,6 +399,600 @@ declare namespace Logged {
399
399
  const run: <W, A>(data: Logged<W, A>) => readonly [A, ReadonlyArray<W>];
400
400
  }
401
401
 
402
+ /**
403
+ * A reusable description of async work — decoupled from execution strategy and lifetime.
404
+ *
405
+ * Separate concerns:
406
+ * - **What** to do: encoded in the `Op` via `Op.create`
407
+ * - **How** to execute: chosen at `Op.interpret` time (restartable, exclusive, queue, etc.)
408
+ *
409
+ * An `Op` never runs on its own. It only executes when passed to `Op.interpret`, which
410
+ * attaches a concurrency strategy and returns a `Manager` that owns the execution.
411
+ *
412
+ * @example
413
+ * ```ts
414
+ * const fetchUser = Op.create(
415
+ * (id: string, signal) => fetch(`/users/${id}`, { signal }).then(r => r.json()),
416
+ * (e) => new ApiError(e),
417
+ * );
418
+ *
419
+ * const manager = Op.interpret(fetchUser, { strategy: "restartable" });
420
+ * manager.subscribe(state => {
421
+ * if (state.kind === "Pending") showSpinner();
422
+ * if (state.kind === "Ok") render(state.value);
423
+ * if (state.kind === "Err") showError(state.error);
424
+ * if (state.kind === "Nil") resetUI();
425
+ * });
426
+ * manager.run(userId);
427
+ * ```
428
+ */
429
+ type Op<I, E, A> = {
430
+ /**
431
+ * @internal — Used by `Op.interpret`. Do not call directly.
432
+ * Returns `null` when the operation was aborted (signal fired before factory resolved).
433
+ */
434
+ readonly _factory: (input: I, signal: AbortSignal) => Deferred<Result<E, A> | null>;
435
+ };
436
+ declare namespace Op {
437
+ /**
438
+ * The three terminal states of a completed async operation.
439
+ *
440
+ * - `Ok` — produced a value.
441
+ * - `Err` — produced a typed error.
442
+ * - `Nil` — completed without a value or error. The `reason` field says why:
443
+ * `"aborted"` — `abort()` was called; `"dropped"` — a new `run()` was ignored
444
+ * because the strategy was already busy; `"replaced"` — a newer `run()` took
445
+ * over a call that was already running; `"evicted"` — a newer `run()` took over
446
+ * a call that was waiting and had not yet started.
447
+ */
448
+ type Outcome<E, A> = Ok<A> | Err<E> | Nil;
449
+ /** A successful outcome with a value. */
450
+ type Ok<A> = WithKind<"Ok"> & WithValue<A>;
451
+ /** A failed outcome with a typed error. */
452
+ type Err<E> = WithKind<"Err"> & WithError<E>;
453
+ /**
454
+ * An outcome that produced nothing. `reason` identifies why:
455
+ * - `"aborted"` — `abort()` was called explicitly.
456
+ * - `"dropped"` — the invocation was ignored because the strategy was busy.
457
+ * - `"replaced"` — a newer invocation took over a call that was already running.
458
+ * - `"evicted"` — a newer invocation took a slot from a call that was waiting and
459
+ * had not yet started (buffered slot, debounce timer, throttle trailing slot).
460
+ */
461
+ type Nil = WithKind<"Nil"> & {
462
+ readonly reason: NilReason;
463
+ };
464
+ /** The reason a `Nil` outcome was produced. */
465
+ type NilReason = "aborted" | "dropped" | "replaced" | "evicted";
466
+ /** A `Nil` produced by an explicit `abort()` call. */
467
+ type AbortedNil = Nil & {
468
+ readonly reason: "aborted";
469
+ };
470
+ /** A `Nil` produced when an invocation was silently ignored (strategy was busy). */
471
+ type DroppedNil = Nil & {
472
+ readonly reason: "dropped";
473
+ };
474
+ /** A `Nil` produced when a newer invocation took over a call that was already running. */
475
+ type ReplacedNil = Nil & {
476
+ readonly reason: "replaced";
477
+ };
478
+ /** A `Nil` produced when a newer invocation took a slot from a call that was waiting and had not yet started. */
479
+ type EvictedNil = Nil & {
480
+ readonly reason: "evicted";
481
+ };
482
+ /** The full set of states a manager can emit, including transient states. */
483
+ type State<E, A> = Idle | Pending | Queued | Retrying<E> | Outcome<E, A>;
484
+ /** The manager has not been run yet (initial state). */
485
+ type Idle = WithKind<"Idle">;
486
+ /** An operation is in-flight. */
487
+ type Pending = WithKind<"Pending">;
488
+ /** An operation is waiting in a queue. `position` is 0-indexed (0 = next to run). */
489
+ type Queued = WithKind<"Queued"> & {
490
+ readonly position: number;
491
+ };
492
+ /** A retry attempt is about to start. */
493
+ type Retrying<E> = WithKind<"Retrying"> & {
494
+ readonly attempt: number;
495
+ readonly lastError: E;
496
+ readonly nextRetryIn?: number;
497
+ };
498
+ /**
499
+ * A stateful execution manager. `run()` both emits state transitions through
500
+ * subscribers and returns a `Deferred` tied to that specific invocation.
501
+ * `S` is narrowed to only the states reachable for this manager's strategy
502
+ * and configuration.
503
+ *
504
+ * @example
505
+ * ```ts
506
+ * const manager = Op.interpret(saveConfig, { strategy: "exclusive" });
507
+ *
508
+ * manager.subscribe(state => {
509
+ * if (state.kind === "Pending") lockForm();
510
+ * if (state.kind === "Ok") toast("Saved");
511
+ * if (state.kind === "Err") toast(`Error: ${state.error.message}`);
512
+ * });
513
+ *
514
+ * // Fire and subscribe (subscriber pattern)
515
+ * manager.run(formData);
516
+ *
517
+ * // Or await the specific invocation's outcome
518
+ * const result = await manager.run(formData);
519
+ * if (Op.isNil(result)) return; // dropped — another save was in-flight
520
+ * ```
521
+ */
522
+ type Manager<I, E, A, S extends State<E, A>> = {
523
+ /** The current state. Useful for synchronous reads (e.g., `useSyncExternalStore`). */
524
+ readonly state: S;
525
+ /**
526
+ * Submits an invocation. Emits state transitions via subscribers and returns a
527
+ * `Deferred` that resolves to the terminal outcome for this specific invocation.
528
+ * `Nil` means this invocation was not executed (dropped, replaced, or aborted).
529
+ */
530
+ run: (input: I) => Deferred<Exclude<S, Idle | Pending | Queued | Retrying<E>>>;
531
+ /**
532
+ * Cancels any in-flight operation and clears the queue.
533
+ * Every pending `run()` Deferred — including queued invocations — settles to `AbortedNil`.
534
+ * Resolution is asynchronous; no Deferred hangs indefinitely.
535
+ */
536
+ abort: () => void;
537
+ /**
538
+ * Registers a subscriber for state transitions. Returns an unsubscribe function.
539
+ * The callback fires immediately with the current state if the manager is not idle.
540
+ */
541
+ subscribe: (cb: (state: S) => void) => () => void;
542
+ };
543
+ /**
544
+ * A stateful manager that maintains independent per-key execution slots.
545
+ * Different keys run in parallel; the same key follows the `perKey` sub-strategy.
546
+ * `abort(key)` cancels a specific key; `abort()` cancels all.
547
+ * `state` is a map of each key's last known state — updated on every transition.
548
+ *
549
+ * @example
550
+ * ```ts
551
+ * const getUser = Op.interpret(fetchUser, {
552
+ * strategy: "keyed",
553
+ * key: (input) => input.id,
554
+ * perKey: "exclusive",
555
+ * });
556
+ *
557
+ * getUser.subscribe((map) => {
558
+ * for (const [id, state] of map) {
559
+ * if (state.kind === "Pending") showSpinner(id);
560
+ * if (state.kind === "Ok") render(id, state.value);
561
+ * }
562
+ * });
563
+ *
564
+ * getUser.run({ id: "user-1" }); // starts key "user-1"
565
+ * getUser.run({ id: "user-2" }); // starts key "user-2" in parallel
566
+ * ```
567
+ */
568
+ type KeyedManager<I, K, E, PerKeyS> = {
569
+ /** Current state map. Keys are present from first `run()` through their last terminal state. */
570
+ readonly state: ReadonlyMap<K, PerKeyS>;
571
+ /**
572
+ * Submits an invocation for the key derived from the input. Returns a `Deferred` tied
573
+ * to this specific invocation. Same-key behaviour is controlled by `perKey`.
574
+ */
575
+ run: (input: I) => Deferred<Exclude<PerKeyS, Pending | Retrying<E>>>;
576
+ /** Cancels the in-flight operation for a specific key, or all keys if omitted. */
577
+ abort: (key?: K) => void;
578
+ /**
579
+ * Registers a subscriber. The callback receives a fresh snapshot of the state map
580
+ * on every transition. Returns an unsubscribe function.
581
+ * Fires immediately with the current map if any key is active.
582
+ */
583
+ subscribe: (cb: (state: ReadonlyMap<K, PerKeyS>) => void) => () => void;
584
+ };
585
+ /** States reachable by a `once` manager (no retry). */
586
+ type OnceState<E, A> = Idle | Pending | Ok<A> | Err<E> | AbortedNil | DroppedNil;
587
+ /** States reachable by a `once` manager with retry configured. */
588
+ type RetryableOnceState<E, A> = Idle | Pending | Retrying<E> | Ok<A> | Err<E> | AbortedNil | DroppedNil;
589
+ /** States reachable by a `restartable` manager (no retry). */
590
+ type RestartableState<E, A> = Idle | Pending | Ok<A> | Err<E> | AbortedNil | ReplacedNil;
591
+ /** States reachable by a `restartable` manager with retry configured. */
592
+ type RetryableRestartableState<E, A> = Idle | Pending | Retrying<E> | Ok<A> | Err<E> | AbortedNil | ReplacedNil;
593
+ /** States reachable by an `exclusive` manager (no retry). */
594
+ type ExclusiveState<E, A> = Idle | Pending | Ok<A> | Err<E> | AbortedNil | DroppedNil;
595
+ /** States reachable by an `exclusive` manager with retry configured. */
596
+ type RetryableExclusiveState<E, A> = Idle | Pending | Retrying<E> | Ok<A> | Err<E> | AbortedNil | DroppedNil;
597
+ /** States reachable by a `queue` manager (no retry, no overflow, no dedupe). */
598
+ type QueueState<E, A> = Idle | Pending | Queued | Ok<A> | Err<E> | AbortedNil;
599
+ /** States reachable by a `queue` manager with retry (no overflow, no dedupe). */
600
+ type RetryableQueueState<E, A> = Idle | Pending | Queued | Retrying<E> | Ok<A> | Err<E> | AbortedNil;
601
+ /** States reachable by a `queue` manager with `overflow:"drop"` or `dedupe` (no retry). */
602
+ type QueueDropState<E, A> = Idle | Pending | Queued | Ok<A> | Err<E> | AbortedNil | DroppedNil;
603
+ /** States reachable by a `queue` manager with `overflow:"drop"` or `dedupe`, with retry. */
604
+ type RetryableQueueDropState<E, A> = Idle | Pending | Queued | Retrying<E> | Ok<A> | Err<E> | AbortedNil | DroppedNil;
605
+ /** States reachable by a `queue` manager with `overflow:"replace-last"` and no `dedupe` (no retry). */
606
+ type QueueReplaceState<E, A> = Idle | Pending | Queued | Ok<A> | Err<E> | AbortedNil | EvictedNil;
607
+ /** States reachable by a `queue` manager with `overflow:"replace-last"` and no `dedupe`, with retry. */
608
+ type RetryableQueueReplaceState<E, A> = Idle | Pending | Queued | Retrying<E> | Ok<A> | Err<E> | AbortedNil | EvictedNil;
609
+ /** States reachable by a `queue` manager with `overflow:"replace-last"` AND `dedupe` (no retry). */
610
+ type QueueDropAndReplaceState<E, A> = Idle | Pending | Queued | Ok<A> | Err<E> | AbortedNil | DroppedNil | EvictedNil;
611
+ /** States reachable by a `queue` manager with `overflow:"replace-last"` AND `dedupe`, with retry. */
612
+ type RetryableQueueDropAndReplaceState<E, A> = Idle | Pending | Queued | Retrying<E> | Ok<A> | Err<E> | AbortedNil | DroppedNil | EvictedNil;
613
+ /** States reachable by a `buffered` manager (no retry). */
614
+ type BufferedState<E, A> = Idle | Pending | Queued | Ok<A> | Err<E> | AbortedNil | EvictedNil;
615
+ /** States reachable by a `buffered` manager with retry configured. */
616
+ type RetryableBufferedState<E, A> = Idle | Pending | Queued | Retrying<E> | Ok<A> | Err<E> | AbortedNil | EvictedNil;
617
+ /** States reachable by a `debounced` manager (no retry). */
618
+ type DebouncedState<E, A> = Idle | Pending | Ok<A> | Err<E> | AbortedNil | EvictedNil;
619
+ /** States reachable by a `debounced` manager with retry configured. */
620
+ type RetryableDebouncedState<E, A> = Idle | Pending | Retrying<E> | Ok<A> | Err<E> | AbortedNil | EvictedNil;
621
+ /** States reachable by a `throttled` manager (leading-only, no retry). */
622
+ type ThrottledState<E, A> = Idle | Pending | Ok<A> | Err<E> | AbortedNil | DroppedNil;
623
+ /** States reachable by a `throttled` manager (leading-only, with retry). */
624
+ type RetryableThrottledState<E, A> = Idle | Pending | Retrying<E> | Ok<A> | Err<E> | AbortedNil | DroppedNil;
625
+ /** States reachable by a `throttled` manager with `trailing: true` (no retry). */
626
+ type ThrottledTrailingState<E, A> = Idle | Pending | Ok<A> | Err<E> | AbortedNil | EvictedNil;
627
+ /** States reachable by a `throttled` manager with `trailing: true` and retry. */
628
+ type RetryableThrottledTrailingState<E, A> = Idle | Pending | Retrying<E> | Ok<A> | Err<E> | AbortedNil | EvictedNil;
629
+ /** States reachable by a `concurrent` manager with `overflow: "queue"` (no retry). */
630
+ type ConcurrentQueueState<E, A> = Idle | Pending | Queued | Ok<A> | Err<E> | AbortedNil;
631
+ /** States reachable by a `concurrent` manager with `overflow: "queue"` and retry. */
632
+ type RetryableConcurrentQueueState<E, A> = Idle | Pending | Queued | Retrying<E> | Ok<A> | Err<E> | AbortedNil;
633
+ /** States reachable by a `concurrent` manager with `overflow: "drop"` (no retry). */
634
+ type ConcurrentDropState<E, A> = Idle | Pending | Ok<A> | Err<E> | AbortedNil | DroppedNil;
635
+ /** States reachable by a `concurrent` manager with `overflow: "drop"` and retry. */
636
+ type RetryableConcurrentDropState<E, A> = Idle | Pending | Retrying<E> | Ok<A> | Err<E> | AbortedNil | DroppedNil;
637
+ /** Per-key state union for a `keyed` manager with `perKey: "exclusive"`. */
638
+ type KeyedExclusivePerKey<E, A> = Pending | Ok<A> | Err<E> | AbortedNil | DroppedNil;
639
+ /** Per-key state union for a `keyed` manager with `perKey: "restartable"`. */
640
+ type KeyedRestartablePerKey<E, A> = Pending | Ok<A> | Err<E> | AbortedNil | ReplacedNil;
641
+ type RetryOptions<E> = RetryOptions<E>;
642
+ type TimeoutOptions<E> = TimeoutOptions<E>;
643
+ /**
644
+ * Creates a `Nil` outcome with a reason.
645
+ *
646
+ * @example
647
+ * ```ts
648
+ * Op.nil("aborted"); // { kind: "Nil", reason: "aborted" }
649
+ * Op.nil("dropped"); // { kind: "Nil", reason: "dropped" }
650
+ * Op.nil("replaced"); // { kind: "Nil", reason: "replaced" }
651
+ * Op.nil("evicted"); // { kind: "Nil", reason: "evicted" }
652
+ * ```
653
+ */
654
+ const nil: (reason: NilReason) => Nil;
655
+ /**
656
+ * Creates an `Op` from an async factory and an error mapper.
657
+ *
658
+ * The factory receives the input and an `AbortSignal`. Operations that support
659
+ * cancellation (like `fetch`) should thread the signal through. The error mapper
660
+ * converts any thrown value into a typed error; it is never called for aborts.
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * const saveProfile = Op.create(
665
+ * (data: ProfileData, signal) =>
666
+ * fetch("/profile", { method: "POST", body: JSON.stringify(data), signal })
667
+ * .then(r => r.json()),
668
+ * (e) => new ApiError(e),
669
+ * );
670
+ * ```
671
+ */
672
+ const create: <I, E, A>(factory: (input: I, signal: AbortSignal) => Promise<A>, onError: (e: unknown) => E) => Op<I, E, A>;
673
+ /**
674
+ * Creates a successful Outcome.
675
+ *
676
+ * @example
677
+ * ```ts
678
+ * Op.ok(42); // { kind: "Ok", value: 42 }
679
+ * ```
680
+ */
681
+ const ok: <A>(value: A) => Ok<A>;
682
+ /**
683
+ * Creates a failed Outcome with a typed error.
684
+ *
685
+ * @example
686
+ * ```ts
687
+ * Op.err(new ApiError("not found")); // { kind: "Err", error: ApiError }
688
+ * ```
689
+ */
690
+ const err: <E>(error: E) => Err<E>;
691
+ /**
692
+ * Returns `true` if the Outcome is `Ok`.
693
+ *
694
+ * @example
695
+ * ```ts
696
+ * if (Op.isOk(outcome)) render(outcome.value);
697
+ * ```
698
+ */
699
+ const isOk: <E, A>(outcome: Outcome<E, A>) => outcome is Ok<A>;
700
+ /**
701
+ * Returns `true` if the Outcome is `Err`.
702
+ *
703
+ * @example
704
+ * ```ts
705
+ * if (Op.isErr(outcome)) logger.error(outcome.error);
706
+ * ```
707
+ */
708
+ const isErr: <E, A>(outcome: Outcome<E, A>) => outcome is Err<E>;
709
+ /**
710
+ * Returns `true` if the Outcome is `Nil`.
711
+ *
712
+ * @example
713
+ * ```ts
714
+ * if (Op.isNil(outcome)) resetUI();
715
+ * ```
716
+ */
717
+ const isNil: <E, A>(outcome: Outcome<E, A>) => outcome is Nil;
718
+ /**
719
+ * Pattern matches on an Outcome using named case handlers.
720
+ *
721
+ * @example
722
+ * ```ts
723
+ * Op.match({
724
+ * ok: (user) => render(user),
725
+ * err: (e) => showError(e.message),
726
+ * nil: () => resetUI(),
727
+ * })(outcome);
728
+ * ```
729
+ */
730
+ const match: <E, A, B>(cases: {
731
+ ok: (a: A) => B;
732
+ err: (e: E) => B;
733
+ nil: () => B;
734
+ }) => (outcome: Outcome<E, A>) => B;
735
+ /**
736
+ * Eliminates an Outcome with positional handlers.
737
+ * Order: `onErr`, `onOk`, `onNil` — mirrors `Result.fold` for the first two.
738
+ *
739
+ * @example
740
+ * ```ts
741
+ * Op.fold(
742
+ * (e) => `error: ${e.message}`,
743
+ * (v) => `value: ${v}`,
744
+ * () => "nothing",
745
+ * )(outcome);
746
+ * ```
747
+ */
748
+ const fold: <E, A, B>(onErr: (e: E) => B, onOk: (a: A) => B, onNil: () => B) => (outcome: Outcome<E, A>) => B;
749
+ /**
750
+ * Returns the success value, or the result of `defaultValue()` for `Err` or `Nil`.
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * Op.getOrElse(() => [] as User[])(outcome);
755
+ * ```
756
+ */
757
+ const getOrElse: <E, A, B>(defaultValue: () => B) => (outcome: Outcome<E, A>) => A | B;
758
+ /**
759
+ * Transforms the success value. `Err` and `Nil` pass through unchanged.
760
+ *
761
+ * @example
762
+ * ```ts
763
+ * pipe(outcome, Op.map(user => user.name));
764
+ * ```
765
+ */
766
+ const map: <E, A, B>(f: (a: A) => B) => (outcome: Outcome<E, A>) => Outcome<E, B>;
767
+ /**
768
+ * Transforms the error value. `Ok` and `Nil` pass through unchanged.
769
+ *
770
+ * @example
771
+ * ```ts
772
+ * pipe(outcome, Op.mapError(e => e.message));
773
+ * ```
774
+ */
775
+ const mapError: <E, F, A>(f: (e: E) => F) => (outcome: Outcome<E, A>) => Outcome<F, A>;
776
+ /**
777
+ * Chains Outcome computations. Runs `f` on `Ok`; `Err` and `Nil` pass through.
778
+ *
779
+ * @example
780
+ * ```ts
781
+ * pipe(
782
+ * outcome,
783
+ * Op.chain(user => user.active ? Op.ok(user) : Op.err(new Error("inactive"))),
784
+ * );
785
+ * ```
786
+ */
787
+ const chain: <E, A, B>(f: (a: A) => Outcome<E, B>) => (outcome: Outcome<E, A>) => Outcome<E, B>;
788
+ /**
789
+ * Runs a side effect on the success value without changing the Outcome.
790
+ *
791
+ * @example
792
+ * ```ts
793
+ * pipe(outcome, Op.tap(user => console.log("loaded", user.id)));
794
+ * ```
795
+ */
796
+ const tap: <E, A>(f: (a: A) => void) => (outcome: Outcome<E, A>) => Outcome<E, A>;
797
+ /**
798
+ * Provides a fallback Outcome when the result is `Err`. `Ok` and `Nil` pass through.
799
+ *
800
+ * @example
801
+ * ```ts
802
+ * pipe(
803
+ * outcome,
804
+ * Op.recover(e => e.isRetryable ? Op.ok(cachedValue) : Op.err(e)),
805
+ * );
806
+ * ```
807
+ */
808
+ const recover: <E, A, B>(f: (e: E) => Outcome<E, B>) => (outcome: Outcome<E, A>) => Outcome<E, A | B>;
809
+ /**
810
+ * Converts an Outcome to a `Result`. `Nil` becomes `Err(onNil())`.
811
+ *
812
+ * @example
813
+ * ```ts
814
+ * Op.toResult(() => new ApiError("no result"))(outcome);
815
+ * ```
816
+ */
817
+ const toResult: <E, A>(onNil: () => E) => (outcome: Outcome<E, A>) => Result<E, A>;
818
+ /**
819
+ * Converts an Outcome to a `Maybe`. `Ok` becomes `Some`; `Err` and `Nil` become `None`.
820
+ *
821
+ * @example
822
+ * ```ts
823
+ * Op.toMaybe(outcome); // Maybe<User>
824
+ * ```
825
+ */
826
+ const toMaybe: <E, A>(outcome: Outcome<E, A>) => Maybe<A>;
827
+ /**
828
+ * Resolves when all invocations settle, returning their outcomes in order.
829
+ * An alternative to `Promise.all` that stays within the `Op` type system.
830
+ *
831
+ * @example
832
+ * ```ts
833
+ * const [a, b] = await Op.all([manager.run(inputA), manager.run(inputB)]);
834
+ * ```
835
+ */
836
+ const all: <E, A>(invocations: ReadonlyArray<Deferred<Outcome<E, A>>>) => Deferred<ReadonlyArray<Outcome<E, A>>>;
837
+ /**
838
+ * Resolves to the outcome of whichever invocation settles first.
839
+ * An alternative to `Promise.race` that stays within the `Op` type system.
840
+ *
841
+ * @example
842
+ * ```ts
843
+ * const winner = await Op.race([manager.run(inputA), manager.run(inputB)]);
844
+ * ```
845
+ */
846
+ const race: <E, A>(invocations: ReadonlyArray<Deferred<Outcome<E, A>>>) => Deferred<Outcome<E, A>>;
847
+ /**
848
+ * Attaches a concurrency strategy to an `Op`, returning a `Manager`.
849
+ *
850
+ * Strategy is data, not a method name. The `S` type parameter is narrowed to only
851
+ * the states reachable for the chosen strategy and options — subscribers cannot
852
+ * reference states that cannot occur.
853
+ *
854
+ * **Strategies:**
855
+ * - `once` — fires once. Only the first `run()` executes; subsequent calls
856
+ * return `DroppedNil` immediately. State is permanent after completion.
857
+ * - `restartable` — new call cancels the previous (`ReplacedNil`). Only the latest result matters.
858
+ * - `exclusive` — new calls while in-flight return `DroppedNil` immediately.
859
+ * - `queue` — calls run in submission order. `Queued` state shows position.
860
+ * - `buffered` — 1 in-flight + 1 waiting slot. Newer calls evict the slot (`EvictedNil`).
861
+ * - `debounced` — waits `ms` ms of quiet before starting. Earlier calls get `EvictedNil`.
862
+ *
863
+ * **`retry` and `timeout`** can be combined with any strategy. Both are applied
864
+ * internally per `run()` call — set the policy once, not at every call site.
865
+ * The timeout wraps the entire retry sequence (one deadline for all attempts).
866
+ * When `retry` is present, `Retrying` is added to the subscriber type.
867
+ *
868
+ * @example
869
+ * ```ts
870
+ * // Load once on mount — further calls are no-ops
871
+ * const getUser = Op.interpret(fetchUser, { strategy: "once" });
872
+ * getUser.subscribe(state => {
873
+ * if (state.kind === "Pending") showSpinner();
874
+ * if (state.kind === "Ok") render(state.value);
875
+ * });
876
+ * getUser.run(userId);
877
+ *
878
+ * // Search: cancel the previous query when a new one starts
879
+ * const search = Op.interpret(searchOp, { strategy: "restartable" });
880
+ *
881
+ * // Form submit: ignore double-clicks while in-flight
882
+ * const submit = Op.interpret(submitOp, {
883
+ * strategy: "exclusive",
884
+ * retry: { attempts: 3, backoff: n => n * 500 },
885
+ * timeout: { ms: 10_000, onTimeout: () => new ApiError("timed out") },
886
+ * });
887
+ *
888
+ * // Auto-save: current save commits fully; latest pending edit saves next
889
+ * const save = Op.interpret(saveOp, { strategy: "buffered" });
890
+ * ```
891
+ */
892
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
893
+ strategy: "throttled";
894
+ } & WithMs & WithTrailing & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableThrottledTrailingState<E, A>>;
895
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
896
+ strategy: "throttled";
897
+ } & WithMs & WithTrailing & WithTimeout<E>): Manager<I, E, A, ThrottledTrailingState<E, A>>;
898
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
899
+ strategy: "throttled";
900
+ } & WithMs & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableThrottledState<E, A>>;
901
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
902
+ strategy: "throttled";
903
+ } & WithMs & WithTimeout<E>): Manager<I, E, A, ThrottledState<E, A>>;
904
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
905
+ strategy: "debounced";
906
+ } & WithMs & WithLeading & WithMaxWait & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableDebouncedState<E, A>>;
907
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
908
+ strategy: "debounced";
909
+ } & WithMs & WithLeading & WithMaxWait & WithTimeout<E>): Manager<I, E, A, DebouncedState<E, A>>;
910
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
911
+ strategy: "debounced";
912
+ } & WithMs & WithLeading & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableDebouncedState<E, A>>;
913
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
914
+ strategy: "debounced";
915
+ } & WithMs & WithLeading & WithTimeout<E>): Manager<I, E, A, DebouncedState<E, A>>;
916
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
917
+ strategy: "debounced";
918
+ } & WithMs & WithMaxWait & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableDebouncedState<E, A>>;
919
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
920
+ strategy: "debounced";
921
+ } & WithMs & WithMaxWait & WithTimeout<E>): Manager<I, E, A, DebouncedState<E, A>>;
922
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
923
+ strategy: "debounced";
924
+ } & WithMs & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableDebouncedState<E, A>>;
925
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
926
+ strategy: "debounced";
927
+ } & WithMs & WithTimeout<E>): Manager<I, E, A, DebouncedState<E, A>>;
928
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
929
+ strategy: "concurrent";
930
+ } & WithN & WithOverflow<"queue"> & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableConcurrentQueueState<E, A>>;
931
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
932
+ strategy: "concurrent";
933
+ } & WithN & WithOverflow<"queue"> & WithTimeout<E>): Manager<I, E, A, ConcurrentQueueState<E, A>>;
934
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
935
+ strategy: "concurrent";
936
+ } & WithN & WithOverflow<"drop"> & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableConcurrentDropState<E, A>>;
937
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
938
+ strategy: "concurrent";
939
+ } & WithN & WithOverflow<"drop"> & WithTimeout<E>): Manager<I, E, A, ConcurrentDropState<E, A>>;
940
+ function interpret<I, K, E, A>(op: Op<I, E, A>, options: {
941
+ strategy: "keyed";
942
+ } & WithKey<I, K> & WithPerKey<"exclusive"> & WithTimeout<E>): KeyedManager<I, K, E, KeyedExclusivePerKey<E, A>>;
943
+ function interpret<I, K, E, A>(op: Op<I, E, A>, options: {
944
+ strategy: "keyed";
945
+ } & WithKey<I, K> & WithPerKey<"restartable"> & WithTimeout<E>): KeyedManager<I, K, E, KeyedRestartablePerKey<E, A>>;
946
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
947
+ strategy: "once";
948
+ } & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableOnceState<E, A>>;
949
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
950
+ strategy: "once";
951
+ } & WithTimeout<E>): Manager<I, E, A, OnceState<E, A>>;
952
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
953
+ strategy: "restartable";
954
+ } & WithMinInterval & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableRestartableState<E, A>>;
955
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
956
+ strategy: "restartable";
957
+ } & WithMinInterval & WithTimeout<E>): Manager<I, E, A, RestartableState<E, A>>;
958
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
959
+ strategy: "exclusive";
960
+ } & WithCooldown & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableExclusiveState<E, A>>;
961
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
962
+ strategy: "exclusive";
963
+ } & WithCooldown & WithTimeout<E>): Manager<I, E, A, ExclusiveState<E, A>>;
964
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
965
+ strategy: "queue";
966
+ } & WithMaxSize & WithOverflow<"replace-last"> & WithDedupe<I> & WithRetry<E> & WithConcurrency & WithTimeout<E>): Manager<I, E, A, RetryableQueueDropAndReplaceState<E, A>>;
967
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
968
+ strategy: "queue";
969
+ } & WithMaxSize & WithOverflow<"replace-last"> & WithDedupe<I> & WithConcurrency & WithTimeout<E>): Manager<I, E, A, QueueDropAndReplaceState<E, A>>;
970
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
971
+ strategy: "queue";
972
+ } & WithMaxSize & WithOverflow<"replace-last"> & WithRetry<E> & WithConcurrency & WithTimeout<E>): Manager<I, E, A, RetryableQueueReplaceState<E, A>>;
973
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
974
+ strategy: "queue";
975
+ } & WithMaxSize & WithOverflow<"replace-last"> & WithConcurrency & WithTimeout<E>): Manager<I, E, A, QueueReplaceState<E, A>>;
976
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
977
+ strategy: "queue";
978
+ } & (WithMaxSize | WithDedupe<I>) & WithRetry<E> & WithConcurrency & WithTimeout<E>): Manager<I, E, A, RetryableQueueDropState<E, A>>;
979
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
980
+ strategy: "queue";
981
+ } & (WithMaxSize | WithDedupe<I>) & WithConcurrency & WithTimeout<E>): Manager<I, E, A, QueueDropState<E, A>>;
982
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
983
+ strategy: "queue";
984
+ } & WithRetry<E> & WithConcurrency & WithTimeout<E>): Manager<I, E, A, RetryableQueueState<E, A>>;
985
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
986
+ strategy: "queue";
987
+ } & WithConcurrency & WithTimeout<E>): Manager<I, E, A, QueueState<E, A>>;
988
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
989
+ strategy: "buffered";
990
+ } & WithSize & WithRetry<E> & WithTimeout<E>): Manager<I, E, A, RetryableBufferedState<E, A>>;
991
+ function interpret<I, E, A>(op: Op<I, E, A>, options: {
992
+ strategy: "buffered";
993
+ } & WithSize & WithTimeout<E>): Manager<I, E, A, BufferedState<E, A>>;
994
+ }
995
+
402
996
  /**
403
997
  * A function from `A` to `A is B` — a type predicate paired with a runtime check.
404
998
  *
@@ -1053,7 +1647,7 @@ declare namespace RemoteData {
1053
1647
  * ```ts
1054
1648
  * const fetchUser = (id: string): TaskResult<Error, User> =>
1055
1649
  * TaskResult.tryCatch(
1056
- * () => fetch(`/users/${id}`).then(r => r.json()),
1650
+ * (signal) => fetch(`/users/${id}`, { signal }).then(r => r.json()),
1057
1651
  * (e) => new Error(`Failed to fetch user: ${e}`)
1058
1652
  * );
1059
1653
  * ```
@@ -1071,17 +1665,18 @@ declare namespace TaskResult {
1071
1665
  /**
1072
1666
  * Creates a TaskResult from a function that may throw.
1073
1667
  * Catches any errors and transforms them using the onError function.
1668
+ * The factory optionally receives an `AbortSignal` forwarded from the call site.
1074
1669
  *
1075
1670
  * @example
1076
1671
  * ```ts
1077
- * const parseJson = (s: string): TaskResult<string, unknown> =>
1672
+ * const fetchUser = (id: string): TaskResult<string, User> =>
1078
1673
  * TaskResult.tryCatch(
1079
- * async () => JSON.parse(s),
1080
- * (e) => `Parse error: ${e}`
1674
+ * (signal) => fetch(`/users/${id}`, { signal }).then(r => r.json()),
1675
+ * String
1081
1676
  * );
1082
1677
  * ```
1083
1678
  */
1084
- const tryCatch: <E, A>(f: () => Promise<A>, onError: (e: unknown) => E) => TaskResult<E, A>;
1679
+ const tryCatch: <E, A>(f: (signal?: AbortSignal) => Promise<A>, onError: (e: unknown) => E) => TaskResult<E, A>;
1085
1680
  /**
1086
1681
  * Transforms the success value inside a TaskResult.
1087
1682
  */
@@ -1121,75 +1716,6 @@ declare namespace TaskResult {
1121
1716
  * Useful for logging or debugging.
1122
1717
  */
1123
1718
  const tap: <E, A>(f: (a: A) => void) => (data: TaskResult<E, A>) => TaskResult<E, A>;
1124
- /**
1125
- * Re-runs a TaskResult on `Err` with configurable attempts, backoff, and retry condition.
1126
- *
1127
- * @param options.attempts - Total number of attempts (1 = no retry, 3 = up to 3 tries)
1128
- * @param options.backoff - Fixed delay in ms, or a function `(attempt) => ms` for computed delay
1129
- * @param options.when - Only retry when this returns true; defaults to always retry on Err
1130
- *
1131
- * @example
1132
- * ```ts
1133
- * // Retry up to 3 times with exponential backoff
1134
- * pipe(
1135
- * fetchUser,
1136
- * TaskResult.retry({ attempts: 3, backoff: n => n * 1000 })
1137
- * );
1138
- *
1139
- * // Only retry on network errors, not auth errors
1140
- * pipe(
1141
- * fetchUser,
1142
- * TaskResult.retry({ attempts: 3, when: e => e instanceof NetworkError })
1143
- * );
1144
- * ```
1145
- */
1146
- const retry: <E>(options: {
1147
- attempts: number;
1148
- backoff?: number | ((attempt: number) => number);
1149
- when?: (error: E) => boolean;
1150
- }) => <A>(data: TaskResult<E, A>) => TaskResult<E, A>;
1151
- /**
1152
- * Polls a TaskResult repeatedly until the success value satisfies a predicate.
1153
- * Stops immediately and returns `Err` if the task fails.
1154
- *
1155
- * `delay` accepts a fixed number of milliseconds or a function `(attempt) => ms`
1156
- * for a computed delay — useful for starting fast and slowing down over time.
1157
- *
1158
- * @example
1159
- * ```ts
1160
- * const checkJob = (id: string): TaskResult<string, { status: "pending" | "done" }> =>
1161
- * TaskResult.tryCatch(() => fetch(`/jobs/${id}`).then(r => r.json()), String);
1162
- *
1163
- * // Fixed delay: poll every 2s
1164
- * pipe(
1165
- * checkJob(jobId),
1166
- * TaskResult.pollUntil({ when: job => job.status === "done", delay: 2000 }),
1167
- * );
1168
- *
1169
- * // Computed delay: 1s, 2s, 3s, ...
1170
- * pipe(
1171
- * checkJob(jobId),
1172
- * TaskResult.pollUntil({ when: job => job.status === "done", delay: n => n * 1000 }),
1173
- * );
1174
- * ```
1175
- */
1176
- const pollUntil: <A>(options: {
1177
- when: (a: A) => boolean;
1178
- delay?: number | ((attempt: number) => number);
1179
- }) => <E>(task: TaskResult<E, A>) => TaskResult<E, A>;
1180
- /**
1181
- * Fails a TaskResult with a typed error if it does not resolve within the given time.
1182
- * Uses `Promise.race` — the underlying operation keeps running after the timeout fires.
1183
- *
1184
- * @example
1185
- * ```ts
1186
- * pipe(
1187
- * fetchUser,
1188
- * TaskResult.timeout(5000, () => new TimeoutError("fetch user timed out"))
1189
- * );
1190
- * ```
1191
- */
1192
- const timeout: <E>(ms: number, onTimeout: () => E) => <A>(data: TaskResult<E, A>) => TaskResult<E, A>;
1193
1719
  }
1194
1720
 
1195
1721
  /**
@@ -2292,4 +2818,4 @@ declare namespace Tuple {
2292
2818
  const tap: <A, B>(f: (a: A, b: B) => void) => (tuple: Tuple<A, B>) => Tuple<A, B>;
2293
2819
  }
2294
2820
 
2295
- export { type Failure, type Invalid, Lens, type Loading, Logged, Maybe, type NotAsked, Optional, Predicate, Reader, Refinement, RemoteData, Resource, Result, State, type Success, Task, TaskMaybe, TaskResult, TaskValidation, These, type TheseBoth, type TheseFirst, type TheseSecond, Tuple, type Valid, Validation };
2821
+ export { Deferred, type Failure, type Invalid, Lens, type Loading, Logged, Maybe, type NotAsked, Op, Optional, Predicate, Reader, Refinement, RemoteData, Resource, Result, State, type Success, Task, TaskMaybe, TaskResult, TaskValidation, These, type TheseBoth, type TheseFirst, type TheseSecond, Tuple, type Valid, Validation };