Skip to content
Effect Days 2026 Get your ticket

RequestResolver

Resolves data requests made with Effect.request.

A Request describes what a fiber needs, while a RequestResolver describes how to collect request entries, group them into batches, run backend work, and complete each waiting entry. This module includes constructors for common resolver shapes and tools for controlling batching, grouping, delays, tracing, caching, racing, hooks around resolver execution, and persistence.

21 exports Added in v2.0.0 Source

Caching

asCache

Added in v4.0.0 Source

Wraps a request resolver in a cache, allowing it to cache results up to a specified capacity and optional time-to-live.

When to use

Use to turn a request resolver into a first-class Cache when callers need cache lookup, refresh, invalidation, or inspection around request results.

Details

The request value is the cache key. Cache misses run the resolver via Effect.request, timeToLive receives the request Exit and the request, and requireServicesAt controls whether services are required at lookup time or construction time.

Gotchas

Cache hits depend on the request value's equality semantics.

See

  • withCache for keeping caching behind a resolver used with Effect.request
  • persisted for storing persistable request results outside process memory
  • Cache.Cache for operations available on the returned cache

Signature

declare const asCache: {
<A extends Any, ServiceMode extends "lookup" | "construction" = never>(options: {
readonly capacity: number;
readonly requireServicesAt?: ServiceMode;
readonly timeToLive?: (exit: Request.Result<A>, request: A) => Duration.Input;
}): (self: RequestResolver<A>) => Effect<Cache<A, Success<A>, Error<A>, "construction" extends ServiceMode ? never : Services<A>>, never, "construction" extends ServiceMode ? Services<A> : never>;
<A extends Any, ServiceMode extends "lookup" | "construction" = never>(self: RequestResolver<A>, options: {
readonly capacity: number;
readonly requireServicesAt?: ServiceMode;
readonly timeToLive?: (exit: Request.Result<A>, request: A) => Duration.Input;
}): Effect<Cache<A, Success<A>, Error<A>, "construction" extends ServiceMode ? never : Services<A>>, never, "construction" extends ServiceMode ? Services<A> : never>;
}

persisted

Added in v4.0.0 Source

Wraps a request resolver with persistent storage for persistable requests.

When to use

Use to keep a RequestResolver interface while reusing completed Persistable request results through a Persistence store.

Details

Cached results are loaded from the configured persistence store before running the underlying resolver. Missing entries are resolved normally and written back to the store. Entries marked stale by staleWhileRevalidate receive the stored result and are also resolved again so the refreshed result can be written back to the store. Creating the persisted resolver requires Persistence.Persistence and Scope.

See

  • withCache for in-memory resolver caching that does not require persistable request values or a persistence store
  • asCache for exposing resolver results through a Cache instead of returning another resolver

Signature

declare const persisted: {
<A extends Request<any, SchemaError | PersistenceError, any> & Any>(options: {
readonly staleWhileRevalidate?: (exit: Request.Result<A>, request: A) => boolean;
readonly storeId: string;
readonly timeToLive?: (exit: Request.Result<A>, request: A) => Duration.Input;
}): (self: RequestResolver<A>) => Effect<RequestResolver<A>, never, Scope | Persistence>;
<A extends Request<any, SchemaError | PersistenceError, any> & Any>(self: RequestResolver<A>, options: {
readonly staleWhileRevalidate?: (exit: Request.Result<A>, request: A) => boolean;
readonly storeId: string;
readonly timeToLive?: (exit: Request.Result<A>, request: A) => Duration.Input;
}): Effect<RequestResolver<A>, never, Scope | Persistence>;
}

withCache

Added in v4.0.0 Source

Adds a bounded in-memory cache to a request resolver.

When to use

Use to reuse completed results for repeated equal request values while still passing a RequestResolver to Effect.request.

Details

Running the returned effect creates the cache and returns a wrapped resolver. The cache stores completed success or failure results by request equality up to capacity. The strategy option controls eviction order and defaults to "lru"; "fifo" keeps insertion order.

Gotchas

Entries do not expire by time, and completed failures are cached the same as successes. Request equality controls cache hits.

See

  • asCache for exposing the resolver as a Cache with time-to-live and service lookup controls
  • persisted for backing persistable requests with the configured persistence store

Signature

declare const withCache: {
<A extends Any>(options: {
readonly capacity: number;
readonly strategy?: "lru" | "fifo";
}): (self: RequestResolver<A>) => Effect<RequestResolver<A>>;
<A extends Any>(self: RequestResolver<A>, options: {
readonly capacity: number;
readonly strategy?: "lru" | "fifo";
}): Effect<RequestResolver<A>>;
}

Combinators

around

Added in v2.0.0 Source

Wraps request resolver execution between before and after effects.

Signature

declare const around: {
<A extends Any, A2, X>(before: (entries: [Entry<NoInfer<A>>, ...Array<Entry<NoInfer<A>>>]) => Effect<A2, Error<A>>, after: (entries: [Entry<NoInfer<A>>, ...Array<Entry<NoInfer<A>>>], a: A2) => Effect<X, Error<A>>): (self: RequestResolver<A>) => RequestResolver<A>;
<A extends Any, A2, X>(self: RequestResolver<A>, before: (entries: [Entry<NoInfer<A>>, ...Array<Entry<NoInfer<A>>>]) => Effect<A2, Error<A>>, after: (entries: [Entry<NoInfer<A>>, ...Array<Entry<NoInfer<A>>>], a: A2) => Effect<X, Error<A>>): RequestResolver<A>;
}

Example

(Running effects around request resolution)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> {
readonly _tag: "GetDataRequest"
}
const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
const events: Array<string> = []
const resolver = RequestResolver.make<GetDataRequest>((entries) =>
Effect.sync(() => {
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed("data"))
}
})
)
// Add setup and cleanup around request execution
const resolverWithAround = RequestResolver.around(
resolver,
(entries) =>
Effect.gen(function*() {
events.push(`Starting batch of ${entries.length} requests`)
return entries.length
}),
(entries, initialSize) =>
Effect.sync(() => {
events.push(`Batch completed with ${entries.length} requests (started with ${initialSize})`)
})
)
const program = Effect.request(GetDataRequest(), resolverWithAround)
const result = await Effect.runPromise(program)
events // => ["Starting batch of 1 requests", "Batch completed with 1 requests (started with 1)"]
result // => "data"

batchN

Added in v2.0.0 Source

Returns a request resolver that collects at most n requests into each batch.

Details

When more than n requests are waiting for the same resolver and batch key, the current batch is run and additional requests are collected into later batches.

Signature

declare const batchN: {
(n: number): <A extends Any>(self: RequestResolver<A>) => RequestResolver<A>;
<A extends Any>(self: RequestResolver<A>, n: number): RequestResolver<A>;
}

Example

(Limiting parallel request batches)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> {
readonly _tag: "GetDataRequest"
readonly id: number
}
const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
const batchSizes: Array<number> = []
const resolver = RequestResolver.make<GetDataRequest>((entries) =>
Effect.sync(() => {
batchSizes.push(entries.length)
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed(`data-${entry.request.id}`))
}
})
)
// Limit batches to maximum 5 requests
const limitedResolver = RequestResolver.batchN(resolver, 5)
// When more than 5 requests are made, they'll be split into multiple batches
const requests = Array.from(
{ length: 12 },
(_, i) => Effect.request(GetDataRequest({ id: i }), limitedResolver)
)
const result = await Effect.runPromise(Effect.all(requests, { concurrency: "unbounded" }))
batchSizes // => [5, 5, 2]
result.length // => 12
Array.of(result[0], result[11]) // => ["data-0", "data-11"]

grouped

Added in v4.0.0 Source

Transforms a request resolver by grouping requests using the specified key function.

Signature

declare const grouped: {
<A extends Any, K>(f: (entry: Entry<A>) => K): (self: RequestResolver<A>) => RequestResolver<A>;
<A extends Any, K>(self: RequestResolver<A>, f: (entry: Entry<A>) => K): RequestResolver<A>;
}

Example

(Grouping resolver requests)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetUserRequest extends Request.Request<string> {
readonly _tag: "GetUserRequest"
readonly userId: number
readonly department: string
}
const GetUserRequest = Request.tagged<GetUserRequest>("GetUserRequest")
const batchSizes: Array<number> = []
const resolver = RequestResolver.make<GetUserRequest>((entries) =>
Effect.sync(() => {
batchSizes.push(entries.length)
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed(`User ${entry.request.userId}`))
}
})
)
// Group requests by department for more efficient processing
const groupedResolver = RequestResolver.grouped(
resolver,
({ request }) => request.department
)
// Requests for the same department will be batched together
const requests = [
Effect.request(
GetUserRequest({ userId: 1, department: "Engineering" }),
groupedResolver
),
Effect.request(
GetUserRequest({ userId: 2, department: "Engineering" }),
groupedResolver
),
Effect.request(
GetUserRequest({ userId: 3, department: "Marketing" }),
groupedResolver
)
]
const result = await Effect.runPromise(Effect.all(requests, { concurrency: "unbounded" }))
batchSizes.sort()
batchSizes // => [1, 2]
result // => ["User 1", "User 2", "User 3"]

race

Added in v2.0.0 Source

Returns a request resolver that sends each batch to both resolvers and completes with the first resolver to finish.

Details

The losing resolver run is interrupted after the winning resolver completes the batch.

Signature

declare const race: {
<A2 extends Any>(that: RequestResolver<A2>): <A extends Any>(self: RequestResolver<A>) => RequestResolver<A2 & A>;
<A extends Any, A2 extends Any>(self: RequestResolver<A>, that: RequestResolver<A2>): RequestResolver<A & A2>;
}

Example

(Racing request resolvers)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> {
readonly _tag: "GetDataRequest"
readonly id: number
}
const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
// Fast resolver (simulating cache)
const fastResolver = RequestResolver.make<GetDataRequest>((entries) =>
Effect.gen(function*() {
yield* Effect.sleep("10 millis")
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed(`fast-${entry.request.id}`))
}
})
)
// Slow resolver (simulating database)
const slowResolver = RequestResolver.make<GetDataRequest>((entries) =>
Effect.gen(function*() {
yield* Effect.sleep("100 millis")
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed(`slow-${entry.request.id}`))
}
})
)
// Race resolvers - will use whichever completes first
const racingResolver = RequestResolver.race(fastResolver, slowResolver)
const program = Effect.request(GetDataRequest({ id: 1 }), racingResolver)
await Effect.runPromise(program) // => "fast-1"

withSpan

Added in v4.0.0 Source

Adds a tracing span to the request resolver, which will also add any span links from the request's.

Signature

declare const withSpan: {
<A extends Any>(name: string, options?: SpanOptions | (entries: [Entry<A>, ...Array<Entry<A>>]) => SpanOptions): (self: RequestResolver<A>) => RequestResolver<A>;
<A extends Any>(self: RequestResolver<A>, name: string, options?: SpanOptions | (entries: [Entry<A>, ...Array<Entry<A>>]) => SpanOptions): RequestResolver<A>;
}

Example

(Adding a tracing span)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> {
readonly _tag: "GetDataRequest"
readonly id: number
}
const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
const resolver = RequestResolver.make<GetDataRequest>((entries) =>
Effect.sync(() => {
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed(`data-${entry.request.id}`))
}
})
)
// Add tracing span with custom name and attributes
const tracedResolver = RequestResolver.withSpan(
resolver,
"user-data-resolver",
{
attributes: {
"resolver.type": "user-data",
"resolver.version": "1.0"
}
}
)
// Spans will automatically include batch size and request links
const effect = Effect.request(GetDataRequest({ id: 123 }), tracedResolver)
await Effect.runPromise(effect) // => "data-123"

Constructors

fromEffect

Added in v2.0.0 Source

Constructs a request resolver from an effectual function.

Signature

declare function fromEffect<A extends Any>(f: (entry: Entry<A>) => Effect<Success<A>, Error<A>>): RequestResolver<A>

Example

(Creating a resolver from an effectful function)

import { Effect, Request, RequestResolver } from "effect"
interface GetUserFromAPIRequest extends Request.Request<string> {
readonly _tag: "GetUserFromAPIRequest"
readonly id: number
}
const GetUserFromAPIRequest = Request.tagged<GetUserFromAPIRequest>(
"GetUserFromAPIRequest"
)
// Create a resolver that uses effects (like HTTP calls)
const UserAPIResolver = RequestResolver.fromEffect<GetUserFromAPIRequest>(
(entry) => Effect.succeed(`User ${entry.request.id} from API`)
)
// Usage
const getUserEffect = Effect.request(
GetUserFromAPIRequest({ id: 123 }),
UserAPIResolver
)
await Effect.runPromise(getUserEffect) // => "User 123 from API"

Constructs a request resolver from a list of tags paired to functions, that takes a list of requests and returns a list of results of the same size. Each item in the result list must correspond to the item at the same index in the request list.

Signature

declare function fromEffectTagged<A extends Any & {
readonly _tag: string;
}>(): <Fns extends { [Tag in string]: [Extract<A, {
readonly _tag: Tag;
}>] extends [Req] ? Req extends Request<ReqA, ReqE, _ReqR> ? (requests: Array<Entry<Req>>) => Effect<Iterable<ReqA, any, any>, ReqE> : never : never }>(fns: Fns) => RequestResolver<A>

Example

(Handling tagged request batches)

import { Effect, Request, RequestResolver } from "effect"
interface GetUser extends Request.Request<string, Error> {
readonly _tag: "GetUser"
readonly id: number
}
interface GetPost extends Request.Request<string, Error> {
readonly _tag: "GetPost"
readonly id: number
}
type MyRequest = GetUser | GetPost
const GetUser = Request.tagged<GetUser>("GetUser")
const GetPost = Request.tagged<GetPost>("GetPost")
// Create a resolver that handles different request types
const MyResolver = RequestResolver.fromEffectTagged<MyRequest>()({
GetUser: (requests) =>
Effect.succeed(requests.map((req) => `User ${req.request.id}`)),
GetPost: (requests) =>
Effect.succeed(requests.map((req) => `Post ${req.request.id}`))
})
const program = Effect.all([
Effect.request<GetUser>(GetUser({ id: 1 }), MyResolver),
Effect.request<GetPost>(GetPost({ id: 2 }), MyResolver)
] as const)
await Effect.runPromise(program) // => ["User 1", "Post 2"]

fromFunction

Added in v2.0.0 Source

Constructs a request resolver from a pure function.

Signature

declare function fromFunction<A extends Any>(f: (entry: Entry<A>) => Success<A>): RequestResolver<A>

Example

(Creating a resolver from a pure function)

import { Effect, Request, RequestResolver } from "effect"
interface GetSquareRequest extends Request.Request<number> {
readonly _tag: "GetSquareRequest"
readonly value: number
}
const GetSquareRequest = Request.tagged<GetSquareRequest>("GetSquareRequest")
// Create a resolver from a pure function
const SquareResolver = RequestResolver.fromFunction<GetSquareRequest>(
(entry) => entry.request.value * entry.request.value
)
// Usage
const getSquareEffect = Effect.request(
GetSquareRequest({ value: 5 }),
SquareResolver
)
await Effect.runPromise(getSquareEffect) // => 25

Constructs a request resolver from a pure function that takes a list of requests and returns a list of results of the same size. Each item in the result list must correspond to the item at the same index in the request list.

Signature

declare function fromFunctionBatched<A extends Any>(f: (entries: [Entry<A>, ...Array<Entry<A>>]) => Iterable<Success<A>>): RequestResolver<A>

Example

(Batching pure request handling)

import { Effect, Request, RequestResolver } from "effect"
interface GetDoubleRequest extends Request.Request<number> {
readonly _tag: "GetDoubleRequest"
readonly value: number
}
const GetDoubleRequest = Request.tagged<GetDoubleRequest>("GetDoubleRequest")
// Create a resolver that processes multiple requests in a batch
const DoubleResolver = RequestResolver.fromFunctionBatched<GetDoubleRequest>(
(entries) => entries.map((entry) => entry.request.value * 2)
)
// Usage with multiple requests
const effects = [1, 2, 3].map((value) =>
Effect.request(GetDoubleRequest({ value }), DoubleResolver)
)
const batchedEffect = Effect.all(effects)
await Effect.runPromise(batchedEffect) // => [2, 4, 6]

make

Added in v2.0.0 Source

Constructs a request resolver with the specified method to run requests.

Signature

declare function make<A extends Any>(runAll: (entries: [Entry<A>, ...Array<Entry<A>>], key: unknown) => Effect<void, Error<A>>): RequestResolver<A>

Example

(Creating a request resolver)

import { Effect, Exit, Request, RequestResolver } from "effect"
// Define a request type
interface GetUserRequest extends Request.Request<string, Error> {
readonly _tag: "GetUserRequest"
readonly id: number
}
const GetUserRequest = Request.tagged<GetUserRequest>("GetUserRequest")
// Create a resolver that handles the requests
const UserResolver = RequestResolver.make<GetUserRequest>((entries) =>
Effect.sync(() => {
for (const entry of entries) {
// Complete each request with a result
entry.completeUnsafe(Exit.succeed(`User ${entry.request.id}`))
}
})
)
// Use the resolver to handle requests
const getUserEffect = Effect.request(GetUserRequest({ id: 123 }), UserResolver)
await Effect.runPromise(getUserEffect) // => "User 123"

makeGrouped

Added in v4.0.0 Source

Constructs a request resolver with the requests grouped by a calculated key.

Details

The key can use the Equal trait to determine if two keys are equal.

Signature

declare function makeGrouped<A extends Any, K>(options: {
readonly key: (entry: Entry<A>) => K;
readonly resolver: (entries: [Entry<A>, ...Array<Entry<A>>], key: K) => Effect<void, Error<A>>;
}): RequestResolver<A>

Example

(Grouping requests by key)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetUserByRole extends Request.Request<string, Error> {
readonly _tag: "GetUserByRole"
readonly role: string
readonly id: number
}
const GetUserByRole = Request.tagged<GetUserByRole>("GetUserByRole")
const batches: Array<[role: string, size: number]> = []
// Group requests by role for efficient batch processing
const UserByRoleResolver = RequestResolver.makeGrouped<GetUserByRole, string>({
key: ({ request }) => request.role,
resolver: (entries, role) =>
Effect.sync(() => {
batches.push([role, entries.length])
for (const entry of entries) {
entry.completeUnsafe(
Exit.succeed(`User ${entry.request.id} with role ${role}`)
)
}
})
})
const program = Effect.all([
Effect.request<GetUserByRole>(GetUserByRole({ role: "admin", id: 1 }), UserByRoleResolver),
Effect.request<GetUserByRole>(GetUserByRole({ role: "admin", id: 2 }), UserByRoleResolver)
] as const, { concurrency: "unbounded" })
const result = await Effect.runPromise(program)
batches // => [["admin", 2]]
result // => ["User 1 with role admin", "User 2 with role admin"]

makeWith

Added in v4.0.0 Source

Creates a request resolver with fine-grained control over its behavior.

When to use

Use when you need to supply the resolver batching primitives directly, including the batch key, optional pre-check, delay effect, collection cutoff, and batch runner.

Details

batchKey groups request entries, delay schedules batch execution, collectWhile can end collection early, and runAll receives a non-empty batch for one key.

Gotchas

Accepted entries must be completed. If runAll succeeds with incomplete entries, waiting requests fail. If preCheck returns false, the entry is not batched, so it must be completed or linked to another completion path.

See

  • make for constructing a resolver from a batch runner
  • makeGrouped for constructing a resolver that groups requests by key

Signature

declare function makeWith<A extends Any>(options: {
readonly batchKey: (request: Entry<A>) => unknown;
readonly collectWhile: (requests: ReadonlySet<Entry<A>>) => boolean;
readonly delay: Effect<void>;
readonly preCheck?: (entry: Entry<A>) => boolean;
readonly runAll: (entries: [Entry<A>, ...Array<Entry<A>>], key: unknown) => Effect<void, Error<A>>;
}): RequestResolver<A>

never

Added in v2.0.0 Source

Creates a request resolver that never executes requests.

When to use

Use as a resolver value for request types that are statically impossible and should never be issued.

Gotchas

If this resolver is used for an actual request, the request waits forever unless the fiber is interrupted.

See

  • make for constructing a resolver that executes batches and completes request entries

Signature

declare const never: RequestResolver<never>

Delays & Timeouts

setDelay

Added in v4.0.0 Source

Sets the batch delay window for this request resolver to the specified duration.

Signature

declare const setDelay: {
(duration: Input): <A extends Any>(self: RequestResolver<A>) => RequestResolver<A>;
<A extends Any>(self: RequestResolver<A>, duration: Input): RequestResolver<A>;
}

Example

(Setting a batch delay)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> {
readonly _tag: "GetDataRequest"
}
const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
const resolver = RequestResolver.make<GetDataRequest>((entries) =>
Effect.sync(() => {
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed("data"))
}
})
)
// Add a 100ms delay to batch requests together
const delayedResolver = RequestResolver.setDelay(resolver, "100 millis")
const program = Effect.request(GetDataRequest(), delayedResolver)
await Effect.runPromise(program) // => "data"

Sets the batch delay effect for this request resolver.

Signature

declare const setDelayEffect: {
(delay: Effect<void>): <A extends Any>(self: RequestResolver<A>) => RequestResolver<A>;
<A extends Any>(self: RequestResolver<A>, delay: Effect<void>): RequestResolver<A>;
}

Example

(Setting an effectful batch delay)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> {
readonly _tag: "GetDataRequest"
}
const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
const resolver = RequestResolver.make<GetDataRequest>((entries) =>
Effect.sync(() => {
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed("data"))
}
})
)
let delayRan = false
// Set a custom delay effect
const resolverWithCustomDelay = RequestResolver.setDelayEffect(
resolver,
Effect.sync(() => {
delayRan = true
})
)
await Effect.runPromise(resolverWithCustomDelay.delay)
Array.of(delayRan, RequestResolver.isRequestResolver(resolverWithCustomDelay)) // => [true, true]

Guards

Returns true if the specified value is a RequestResolver, false otherwise.

When to use

Use to narrow unknown values before passing them to APIs that require a RequestResolver.

See

Signature

declare function isRequestResolver(u: unknown): u is RequestResolver<any>

Models

RequestResolver interface

Added in v2.0.0 Source

A resolver that executes and completes batched Request entries.

Details

A resolver controls how requests are grouped, delayed, optionally pre-checked, and finally run. Its runAll method receives a non-empty batch of Request.Entry values for a single batch key and must complete every received entry, usually by calling completeUnsafe or one of the Request completion helpers.

Gotchas

If a resolver finishes without completing an entry, the waiting request fails because the resolver did not supply a result.

Signature

interface RequestResolver<in A extends Request.Any> extends Variance<A>, Pipeable {
readonly delay: Effect<void>;
readonly preCheck: (entry: Entry<A>) => boolean | undefined;
batchKey(entry: Entry<A>): unknown;
collectWhile(entries: ReadonlySet<Entry<A>>): boolean;
runAll(entries: [Entry<A>, ...Array<Entry<A>>], key: unknown): Effect<void, Error<A>>;
}

Example

(Defining a request resolver)

import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetUserRequest extends Request.Request<string, Error> {
readonly _tag: "GetUserRequest"
readonly id: number
}
const GetUserRequest = Request.tagged<GetUserRequest>("GetUserRequest")
// In practice, you would typically use RequestResolver.make() instead
const resolver = RequestResolver.make<GetUserRequest>((entries) =>
Effect.sync(() => {
for (const entry of entries) {
entry.completeUnsafe(Exit.succeed(`User ${entry.request.id}`))
}
})
)
const program = Effect.request(GetUserRequest({ id: 1 }), resolver)
await Effect.runPromise(program) // => "User 1"

Other

Namespace containing type-level helpers associated with RequestResolver.