Skip to content

Task

Task: object

Defined in: Core/Task.ts:59

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.

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.

K extends string

A

B

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

B

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<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<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"
  })
)();

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

Creates a memoized version of a Task.Maybe. The task is executed at most once on first call, and its resolved Maybe is cached for all subsequent calls.

A

Maybe<A>

Maybe<A>

const loadUser = Task.Maybe.memoize(fetchUserMaybeTask);

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

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

B

() => 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. 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 it 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 is executed at most once on first call, and its resolved value is cached for all subsequent calls.

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 stops the loop after N calls — the last value is returned regardless of whether the predicate was satisfied.

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<T[number] extends Task<A> ? A : never>) & (<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

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.

K extends string

E2

A

B

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 })

bimap: <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.bimap(
    (err: string) => new Error(err),
    (n: number) => n * 2
  )
); // Task.Result resolving to Ok(84)

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

A

B

(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, 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, 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)

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)

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"

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

Creates a memoized version of a Task.Result. The task is executed at most once on first call, and its resolved Result is cached for all subsequent calls.

E

A

Result<E, A>

Result<E, A>

const loadConfig = Task.Result.memoize(fetchConfigTask);

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

B

(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

B

(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<T[keyof T] extends Result<E, unknown> ? E : never, { [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. 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<T[keyof T] extends Result<E, unknown> ? E : never, { [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(),
);

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

Runs an array or tuple of Tasks one at a time in order, collecting all results. Each Task starts only after the previous one resolves.

T extends readonly Task<unknown>[]

readonly [T]

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

Task.all to run an array or tuple of tasks in parallel.

let log: number[] = [];
const makeTask = (n: number) => Task.make(n);

await Task.sequential([makeTask(1), makeTask(2), makeTask(3)])();
// log = [1, 2, 3] — tasks ran in order

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)
  })
)();

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

Creates a memoized version of a Task.Validation. The task is executed at most once on first call, and its resolved Validation is cached for all subsequent calls.

E

A

Validation<E, A>

Validation<E, A>

const validate = Task.Validation.memoize(validateFormTask);

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<T[keyof T] extends Validation<E, unknown> ? E : never, { [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. Evaluates fields in parallel and accumulates all validation errors.

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

T

Validation<T[keyof T] extends Validation<E, unknown> ? E : never, { [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}%`))
);