IO constructor - creates an IO from a function. Automatically detects if the function returns a Promise and handles it appropriately.
Alias for bracket with a more descriptive name.
Runs all IOs and collects the results in input order. Stops at the first failure.
By default they run one after another. Pass { concurrency: n } to run up to n at once, or
"unbounded" to start them all together. With more than one in flight, the first failure is the
result at once and the others are interrupted: they start no new step, and an IO.async thunk
sees its signal abort. A promise that ignores its signal keeps running, but the result doesn't wait.
Returns the first effect to succeed, or fails if all fail.
Creates an IO from an async thunk. The Promise is not created until the IO is run.
Inside an interruptOn region (or runCancellable, or runExit({ signal })), the thunk
receives the region's abort signal; pass it to fetch and the request stops when the run is
cancelled. Outside a region it receives undefined. A function whose first parameter is
something else ((page = 1) => load(page)) would receive the signal in that slot, so wrap
it: IO.async(() => load()).
Creates an IO from an async function that returns { data, error }. Handles both:
This is the most ergonomic way to wrap Supabase and similar API calls.
// Supabase query in one line:
const getUser = (id: string): IO<never, Error, Option<User>> =>
IO.asyncResult(
() => supabase.from('users').select('*').eq('id', id).single(),
toError
)
// With custom field names:
const result = IO.asyncResult(
() => customApi.fetch(),
toError,
{ dataKey: 'result', errorKey: 'err' }
)
// With cancellation:
const controller = new AbortController()
const getUser = IO.asyncResult(
(signal) => supabase.from('users').abortSignal(signal).select('*').single(),
toError,
{ signal: controller.signal }
)
controller.abort() // Cancels the request
Ensures a resource is properly released after use. The release function always runs, even if use fails.
Like bracket, but the release callback receives the Exit of the use-step.
Use this when cleanup needs to branch on whether use succeeded or failed —
e.g., emit a different audit event on Success vs Failure.
The release effect always runs (whether use succeeded, failed, or was
interrupted). If acquire itself fails, release is not called.
Creates an IO that dies with an unrecoverable defect.
Creates an IO that fails with the given error.
Runs IOs in sequence, returning the first success or last failure.
Executes an effect for each element in the array, collecting results in input order.
Runs them one after another by default. { concurrency } works as it does for IO.all.
Creates an IO from an Either.
Converts a nullable value to an IO.
Creates an IO from an Option.
Creates an IO from an Option with custom error.
Lifts an async function that returns Either<E, A> into IO<never, E, A>, keeping E typed.
The promise is not created until the IO is run.
A Left becomes a failure in the E channel. A rejected promise is a defect by default (the function
is not expected to throw); pass onReject to map rejections into the error channel instead.
Creates an IO from a result object with data/error pattern. If error is present (truthy), fails with the error. Otherwise succeeds with Option-wrapped data (None if data is null/undefined).
This handles the common { data, error } response pattern used by
Supabase, many REST APIs, and similar libraries.
const response = { data: user, error: null }
const io = IO.fromResult(response) // IO<never, null, Option<User>> -> Some(user)
const emptyResponse = { data: null, error: null }
const emptyIo = IO.fromResult(emptyResponse) // IO<never, null, Option<User>> -> None
const errorResponse = { data: null, error: new Error("Not found") }
const failedIo = IO.fromResult(errorResponse) // IO<never, Error, Option<null>> -> fails
Creates an IO from a Try.
Creates an IO from a generator function. This enables do-notation style programming.
Creates an IO that is immediately interrupted.
Effectful stateful loop — the value-driven dual of the retry family.
Threads state S through an effectful step until done(state) holds, or
the max bound is reached. Semantics:
done(seed) is evaluated before the first step — an already-satisfied
seed returns immediately without executing the effect.E failure short-circuits the loop and propagates.max defaults to 10_000 and caps total step invocations to keep every
loop bounded by construction. Callers running larger iterations must set
max explicitly.flatMap trampoline, not the JS
call stack.Lifts a Promise-returning function into an IO-returning function.
Lifts a synchronous function into an IO-returning function.
Creates an IO that never completes.
Races multiple effects, returning the first to complete. Note: Other effects are NOT cancelled (JS limitation).
Creates an IO that requires a service identified by the tag. The service must be provided before the effect can be run.
Accesses a service and applies a function to it.
Accesses a service and applies an effectful function to it.
Creates an IO that sleeps for the specified duration.
Creates an IO that succeeds with the given value.
Creates an IO from a synchronous thunk. The function is not executed until the IO is run.
Creates a timeout effect that fails with TimeoutError.
Creates an IO from an async thunk with typed error handling. Catches any thrown errors and maps them using the provided function. Supports cancellation via AbortSignal.
This is a simpler alternative to tryPromise that takes a direct
error mapper function instead of an options object.
const io = IO.tryAsync(
() => fetch('/api/users').then(r => r.json()),
(e) => new ApiError(e)
)
// With cancellation:
const controller = new AbortController()
const io = IO.tryAsync(
(signal) => fetch('/api/users', { signal }).then(r => r.json()),
(e) => new ApiError(e),
controller.signal
)
controller.abort() // Cancels the request
Creates an IO from a function that might throw.
Creates an IO from a Promise with error handling.
try receives the region's abort signal, as IO.async's thunk does.
Accesses multiple services and applies a function to them. Provides a convenient way to work with multiple dependencies.
Starts a Do-builder context for binding values. This enables do-notation style programming without generators.
Creates a unit IO.
// Basic usage
const program = IO.sync(() => 42)
.map(x => x * 2)
.flatMap(x => IO.succeed(x + 1))
const result = await program.run() // 85
// Error handling
const safe = IO.tryPromise({
try: () => fetch('/api/data'),
catch: (e) => new NetworkError(e)
})
.map(res => res.json())
.recover({ fallback: 'default' })
// Composition
const composed = IO.all([
IO.succeed(1),
IO.succeed(2),
IO.succeed(3)
]) // IO<never, never, [1, 2, 3]>
IO effect type for lazy, composable effects with typed errors.