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) => Task<{ [K in string | number | symbol]: T[K] extends Task<A> ? A : never }>

Runs multiple Tasks in parallel and collects their results.

T extends readonly Task<unknown>[]

T

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

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

ap: <A>(arg) => <B>(data) => 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>(data) => Task<B>

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

bind: <K, A, B>(key, f) => (data) => 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

(a) => Task<B>

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

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

bindTo: <K>(key) => <A>(data) => 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>(data) => Task<{ [P in string]: A }>

pipe(Task.resolve(42), Task.bindTo("value")); // Task({ value: 42 })

chain: <A, B>(f) => (data) => Task<B>

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

A

B

(a) => Task<B>

(data) => Task<B>

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

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

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

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

Duration

<A>(data) => Task<A>

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

from: object

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

Creates a Task from a lazy synchronous thunk. Unlike Task.resolve(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

map: <A, B>(f) => (data) => Task<B>

Transforms the value inside a Task.

A

B

(a) => B

(data) => Task<B>

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

Maybe: object = TaskMaybe

ap: <A>(arg) => <B>(data) => 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>(data) => Maybe<B>

bind: <K, A, B>(key, f) => (data) => 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

(a) => Maybe<B>

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

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

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

Lifts a Task.Maybe value into an accumulator object.

K extends string

K

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

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

chain: <A, B>(f) => (data) => Maybe<B> = chainTaskMaybe

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

A

B

(a) => Maybe<B>

(data) => Maybe<B>

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

filter: <A>(predicate) => (data) => Maybe<A>

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

A

(a) => boolean

(data) => Maybe<A>

fold: <A, B>(onNone, onSome) => (data) => Task<B>

Extracts a value from a Task.Maybe by providing handlers for both cases.

A

B

() => B

(a) => B

(data) => Task<B>

from: object

Maybe: <A>(option) => 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.resolve(42)); // resolves to Some(42)

getOrElse: <B>(defaultValue) => <A>(data) => 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>(data) => Task<B | A>

make: object

Wraps a value in a Some inside a Task.

const task = Task.Maybe.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>(f) => (data) => Maybe<B> = mapTaskMaybe

Transforms the value inside a Task.Maybe.

A

B

(a) => B

(data) => Maybe<B>

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

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

A

B

() => B

(a) => B

(data) => 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>(data) => Maybe<B | A>

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

B

() => Maybe<B>

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

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

struct: <R>(fields) => Maybe<R>

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.

R extends Record<string, any>

{ [K in string | number | symbol]: Maybe<R[K]> }

Maybe<R>

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

tap: <A>(f) => (data) => Maybe<A>

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

A

(a) => void

(data) => Maybe<A>

to: object

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

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

E

() => E

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

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

tryCatch: <A>(f) => 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

race: <A>(tasks) => Task<A>

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

A

readonly Task<A>[]

Task<A>

const fast = Task.resolve("fast");
const slow = Task.delay(Duration.milliseconds(200))(Task.resolve("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[]>

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

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

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

A

Duration

number

(a) => boolean

(task) => Task<A>

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

resolve: <A>(value) => Task<A> = resolveTask

Creates a Task that immediately resolves to the given value.

A

A

Task<A>

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

Result: object = TaskResult

allSettled: <E, A>(tasks) => Task<readonly Result<E, A>[]>

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

E

A

readonly Result<E, A>[]

Task<readonly Result<E, A>[]>

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

ap: <E, A>(arg) => <B>(data) => Result<E, B>

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

E

A

Result<E, A>

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

bind: <K, E, A, B>(key, f) => (data) => Result<E, 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

E

A

B

K

(a) => Result<E, B>

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

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

bindTo: <K>(key) => <E, A>(data) => 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>(data) => Result<E, { [P in string]: A }>

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

chain: <E2, A, B>(f) => <E1>(data) => Result<E2 | E1, B> = chainTaskResult

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

E2

A

B

(a) => Result<E2, B>

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

fold: <E, A, B>(onErr, onOk) => (data) => Task<B>

Extracts the value from a Task.Result by providing handlers for both cases.

E

A

B

(e) => B

(a) => B

(data) => Task<B>

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>(defaultValue) => <E, A>(data) => 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

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

make: object

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

Creates a failed Task.Result with the given error.

E

A = never

E

Result<E, A>

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

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

Wraps a value in a successful Task.Result.

E = never

A = unknown

A

Result<E, A>

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

map: <E, A, B>(f) => (data) => Result<E, B> = mapTaskResult

Transforms the success value inside a Task.Result.

E

A

B

(a) => B

(data) => Result<E, B>

mapError: <E, F, A>(f) => (data) => Result<F, A>

Transforms the error value inside a Task.Result.

E

F

A

(e) => F

(data) => Result<F, A>

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

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

E

A

B

(e) => B

(a) => B

(data) => Task<B>

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: <E, B>(fallback) => <A>(data) => Result<E, B | A>

Recovers from an error by providing a fallback Task.Result. The fallback can produce a different success type, widening the result to Task.Result<E, A | B>.

E

B

(e) => Result<E, B>

<A>(data) => Result<E, B | A>

recoverUnless: <E, B>(isBlocked, fallback) => <A>(data) => Result<E, 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<E, A | B>.

E

B

(e) => boolean

(e) => Result<E, B>

<A>(data) => Result<E, B | A>

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

retry: (policy) => <E, A>(task) => Result<E, 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.

RetryPolicy

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

const policy = RetryPolicy.exponential({ attempts: 3, initial: Duration.milliseconds(100) });
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 Promise<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: <E, R>(fields) => Result<E, R>

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.

E

R extends Record<string, any>

{ [K in string | number | symbol]: Result<E, R[K]> }

Result<E, R>

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

tap: <E, A>(f) => (data) => Result<E, A>

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

E

A

(a) => void

(data) => Result<E, A>

tapError: <E, A>(f) => (data) => Result<E, A>

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

E

A

(e) => void

(data) => Result<E, A>

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>(data) => 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>(f, 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(),
);

sequence: <A>(tasks) => Task<readonly A[]>

Runs an array of Tasks concurrently and collects their results in an array. Forward-propagates the call site’s AbortSignal to all subtasks concurrently.

A

readonly Task<A>[]

Task<readonly A[]>

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

sequential: <A>(tasks) => Task<readonly A[]>

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

A

readonly Task<A>[]

Task<readonly A[]>

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

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

tap: <A>(f) => (data) => Task<A>

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

A

(a) => void

(data) => 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>(f, options) => Task<A>

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

A

(signal?) => Promise<A>

(error) => A

Task<A>

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

Validation: object = TaskValidation

ap: <E, A>(arg) => <B>(data) => Validation<E, 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.

E

A

Validation<E, A>

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

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

fold: <E, A, B>(onFailed, onPassed) => (data) => Task<B>

Extracts a value from a Task.Validation by providing handlers for both cases.

E

A

B

(errors) => B

(a) => B

(data) => Task<B>

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, 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, 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>(defaultValue) => <E, A>(data) => 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

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

make: object

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

Creates a failed Task.Validation with a single error.

E

A = never

E

Validation<E, A>

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

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

Creates a failed Task.Validation from multiple errors.

E

A = never

NonEmptyArr<E>

Validation<E, A>

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

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

Wraps a value in a passed Task.Validation.

E = never

A = unknown

A

Validation<E, A>

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

map: <E, A, B>(f) => (data) => Validation<E, B>

Transforms the success value inside a Task.Validation.

E

A

B

(a) => B

(data) => Validation<E, B>

mapError: <E, F, A>(f) => (data) => Validation<F, A>

Transforms all accumulated errors inside a Task.Validation.

E

F

A

(e) => F

(data) => Validation<F, A>

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

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

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

E

A

B

(errors) => B

(a) => B

(data) => 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);

product: <E, A, B>(first, second) => Validation<E, readonly [A, B]>

Runs two Task.Validations concurrently and combines their results into a tuple. If both are Passed, returns Passed with both values. If either fails, accumulates errors from both sides.

E

A

B

Validation<E, A>

Validation<E, B>

Validation<E, readonly [A, B]>

await Task.Validation.product(
  validateName(form.name),
  validateAge(form.age),
)(); // Passed(["Alice", 30]) or Failed([...errors])

productAll: <E, A>(data) => Validation<E, readonly A[]>

Runs all Task.Validations concurrently and collects results. If all are Passed, returns Passed with all values as an array. If any fail, returns Failed with all accumulated errors.

E

A

NonEmptyArr<Validation<E, A>>

Validation<E, readonly A[]>

await Task.Validation.productAll([
  validateName(form.name),
  validateEmail(form.email),
  validateAge(form.age),
])(); // Passed([name, email, age]) or Failed([...all errors])

recover: <E, B>(fallback) => <A>(data) => Validation<E, 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, widening the result to Task.Validation<E, A | B>.

E

B

(errors) => Validation<E, B>

<A>(data) => Validation<E, B | A>

recoverUnless: <E, B>(isBlocked, fallback) => <A>(data) => Validation<E, 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<E, A | B>.

E

B

(errors) => boolean

(errors) => Validation<E, B>

<A>(data) => Validation<E, B | A>

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

struct: <E, R>(fields) => Validation<E, R>

Combines a record of Task.Validations into a single Task.Validation of a record. Evaluates fields in parallel and accumulates all validation errors.

E

R extends Record<string, any>

{ [K in string | number | symbol]: Validation<E, R[K]> }

Validation<E, R>

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

tap: <E, A>(f) => (data) => Validation<E, A>

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

E

A

(a) => void

(data) => Validation<E, A>

tapError: <E, A>(f) => (data) => Validation<E, A>

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

E

A

(errors) => void

(data) => Validation<E, A>

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

to: object

Maybe: <E, A>(data) => 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, A>(combineErrors) => (data) => 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

A

(errors) => E2

(data) => Result<E2, A>

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

tryCatch: <E, A>(f, 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: <A>(onProgress) => (task) => Task<A>

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

A

(ratio) => void

(task) => Task<A>

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