Skip to content

Task

Task: object

Defined in: Core/Task.ts:62

abortable: <A>(factory) => object

Creates a Task paired with an abort handle. Calling abort() cancels the current in-flight call immediately. Unlike a one-shot abort, calling task() again after abort() starts a fresh call with a new signal.

Each invocation of task() automatically cancels the previous in-flight call, making it safe to call repeatedly (e.g. on user input) without leaking promises.

If an outer signal is also present (passed at the call site), aborting it propagates into the internal controller.

A

(signal) => Thenable<A>

object

abort: () => void

void

task: Task<A>

const { task: poll, abort } = Task.abortable(
  (signal) => waitForEvent(bus, "ready", { signal }),
);

onUnmount(abort);
await poll();

all: <T>(tasks, options?) => Task<{ [K in string | number | symbol]: T[K] extends Task<A> ? A : never }>

Runs multiple Tasks in parallel and collects their results. An optional concurrency option limits the number of tasks executing at any given time; a provided nonpositive or non-integer value clamps to 1, and omitting the option runs all tasks unbounded. An optional signal passed at the call site is forwarded to every task, including queued tasks.

T extends readonly Task<unknown>[]

readonly [T]

number

Task<{ [K in string | number | symbol]: T[K] extends Task<A> ? A : never }>

Task.sequential to run an array of Tasks one after another in order.

Task.all([loadConfig, detectLocale, loadTheme])();
// Deferred<[Config, string, Theme]>

Task.all([loadConfig, detectLocale, loadTheme], { concurrency: 2 })();

apply: <A>(arg) => <B>(task) => Task<B>

Applies a function wrapped in a Task to a value wrapped in a Task. Both Tasks run in parallel.

A

Task<A>

<B>(task) => Task<B>

Task.all to run multiple independent Tasks in parallel.

const add = (a: number) => (b: number) => a + b;
pipe(
  Task.make(add),
  Task.apply(Task.make(5)),
  Task.apply(Task.make(3))
)(); // Deferred<8>

assign: <K, A, B>(key, transform) => (task) => Task<A & { [P in string]: B }>

Evaluates a new Task using the current accumulator and attaches the output to a new key. The key must not already exist on the accumulator — use map to update an existing field. The accumulator is expected to be a plain object (own enumerable properties).

K extends string

A extends object

B

K extends KeysOfUnion<A> ? never : K

(value) => Task<B>

(task) => Task<A & { [P in string]: B }>

pipe(
  Task.make({ a: 1 }),
  Task.assign("b", ({ a }) => Task.make(a + 1))
); // Task({ a: 1, b: 2 })

assignTo: <K>(key) => <A>(task) => Task<{ [P in string]: A }>

Converts a Task value into an object containing a single property. Initiates the pipeline accumulator record.

K extends string

K

<A>(task) => Task<{ [P in string]: A }>

pipe(Task.make(42), Task.assignTo("value")); // Task({ value: 42 })

chain: <A, B>(transform) => (task) => Task<B>

Chains Task computations. Passes the resolved value of the first Task to transform.

A

B

(value) => Task<B>

(task) => Task<B>

Task.map to transform the resolved value without creating a new Task.

const readUserId: Task<string> = Task.make(session.userId);
const loadPrefs = (id: string): Task<Preferences> =>
  Task.make(prefsCache.get(id));

pipe(
  readUserId,
  Task.chain(loadPrefs)
)(); // Deferred<Preferences>

delay: (duration) => <A>(task) => Task<A>

Delays the execution of a Task by the specified duration. Useful for debouncing or rate limiting.

Duration

<A>(task) => Task<A>

pipe(
  Task.make(42),
  Task.delay(Duration.seconds(1))
)(); // Resolves after 1 second

from: object

sync: <A>(fn) => Task<A> = syncTask

Creates a Task from a lazy synchronous thunk. Unlike Task.make(f()), from.sync does not evaluate f until the Task is called.

A

() => A

Task<A>

const t = Task.from.sync(() => Date.now()); // Date.now() not called yet
const ts = await t(); // called here, every time

make: <A>(value) => Task<A> = makeTask

Creates a Task that immediately resolves to the given value.

A

A

Task<A>

const task = Task.make(42);
const value = await task(); // 42

map: <A, B>(transform) => (task) => Task<B>

Transforms the value inside a Task.

A

B

(value) => B

(task) => Task<B>

Task.chain to sequence operations that return a Task.

pipe(
  Task.make(5),
  Task.map(n => n * 2)
)(); // Deferred<10>

Maybe: object = TaskMaybe

apply: <A>(arg) => <B>(task) => Maybe<B>

Applies a function wrapped in a Task.Maybe to a value wrapped in a Task.Maybe. Both Tasks run in parallel.

A

Maybe<A>

<B>(task) => Maybe<B>

const add = (a: number) => (b: number) => a + b;
pipe(
  Task.Maybe.make.some(add),
  Task.Maybe.apply(Task.Maybe.make.some(5)),
  Task.Maybe.apply(Task.Maybe.make.some(3)),
); // Task.Maybe resolving to Some(8)

assign: <K, A, B>(key, transform) => (task) => Maybe<A & { [P in string]: B }>

Evaluates a new Task.Maybe using the current accumulator and attaches the output to a new key.

K extends string

A extends object

B

K extends KeysOfUnion<A> ? never : K

(value) => Maybe<B>

(task) => Maybe<A & { [P in string]: B }>

pipe(
  Task.Maybe.make.some({ a: 1 }),
  Task.Maybe.assign("b", ({ a }) => Task.Maybe.make.some(a + 1))
); // Task.Maybe({ a: 1, b: 2 })

assignTo: <K>(key) => <A>(task) => Maybe<{ [P in string]: A }>

Lifts a Task.Maybe value into an accumulator object.

K extends string

K

<A>(task) => Maybe<{ [P in string]: A }>

pipe(Task.Maybe.make.some(42), Task.Maybe.assignTo("value")); // Task.Maybe({ value: 42 })

chain: <A, B>(transform) => (task) => Maybe<B> = chainTaskMaybe

Chains Task.Maybe computations. If the first resolves to Some, passes the value to transform. If the first resolves to None, propagates None.

A

B

(value) => Maybe<B>

(task) => Maybe<B>

Task.Maybe.map to transform the inner value without returning a new Task.Maybe.

pipe(
  findUser("123"),
  Task.Maybe.chain(user => findOrg(user.orgId))
)();

filter: {<A, B>(refinement): (task) => Maybe<B>; <A>(predicate): (task) => Maybe<A>; }

Filters the value inside a Task.Maybe. Returns None if the predicate fails.

<A, B>(refinement): (task) => Maybe<B>

A

B

(value) => value is B

(task) => Maybe<B>

<A>(predicate): (task) => Maybe<A>

A

(value) => boolean

(task) => Maybe<A>

pipe(
  Task.Maybe.make.some(42),
  Task.Maybe.filter(n => n > 50),
); // Task.Maybe resolving to None

from: object

Maybe: <A>(maybe) => Maybe<A>

Lifts a Maybe into a Task.Maybe.

A

Maybe<A>

Maybe<A>

Task.Maybe.from.Maybe(Maybe.make.some(42));

nullable: <A>(value) => Maybe<NonNullable<A>>

Creates a Task.Maybe from a nullable value. Returns Some if the value is not null or undefined, None otherwise.

A

A | null | undefined

Maybe<NonNullable<A>>

Task.Maybe.from.nullable(42);   // resolves to Some(42)
Task.Maybe.from.nullable(null); // resolves to None

Result: <E, A>(result) => Maybe<A>

Creates a Task.Maybe from a Result. Ok becomes Some, Error becomes None (the error value is discarded).

E

A

Result<E, A>

Maybe<A>

Task.Maybe.from.Result(Result.make.ok(42)); // resolves to Some(42)
Task.Maybe.from.Result(Result.make.err("e")); // resolves to None

Task: <A>(task) => Maybe<A>

Lifts a Task into a Task.Maybe by wrapping its result in Some.

A

Task<A>

Maybe<A>

Task.Maybe.from.Task(Task.make(42)); // resolves to Some(42)

getOrElse: <B>(fallback) => <A>(task) => Task<B | A>

Returns the value or a default if the Task.Maybe resolves to None. The default can be a different type, widening the result to Task<A | B>.

B

() => B

<A>(task) => Task<B | A>

pipe(
  Task.Maybe.make.none(),
  Task.Maybe.getOrElse(() => 0),
); // Task resolving to 0

make: object

Wraps a value in a Some inside a Task.

const task = Task.Maybe.make.some(42);
const res = await task(); // Some(42)

none: <A>() => Maybe<A> = makeNone

Creates a Task.Maybe that resolves to None.

A = never

Maybe<A>

const task = Task.Maybe.make.none();
const res = await task(); // None

some: <A>(value) => Maybe<A> = makeSome

Creates a Task.Maybe that resolves to Some(value).

A

A

Maybe<A>

const task = Task.Maybe.make.some(42);
const res = await task(); // Some(42)

map: <A, B>(transform) => (task) => Maybe<B> = mapTaskMaybe

Transforms the value inside a Task.Maybe.

A

B

(value) => B

(task) => Maybe<B>

Task.Maybe.chain to sequence operations that themselves return a Task.Maybe.

pipe(
  Task.Maybe.make.some(5),
  Task.Maybe.map(n => n * 2),
); // Task.Maybe resolving to Some(10)

match: <A, B>(cases) => (task) => Task<B>

Pattern matches on a Task.Maybe, returning a Task of the result.

A

B

() => B

(value) => B

(task) => Task<B>

pipe(
  findUser("123"),
  Task.Maybe.match({
    some: user => `Hello, ${user.name}`,
    none: () => "User not found"
  })
)();

recover: <B>(fallback) => <A>(task) => Maybe<B | A>

Recovers from a None state by providing a fallback Task.Maybe.

B = never

() => Maybe<B>

<A>(task) => Maybe<B | A>

pipe(
  Task.Maybe.make.none(),
  Task.Maybe.recover(() => Task.Maybe.make.some(42))
); // Task.Maybe(42)

run: (signal?) => <A>(task) => Deferred<Maybe<A>>

Executes a Task.Maybe with an optional signal, returning Deferred<Maybe<A>>. Use as a terminal step in a pipe chain.

AbortSignal

<A>(task) => Deferred<Maybe<A>>

const result = await pipe(
  Task.Maybe.make.some(42),
  Task.Maybe.run(),
);

struct: <T>(fields) => Maybe<{ [K in string | number | symbol]: T[K] extends Maybe<A> ? A : never }>

Combines a record of Task.Maybes into a single Task.Maybe of a record. Own string and symbol keys are processed in key order; inherited fields are not supported. Evaluates fields in parallel and returns None if any task resolves to None.

T extends Record<string, Maybe<unknown>>

T

Maybe<{ [K in string | number | symbol]: T[K] extends Maybe<A> ? A : never }>

Task.Maybe.struct({
  name: Task.Maybe.make.some("Alice"),
  age: Task.Maybe.make.some(30)
}); // Task.Maybe({ name: "Alice", age: 30 })

tap: <A>(sideEffect) => (task) => Maybe<A>

Executes a side effect on the value without changing the Task.Maybe. Useful for logging or debugging.

A

(value) => void

(task) => Maybe<A>

pipe(
  Task.Maybe.make.some(42),
  Task.Maybe.tap(n => console.log("Value:", n)),
);

to: object

Result: <E>(onNone) => <A>(task) => Result<E, A>

Converts a Task.Maybe to a Task.Result, using onNone to produce the error value.

E

() => E

<A>(task) => Result<E, A>

pipe(
  findUser("123"),
  Task.Maybe.to.Result(() => "User not found")
);

tryCatch: <A>(fn) => Maybe<A>

Creates a Task.Maybe from a Promise-returning function. Returns Some if the promise resolves, None if the factory throws or the promise rejects. The factory optionally receives an AbortSignal forwarded from the call site.

A

(signal?) => Thenable<A>

Maybe<A>

const fetchUser = Task.Maybe.tryCatch((signal) =>
  fetch("/user/1", { signal }).then(r => r.json())
);

memoize: <A>(task) => Task<A>

Creates a memoized version of a Task. The task runs at most once, and the shared result is reused by every later call. Because the run is shared, it is not tied to any caller’s signal: cancellation of a memoized task is a no-op.

A

Task<A>

Task<A>

const loadToken = Task.memoize(loadAuthToken);
const token1 = await loadToken(); // loads token
const token2 = await loadToken(); // returns cached token immediately

poll: <A>(options) => (task) => Task<A>

Polls a Task repeatedly until the result satisfies a predicate, returning that result. An optional delay duration can be inserted between polling runs. An optional attempts cap of N calls stops the loop after N calls — the last value is returned regardless of whether the predicate was satisfied. attempts is a positive integer.

A

number

Duration

(value) => boolean

(task) => Task<A>

Task.repeat to run a Task a fixed number of times.

pipe(
  checkStatus,
  Task.poll({ until: (s) => s === "ready", delay: Duration.milliseconds(500) })
)(); // polls every 500ms until status is "ready"

race: (<T>(tasks) => Task<TaskValue<T[number]>>) & (<A>(tasks) => Task<A>) = raceTask

Resolves with the value of the first Task to complete. All Tasks start immediately. When one resolves, the other tasks are cancelled (aborted) downstream.

const fast = Task.make("fast");
const slow = Task.delay(Duration.milliseconds(200))(Task.make("slow"));

await Task.race([fast, slow])(); // "fast"

repeat: (options) => <A>(task) => Task<readonly A[]>

Runs a Task a fixed number of times sequentially, collecting all results into an array. An optional delay duration can be inserted between runs.

Duration

number

<A>(task) => Task<readonly A[]>

Task.poll to repeatedly run a Task until a predicate is satisfied.

pipe(
  pollSensor,
  Task.repeat({ times: 5, delay: Duration.seconds(1) })
)(); // Task<Reading[]> — 5 readings, one per second

Result: object = TaskResult

all: <T>(tasks, options?) => Result<Aborted | TaskErrOf<T[number]>, { [K in string | number | symbol]: T[K] extends Result<unknown, A> ? A : never }>

Runs multiple fallible tasks in parallel and collects their successful values into a tuple.

  • An optional concurrency option limits the number of tasks executing at any given time; a provided nonpositive or non-integer value clamps to 1, and omitting the option runs all tasks unbounded.
  • Short-circuits on the first Err: cancels all currently active tasks and avoids starting queued tasks.
  • If caller’s signal is already aborted, returns Aborted immediately without starting work.
  • If caller’s signal aborts during execution, cancels active work and settles immediately with Aborted.

T extends readonly Result<unknown, unknown>[]

readonly [T]

number

Result<Aborted | TaskErrOf<T[number]>, { [K in string | number | symbol]: T[K] extends Result<unknown, A> ? A : never }>

  • Task.Result.sequential to run an array of fallible tasks one at a time in order.
  • Task.Result.allSettled to collect all outcomes without short-circuiting on failure.
const result = await Task.Result.all([loadUser, loadPreferences])();
if (Result.is.ok(result)) {
  const [user, preferences] = result.value;
}

allSettled: <T>(tasks) => Task<{ [K in string | number | symbol]: T[K] extends Result<E, A> ? Result<E, A> : never }>

Runs a list of fallible tasks in parallel and collects all outcomes (Ok and Err) without short-circuiting on failure.

T extends readonly Result<any, any>[]

readonly [T]

Task<{ [K in string | number | symbol]: T[K] extends Result<E, A> ? Result<E, A> : never }>

const results = await Task.Result.allSettled([task1, task2, task3])();
// [Ok(val1), Err(err2), Ok(val3)]

apply: <E2, A>(arg) => <B, E1>(task) => Result<E2 | E1, B>

Applies a function wrapped in a Task.Result to a value wrapped in a Task.Result. Both Tasks run in parallel.

E2 = never

A = unknown

Result<E2, A>

<B, E1>(task) => Result<E2 | E1, B>

const add = (a: number) => (b: number) => a + b;
pipe(
  Task.Result.make.ok(add),
  Task.Result.apply(Task.Result.make.ok(5)),
  Task.Result.apply(Task.Result.make.ok(3)),
); // Task.Result resolving to Ok(8)

assign: <K, E2, A, B>(key, transform) => <E1>(task) => Result<E2 | E1, A & { [P in string]: B }>

Evaluates a new Task.Result using the current accumulator and attaches the output to a new key.

Evaluates a new fallible Task using the current accumulator and attaches the output to a new key. The key must not already exist on the accumulator — use map to update an existing field. The accumulator is expected to be a plain object (own enumerable properties).

K extends string

E2

A extends object

B

K extends KeysOfUnion<A> ? never : K

(value) => Result<E2, B>

<E1>(task) => Result<E2 | E1, A & { [P in string]: B }>

pipe(
  Task.Result.make.ok({ a: 1 }),
  Task.Result.assign("b", ({ a }) => Task.Result.make.ok(a + 1))
); // Task.Result({ a: 1, b: 2 })

assignTo: <K>(key) => <E, A>(task) => Result<E, { [P in string]: A }>

Converts a Task.Result value into an object containing a single property. Initiates the pipeline accumulator record.

K extends string

K

<E, A>(task) => Result<E, { [P in string]: A }>

pipe(Task.Result.make.ok(42), Task.Result.assignTo("value")); // Task.Result({ value: 42 })

chain: <E2, A, B>(transform) => <E1>(task) => Result<E2 | E1, B> = chainTaskResult

Chains Task.Result computations. If the first succeeds, passes the value to transform. If the first fails, propagates the error.

E2 = never

A = unknown

B = unknown

(value) => Result<E2, B>

<E1>(task) => Result<E2 | E1, B>

Task.Result.map to transform the inner value without returning a new Task.Result.

const loadUserId: Task.Result<string, number> = Task.Result.make.ok(42);
const loadProfile = (id: number): Task.Result<string, { id: number; name: string }> =>
  Task.Result.make.ok({ id, name: "Alice" });

pipe(
  loadUserId,
  Task.Result.chain(loadProfile),
); // Task.Result resolving to Ok({ id: 42, name: "Alice" })

ensure: {<A, B, E2>(refinement, onFail): <E1>(task) => Result<E2 | E1, B>; <A, E2>(predicate, onFail): <E1>(task) => Result<E2 | E1, A>; }

Narrows the Ok value with a predicate or type guard. If the predicate returns false, converts the value to an Err using onFail.

<A, B, E2>(refinement, onFail): <E1>(task) => Result<E2 | E1, B>

A

B

E2

(value) => value is B

(value) => E2

<E1>(task) => Result<E2 | E1, B>

<A, E2>(predicate, onFail): <E1>(task) => Result<E2 | E1, A>

A

E2

(value) => boolean

(value) => E2

<E1>(task) => Result<E2 | E1, A>

pipe(
  Task.Result.make.ok(42),
  Task.Result.ensure(n => n > 50, () => "too small")
); // Task.Result resolving to Err("too small")

from: object

Maybe: <E>(onNone) => <A>(maybe) => Result<E, A>

Creates a Task.Result from a Maybe. Some becomes Ok, None becomes err from onNone.

E

() => E

<A>(maybe) => Result<E, A>

Task.Result.from.Maybe(() => "empty")(Maybe.make.some(42)); // resolves to Ok(42)
Task.Result.from.Maybe(() => "empty")(Maybe.make.none());   // resolves to Err("empty")

nullable: <E>(onNull) => <A>(value) => Result<E, NonNullable<A>>

Creates a Task.Result from a nullable value. Returns Ok if the value is not null or undefined, err from onNull otherwise.

E

() => E

<A>(value) => Result<E, NonNullable<A>>

Task.Result.from.nullable(() => "missing")(42);   // resolves to Ok(42)
Task.Result.from.nullable(() => "missing")(null); // resolves to Err("missing")

Result: <E, A>(result) => Result<E, A>

Lifts a Result into a Task.Result.

E

A

Result<E, A>

Result<E, A>

Task.Result.from.Result(Result.make.ok(42)); // resolves to Ok(42)

Task: <A>(task) => Result<never, A>

Lifts an infallible Task into a Task.Result with error type never.

A

Task<A>

Result<never, A>

Task.Result.from.Task(Task.make(42)); // resolves to Ok(42)

getOrElse: <B>(fallback) => <A, E>(task) => Task<B | A>

Returns the success value or a default value if the Task.Result is an error. The default can be a different type, widening the result to Task<A | B>.

B

() => B

<A, E>(task) => Task<B | A>

pipe(
  Task.Result.make.err("oops"),
  Task.Result.getOrElse(() => 0),
); // Task resolving to 0

make: object

err: <E>(error) => Result<E, never> = makeErr

Creates a failed Task.Result with the given error.

E

E

Result<E, never>

const task = Task.Result.make.err("failed");
const res = await task(); // Err("failed")

ok: <A>(value) => Result<never, A> = makeOk

Wraps a value in a successful Task.Result.

A

A

Result<never, A>

const task = Task.Result.make.ok(42);
const res = await task(); // Ok(42)

map: <A, B>(transform) => <E>(task) => Result<E, B> = mapTaskResult

Transforms the success value inside a Task.Result.

A

B

(value) => B

<E>(task) => Result<E, B>

  • Task.Result.chain to sequence operations that themselves return a Task.Result.
  • Task.Result.mapError to transform the error value instead of the success value.
pipe(
  Task.Result.make.ok(5),
  Task.Result.map(n => n * 2),
); // Task.Result resolving to Ok(10)

mapBoth: <E1, E2, A, B>(onErr, onOk) => (task) => Result<E2, B>

Maps both the Err and Ok channels simultaneously.

E1

E2

A

B

(error) => E2

(value) => B

(task) => Result<E2, B>

pipe(
  Task.Result.make.ok(42),
  Task.Result.mapBoth(
    (err: string) => new Error(err),
    (n: number) => n * 2
  )
); // Task.Result resolving to Ok(84)

mapError: <E, F>(transform) => <A>(task) => Result<F, A>

Transforms the error value inside a Task.Result.

E

F

(error) => F

<A>(task) => Result<F, A>

Task.Result.map to transform the success value instead of the error value.

pipe(
  Task.Result.make.err("not found"),
  Task.Result.mapError(e => `Error: ${e}`),
); // Task.Result resolving to Err("Error: not found")

match: <E, A, B>(cases) => (task) => Task<B>

Pattern matches on a Task.Result, returning a Task of the result.

E

A

B

(error) => B

(value) => B

(task) => Task<B>

pipe(
  Task.Result.make.ok(42),
  Task.Result.match({
    err: e => `Error: ${e}`,
    ok: v => `Got ${v}`,
  }),
); // Task resolving to "Got 42"

paginate: <E, A, C>(load) => Result<Aborted | E, readonly A[]>

Runs a cursor-driven loader until a page reports no next cursor, collecting every item.

Pages are requested sequentially, because each cursor comes from the page before it. The first Err ends the run and returns that error; pages already collected are not returned. The loader receives the caller’s signal, and the run settles with Aborted when the signal aborts before the next page starts.

E

A

C

(cursor) => Result<E, { items: readonly A[]; next?: C; }>

Result<Aborted | E, readonly A[]>

  • Task.Result.all to run independent tasks instead of a cursor chain.
  • Task.Result.poll to repeat one task until its value is ready.
const loadAllIssues = Task.Result.paginate(fetchIssuePage);

const issues = await loadAllIssues();
if (Result.is.ok(issues)) {
  console.log(issues.value.length);
}

poll: <A>(options) => <E>(task) => Result<E, A>

Repeats a fallible task until its value satisfies until, keeping the value that satisfied it.

until, delay, and attempts behave exactly as they do in Task.poll: attempts is a positive integer, and the last value is returned when it runs out. The difference is the error channel: the first Err ends the loop, because retrying a failed attempt belongs to Task.Result.retry.

A

number

Duration

(value) => boolean

<E>(task) => Result<E, A>

  • Task.poll for the infallible form.
  • Task.Result.retry to repeat a failed attempt rather than a successful one.
const waitForExport = pipe(
  fetchExportJob,
  Task.Result.poll({ until: (job) => job.status !== "pending", delay: Duration.seconds(2) }),
);

recover: <E1, E2, B>(fallback) => <A>(task) => Result<E2, B | A>

Recovers from an error by providing a fallback Task.Result. The fallback can produce a different success type or resolve with a different error type.

E1

E2 = never

B = never

(error) => Result<E2, B>

<A>(task) => Result<E2, B | A>

Task.Result.recoverUnless to conditionally recover based on the error value.

pipe(
  Task.Result.make.err("disk error"),
  Task.Result.recover(() => Task.Result.make.ok("cached value")),
); // Task.Result resolving to Ok("cached value")

recoverUnless: <E1, E2, B>(isBlocked, fallback) => <A>(task) => Result<E1 | E2, B | A>

Recovers from an error unless the predicate isBlocked returns true for that error. The fallback can produce a different success type, widening the result to Task.Result<E1 | E2, A | B>.

E1

E2 = never

B = never

(error) => boolean

(error) => Result<E2, B>

<A>(task) => Result<E1 | E2, B | A>

Task.Result.recover for unconditional error recovery.

pipe(
  fetchTask,
  Task.Result.recoverUnless(
    (e) => e === "fatal",
    () => Task.Result.make.ok("fallback")
  )
);

retry: <E>(policy) => <E1, A>(task) => Result<E1, A>

Retries a fallible Task.Result according to a RetryPolicy. If the task succeeds, returns Ok immediately. If the task fails, retries up to policy.attempts times with delays generated by policy. Any optional when predicate or onRetry callback configured on the RetryPolicy will filter retriable errors and emit telemetry events before each backoff delay.

E = unknown

RetryPolicy<E>

<E1, A>(task) => Result<E1, A>

const policy = RetryPolicy.exponential({
  attempts: 3,
  initial: Duration.milliseconds(100),
  when: (err) => isTransient(err),
  onRetry: ({ attempt, error, delay }) => console.warn(`Retry ${attempt}: ${error}`),
});
const retryableFetch = pipe(
  fetchData,
  Task.Result.retry(policy),
);

run: (signal?) => <E, A>(task) => Deferred<Result<E, A>>

Executes a Task.Result with an optional signal, returning Deferred<Result<E, A>>. Use as a terminal step in a pipe chain.

AbortSignal

<E, A>(task) => Deferred<Result<E, A>>

const controller = new AbortController();
const result = await pipe(
    fetchUser("42"),
    Task.Result.chain(user => fetchPosts(user.id)),
    Task.Result.run(controller.signal),
);
if (Result.is.ok(result)) render(result.value);

struct: <T>(fields) => Result<TaskErrOf<T[keyof T]>, { [K in string | number | symbol]: T[K] extends Result<unknown, A> ? A : never }>

Combines a record of Task.Results into a single Task.Result of a record. Own string and symbol keys are processed in key order; inherited fields are not supported. Evaluates all tasks in parallel, forwarding the AbortSignal down to each sub-task. Returns the first Err encountered in key order.

T extends Record<string, Result<unknown, unknown>>

T

Result<TaskErrOf<T[keyof T]>, { [K in string | number | symbol]: T[K] extends Result<unknown, A> ? A : never }>

Task.Result.struct({
  name: Task.Result.make.ok("Alice"),
  age: Task.Result.make.ok(30)
}); // Task.Result({ name: "Alice", age: 30 })

tap: <A>(sideEffect) => <E>(task) => Result<E, A>

Executes a side effect on the success value without changing the Task.Result. Useful for logging or debugging.

A

(value) => void

<E>(task) => Result<E, A>

Task.Result.tapError to perform a side effect on the error value.

pipe(
  Task.Result.make.ok({ status: "done" }),
  Task.Result.tap(v => console.log("Success:", v)),
);

tapError: <E>(sideEffect) => <A>(task) => Result<E, A>

Executes a side effect on the error value without changing the Task.Result. Useful for logging or reporting async errors.

E

(error) => void

<A>(task) => Result<E, A>

Task.Result.tap to perform a side effect on the success value.

pipe(
  fetchUser(id),
  Task.Result.tapError(e => console.error("fetch failed:", e)),
  Task.Result.chain(saveToCache),
)

timeout: <E2>(options) => <E1, A>(task) => Result<E2 | E1, A>

Times out a fallible task, resolving to Err(onTimeout()) if the duration elapses before the task completes.

E2

Duration

() => E2

<E1, A>(task) => Result<E2 | E1, A>

const fetchWithTimeout = pipe(
  fetchTask,
  Task.Result.timeout({ duration: Duration.seconds(5), onTimeout: () => "Request timed out" })
);

to: object

Maybe: <E, A>(task) => Maybe<A>

Converts a Task.Result to a Task.Maybe, dropping the error value on Err.

E

A

Result<E, A>

Maybe<A>

const taskResult = Task.Result.make.ok(42);
const taskMaybe = pipe(taskResult, Task.Result.to.Maybe);

tryCatch: <E, A>(fn, options) => Result<E, A>

Creates a Task.Result from a Promise-returning thunk that may throw or reject. Catches any errors and transforms them using the onError function into an Err. The thunk optionally receives an AbortSignal forwarded from the call site.

E

A

(signal?) => Thenable<A>

(error) => E

Result<E, A>

const loadUser = Task.Result.tryCatch(
  (signal) => userStore.get("u_123", { signal }),
  { onError: (e) => new DbError(e) }
);

run: (signal?) => <A>(task) => Deferred<A>

Executes a task with an optional signal. Use as a terminal step in a pipe chain.

AbortSignal

<A>(task) => Deferred<A>

const name = await pipe(
    loadConfig,
    Task.map(config => config.name),
    Task.run(),
);

tap: <A>(sideEffect) => (task) => Task<A>

Executes a side effect on the value without changing the Task. Useful for logging or debugging.

A

(value) => void

(task) => Task<A>

pipe(
  loadConfig,
  Task.tap(cfg => console.log("Config:", cfg)),
  Task.map(buildReport)
);

timeout: <E>(options) => <A>(task) => Task<Result<E, A>>

Converts a Task<A> into a Task<Result<E, A>>, resolving to Err if the Task does not complete within the given duration. The inner Task receives an AbortSignal that fires when the deadline passes, so asynchronous operations that accept a signal are cancelled rather than left dangling.

E

Duration

() => E

<A>(task) => Task<Result<E, A>>

pipe(
  heavyComputation,
  Task.timeout({ duration: Duration.seconds(5), onTimeout: () => "timed out" }),
  Task.Result.chain(processResult)
);

tryCatch: <A, B>(fn, options) => Task<A | B>

Wraps a Promise-returning thunk that may throw or reject, trapping errors with a fallback function and returning a guaranteed Task<A>.

A

B = A

(signal?) => Thenable<A>

(error) => B

Task<A | B>

const loadConfig = Task.tryCatch(
  () => configStore.get("default"),
  { onError: () => DEFAULT_CONFIG }
);

Validation: object = TaskValidation

apply: <E2, A>(arg) => <B, E1>(task) => Validation<E2 | E1, B>

Applies a function wrapped in a Task.Validation to a value wrapped in a Task.Validation. Both Tasks run in parallel and errors from both sides are accumulated.

E2 = never

A = unknown

Validation<E2, A>

<B, E1>(task) => Validation<E2 | E1, B>

Task.Validation.struct to combine multiple validations into a record.

pipe(
  Task.Validation.make.passed((name: string) => (age: number) => ({ name, age })),
  Task.Validation.apply(validateName(name)),
  Task.Validation.apply(validateAge(age))
)();

from: object

Maybe: <E>(onNone) => <A>(maybe) => Validation<E, A>

Creates a Task.Validation from a Maybe. Some becomes Passed, None becomes Failed with the error from onNone.

E

() => E

<A>(maybe) => Validation<E, A>

Task.Validation.from.Maybe(() => "empty")(Maybe.make.some(42)); // resolves to Passed(42)
Task.Validation.from.Maybe(() => "empty")(Maybe.make.none());   // resolves to Failed(["empty"])

nullable: <E>(onNull) => <A>(value) => Validation<E, NonNullable<A>>

Creates a Task.Validation from a nullable value. If the value is null or undefined, returns Failed with the error from onNull. Otherwise, returns Passed.

E

() => E

<A>(value) => Validation<E, NonNullable<A>>

Task.Validation.from.nullable(() => "missing")(42);   // resolves to Passed(42)
Task.Validation.from.nullable(() => "missing")(null); // resolves to Failed(["missing"])

Result: <E, A>(result) => Validation<E, A>

Creates a Task.Validation from a Result. Ok becomes Passed, Err(e) becomes Failed([e]).

E

A

Result<E, A>

Validation<E, A>

Task.Validation.from.Result(Result.make.ok(42));     // resolves to Passed(42)
Task.Validation.from.Result(Result.make.err("bad")); // resolves to Failed(["bad"])

Validation: <E, A>(validation) => Validation<E, A>

Lifts a Validation into a Task.Validation.

E

A

Validation<E, A>

Validation<E, A>

Task.Validation.from.Validation(Validation.make.passed(42));

getOrElse: <B>(fallback) => <A, E>(task) => Task<B | A>

Returns the success value or a default value if the Task.Validation is failed. The default can be a different type, widening the result to Task<A | B>.

B

() => B

<A, E>(task) => Task<B | A>

pipe(
  Task.Validation.make.failed("invalid"),
  Task.Validation.getOrElse(() => 0),
); // Task resolving to 0

make: object

failed: <E>(error) => Validation<E, never> = makeFailed

Creates a failed Task.Validation with a single error.

E

E

Validation<E, never>

const task = Task.Validation.make.failed("invalid");
const res = await task(); // Failed(["invalid"])

failedAll: <E>(errors) => Validation<E, never> = makeFailedAll

Creates a failed Task.Validation from multiple errors.

E

NonEmptyArr<E>

Validation<E, never>

const task = Task.Validation.make.failedAll(["err1", "err2"]);
const res = await task(); // Failed(["err1", "err2"])

passed: <A>(value) => Validation<never, A> = makePassed

Wraps a value in a passed Task.Validation.

A

A

Validation<never, A>

const task = Task.Validation.make.passed(42);
const res = await task(); // Passed(42)

map: <A, B>(transform) => <E>(task) => Validation<E, B>

Transforms the success value inside a Task.Validation.

A

B

(value) => B

<E>(task) => Validation<E, B>

  • Task.Validation.mapError to transform accumulated errors.
  • Task.Validation.apply to combine multiple validations in parallel.
pipe(
  Task.Validation.make.passed(5),
  Task.Validation.map(n => n * 2),
); // Task.Validation resolving to Passed(10)

mapError: <E, F>(transform) => <A>(task) => Validation<F, A>

Transforms all accumulated errors inside a Task.Validation.

E

F

(error) => F

<A>(task) => Validation<F, A>

Task.Validation.map to transform the success value.

pipe(
  Task.Validation.make.failed("oops"),
  Task.Validation.mapError(e => e.toUpperCase())
); // Task.Validation(Failed(["OOPS"]))

match: <E, A, B>(cases) => (task) => Task<B>

Pattern matches on a Task.Validation, returning a Task of the result.

E

A

B

(errors) => B

(value) => B

(task) => Task<B>

pipe(
  validateForm(input),
  Task.Validation.match({
    passed: data => save(data),
    failed: errors => showErrors(errors)
  })
)();

recover: <E1, B, E2>(fallback) => <A>(task) => Validation<E2, B | A>

Recovers from a Failed state by providing a fallback Task.Validation. The fallback receives the accumulated error list so callers can inspect which errors occurred. The fallback can produce a different success type or resolve with a different error type.

E1

B

E2 = never

(errors) => Validation<E2, B>

<A>(task) => Validation<E2, B | A>

Task.Validation.recoverUnless to conditionally recover based on accumulated errors.

pipe(
  Task.Validation.make.failed("missing"),
  Task.Validation.recover(() => Task.Validation.make.passed("default")),
); // Task.Validation resolving to Passed("default")

recoverUnless: <E1, B, E2>(isBlocked, fallback) => <A>(task) => Validation<E1 | E2, B | A>

Recovers from a Failed state unless the predicate isBlocked returns true for the accumulated errors. The fallback receives the accumulated errors and can produce a different success type, widening the result to Task.Validation<E1 | E2, A | B>.

E1

B

E2 = never

(errors) => boolean

(errors) => Validation<E2, B>

<A>(task) => Validation<E1 | E2, B | A>

Task.Validation.recover for unconditional error recovery.

pipe(
  validationTask,
  Task.Validation.recoverUnless(
    (errors) => errors.includes("fatal"),
    (errors) => Task.Validation.make.passed("fallback")
  )
);

run: (signal?) => <E, A>(task) => Deferred<Validation<E, A>>

Executes a Task.Validation with an optional signal, returning Deferred<Validation<E, A>>. Use as a terminal step in a pipe chain.

AbortSignal

<E, A>(task) => Deferred<Validation<E, A>>

const result = await pipe(
  Task.Validation.make.passed(42),
  Task.Validation.run(),
);

struct: <T>(fields) => Validation<TaskValidationErrOf<T[keyof T]>, { [K in string | number | symbol]: T[K] extends Validation<unknown, A> ? A : never }>

Combines a record of Task.Validations into a single Task.Validation of a record. Own string and symbol keys are processed in key order; inherited fields are not supported. Evaluates fields in parallel and accumulates all validation errors.

T extends Record<string, Validation<unknown, unknown>>

T

Validation<TaskValidationErrOf<T[keyof T]>, { [K in string | number | symbol]: T[K] extends Validation<unknown, A> ? A : never }>

Task.Validation.struct({
  name: Task.Validation.make.passed("Alice"),
  age: Task.Validation.make.passed(30)
}); // Task.Validation({ name: "Alice", age: 30 })

tap: <A>(sideEffect) => <E>(task) => Validation<E, A>

Executes a side effect on the success value without changing the Task.Validation. Useful for logging or debugging.

A

(value) => void

<E>(task) => Validation<E, A>

Task.Validation.tapError to perform a side effect on accumulated errors.

pipe(
  Task.Validation.make.passed(42),
  Task.Validation.tap(n => console.log("Validated:", n)),
);

tapError: <E>(sideEffect) => <A>(task) => Validation<E, A>

Executes a side effect on the accumulated errors without changing the Task.Validation.

E

(errors) => void

<A>(task) => Validation<E, A>

Task.Validation.tap to perform a side effect on the success value.

pipe(
  Task.Validation.make.failed("invalid name"),
  Task.Validation.tapError(errs => logger.error(errs))
);

to: object

Maybe: <E, A>(task) => Maybe<A>

Converts a Task.Validation to a Task.Maybe. Passed(a) becomes Some(a); Failed(errors) becomes None (errors are discarded).

E

A

Validation<E, A>

Maybe<A>

Task.Validation.to.Maybe(validationTask);

Result: <E1, E2>(combineErrors) => <A>(task) => Result<E2, A>

Converts a Task.Validation to a Task.Result, combining accumulated errors using combineErrors. Passed(a) becomes Ok(a); Failed(errors) becomes Err(combineErrors(errors)).

E1

E2

(errors) => E2

<A>(task) => Result<E2, A>

Task.Validation.to.Result((errors) => errors.join(", "))(validationTask);

tryCatch: <E, A>(fn, options) => Validation<E, A>

Creates a Task.Validation from a Promise-returning thunk that may throw or reject. Catches any errors and transforms them using the onError function into a Failed validation. The thunk optionally receives an AbortSignal forwarded from the call site.

E

A

(signal?) => Thenable<A>

(error) => E

Validation<E, A>

const loadConfig = Task.Validation.tryCatch(
  (signal) => configStore.get("default", { signal }),
  { onError: (e) => `Failed to load config: ${e}` }
);

withLabel: <L>(label) => <A>(task) => LabeledTask<L, A>

Attaches a read-only .label property to a Task, preserving the literal string generic type for IDE tooltips.

L extends string

L

<A>(task) => LabeledTask<L, A>

const labeledTask = pipe(readTask, Task.withLabel("readUser"));
console.log(labeledTask.label); // "readUser"

withProgress: (onProgress) => <A>(task) => Task<A>

Monitors progress of a Task by calling onProgress(0) before execution and onProgress(1) upon completion.

(ratio) => void

<A>(task) => Task<A>

const taskWithProgress = pipe(
  readTask,
  Task.withProgress((ratio) => console.log(`Progress: ${ratio * 100}%`))
);