/** * 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. * * @stability stable * @since 2.0.0 */ import type { NonEmptyArray } from "./Array.ts"; import * as Cache from "./Cache.ts"; import type * as Duration from "./Duration.ts"; import * as Effect from "./Effect.ts"; import type * as Persistable from "./persistence/Persistable.ts"; import * as Persistence from "./persistence/Persistence.ts"; import { type Pipeable } from "./Pipeable.ts"; import type * as Request from "./Request.ts"; import type * as Schema from "./Schema.ts"; import type { Scope } from "./Scope.ts"; import * as Tracer from "./Tracer.ts"; import type * as Types from "./Types.ts"; declare const TypeId = "~effect/RequestResolver"; /** * 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. * * **Example** (Defining a request resolver) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserRequest extends Request.Request { * readonly _tag: "GetUserRequest" * readonly id: number * } * const GetUserRequest = Request.tagged("GetUserRequest") * * // In practice, you would typically use RequestResolver.make() instead * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category models * @since 2.0.0 */ export interface RequestResolver extends RequestResolver.Variance, Pipeable { readonly delay: Effect.Effect; /** * Get a batch key for the given request. */ batchKey(entry: Request.Entry): unknown; /** * An optional pre-check function that can be used to filter requests before * they are added to a batch. If the function returns `false`, the request * will not be processed. */ readonly preCheck: ((entry: Request.Entry) => boolean) | undefined; /** * Should the resolver continue collecting requests? Otherwise, it will * immediately execute the collected requests cutting the delay short. */ collectWhile(entries: ReadonlySet>): boolean; /** * Execute a collection of requests. */ runAll(entries: NonEmptyArray>, key: unknown): Effect.Effect>; } /** * Namespace containing type-level helpers associated with `RequestResolver`. * * @stability stable * @since 2.0.0 */ export declare namespace RequestResolver { /** * Variance marker carried by every `RequestResolver`. * * **Details** * * This marker preserves the request type accepted by the resolver for * Effect's type-level machinery. Users normally do not implement it directly. * * @category models * @since 2.0.0 */ interface Variance { readonly [TypeId]: { readonly _A: Types.Contravariant; }; } } /** * 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 {@link RequestResolver} for the type narrowed by this guard * * @stability stable * @category guards * @since 2.0.0 */ export declare const isRequestResolver: (u: unknown) => u is RequestResolver; /** * 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. The batch runs with the context of the caller that created * it, even when a later caller ends collection. Each entry retains its own * caller context in `entry.context`. * * **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 {@link make} for constructing a resolver from a batch runner * @see {@link makeGrouped} for constructing a resolver that groups requests by key * * @stability stable * @category constructors * @since 4.0.0 */ export declare const makeWith: (options: { readonly batchKey: (request: Request.Entry) => unknown; readonly preCheck?: ((entry: Request.Entry) => boolean) | undefined; readonly delay: Effect.Effect; readonly collectWhile: (requests: ReadonlySet>) => boolean; readonly runAll: (entries: NonEmptyArray>, key: unknown) => Effect.Effect>; }) => RequestResolver; /** * Constructs a request resolver with the specified method to run requests. * * **Details** * * `runAll` runs with the context of the caller that created the batch, including * when `collectWhile` ends collection early (for example with `batchN`). * * **Gotchas** * * Different caller contexts do not split batches. Use `entry.context` for * per-entry services, or `makeGrouped` to separate requests by the services * that affect the backend operation, such as tenant or authorization settings. * * **Example** (Creating a request resolver) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * // Define a request type * interface GetUserRequest extends Request.Request { * readonly _tag: "GetUserRequest" * readonly id: number * } * const GetUserRequest = Request.tagged("GetUserRequest") * * // Create a resolver that handles the requests * const UserResolver = RequestResolver.make((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" * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export declare const make: (runAll: (entries: NonEmptyArray>, key: unknown) => Effect.Effect>) => RequestResolver; /** * 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. * Each batch runs with the context of the caller that created that batch, * whether collection ends after the delay or at a `collectWhile` cutoff. * * **Gotchas** * * Caller services that are not included in the key do not split batches. Include * tenant or authorization settings in the key when they affect the backend * operation, or read each caller's services from `entry.context`. * * **Example** (Grouping requests by key) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserByRole extends Request.Request { * readonly _tag: "GetUserByRole" * readonly role: string * readonly id: number * } * const GetUserByRole = Request.tagged("GetUserByRole") * * const batches: Array<[role: string, size: number]> = [] * * // Group requests by role for efficient batch processing * const UserByRoleResolver = RequestResolver.makeGrouped({ * 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({ role: "admin", id: 1 }), UserByRoleResolver), * Effect.request(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"] * ``` * * @stability stable * @category constructors * @since 4.0.0 */ export declare const makeGrouped: (options: { readonly key: (entry: Request.Entry) => K; readonly resolver: (entries: NonEmptyArray>, key: K) => Effect.Effect>; }) => RequestResolver; /** * Constructs a request resolver from a pure function. * * **Example** (Creating a resolver from a pure function) * * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetSquareRequest extends Request.Request { * readonly _tag: "GetSquareRequest" * readonly value: number * } * const GetSquareRequest = Request.tagged("GetSquareRequest") * * // Create a resolver from a pure function * const SquareResolver = RequestResolver.fromFunction( * (entry) => entry.request.value * entry.request.value * ) * * // Usage * const getSquareEffect = Effect.request( * GetSquareRequest({ value: 5 }), * SquareResolver * ) * await Effect.runPromise(getSquareEffect) // => 25 * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export declare const fromFunction: (f: (entry: Request.Entry) => Request.Success) => RequestResolver; /** * 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. * * **Example** (Batching pure request handling) * * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetDoubleRequest extends Request.Request { * readonly _tag: "GetDoubleRequest" * readonly value: number * } * const GetDoubleRequest = Request.tagged("GetDoubleRequest") * * // Create a resolver that processes multiple requests in a batch * const DoubleResolver = RequestResolver.fromFunctionBatched( * (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] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export declare const fromFunctionBatched: (f: (entries: NonEmptyArray>) => Iterable>) => RequestResolver; /** * Constructs a request resolver from an effectual function. * * **Details** * * Each entry's effect runs with its own caller context. Services provided around * `runAll` are not visible to it; provide them inside `f` instead. Entries with * different caller contexts can still share a batch. * * With `withSpan`, spans created inside `f` are children of the caller's span, * not the resolver span. The resolver span still links to each caller's span. * * **Example** (Creating a resolver from an effectful function) * * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetUserFromAPIRequest extends Request.Request { * readonly _tag: "GetUserFromAPIRequest" * readonly id: number * } * const GetUserFromAPIRequest = Request.tagged( * "GetUserFromAPIRequest" * ) * * // Create a resolver that uses effects (like HTTP calls) * const UserAPIResolver = RequestResolver.fromEffect( * (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" * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export declare const fromEffect: (f: (entry: Request.Entry) => Effect.Effect, Request.Error>) => RequestResolver; /** * 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. * * **Example** (Handling tagged request batches) * * ```ts import.meta.vitest * import { Effect, Request, RequestResolver } from "effect" * * interface GetUser extends Request.Request { * readonly _tag: "GetUser" * readonly id: number * } * * interface GetPost extends Request.Request { * readonly _tag: "GetPost" * readonly id: number * } * * type MyRequest = GetUser | GetPost * const GetUser = Request.tagged("GetUser") * const GetPost = Request.tagged("GetPost") * * // Create a resolver that handles different request types * const MyResolver = RequestResolver.fromEffectTagged()({ * 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({ id: 1 }), MyResolver), * Effect.request(GetPost({ id: 2 }), MyResolver) * ] as const) * await Effect.runPromise(program) // => ["User 1", "Post 2"] * ``` * * @stability stable * @category constructors * @since 2.0.0 */ export declare const fromEffectTagged: () => ] extends [infer Req] ? Req extends Request.Request ? (requests: Array>) => Effect.Effect, ReqE> : never : never; }>(fns: Fns) => RequestResolver; /** * Sets the batch delay effect for this request resolver. * * **Example** (Setting an effectful batch delay) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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] * ``` * * @stability stable * @category delays & timeouts * @since 4.0.0 */ export declare const setDelayEffect: { /** * Sets the batch delay effect for this request resolver. * * **Example** (Setting an effectful batch delay) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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] * ``` * * @stability stable * @category delays & timeouts * @since 4.0.0 */ (delay: Effect.Effect): (self: RequestResolver) => RequestResolver; /** * Sets the batch delay effect for this request resolver. * * **Example** (Setting an effectful batch delay) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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] * ``` * * @stability stable * @category delays & timeouts * @since 4.0.0 */ (self: RequestResolver, delay: Effect.Effect): RequestResolver; }; /** * Sets the batch delay window for this request resolver to the specified duration. * * **Example** (Setting a batch delay) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category delays & timeouts * @since 4.0.0 */ export declare const setDelay: { /** * Sets the batch delay window for this request resolver to the specified duration. * * **Example** (Setting a batch delay) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category delays & timeouts * @since 4.0.0 */ (duration: Duration.Input): (self: RequestResolver) => RequestResolver; /** * Sets the batch delay window for this request resolver to the specified duration. * * **Example** (Setting a batch delay) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category delays & timeouts * @since 4.0.0 */ (self: RequestResolver, duration: Duration.Input): RequestResolver; }; /** * Wraps request resolver execution between `before` and `after` effects. * * **Example** (Running effects around request resolution) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const events: Array = [] * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category combinators * @since 2.0.0 */ export declare const around: { /** * Wraps request resolver execution between `before` and `after` effects. * * **Example** (Running effects around request resolution) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const events: Array = [] * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category combinators * @since 2.0.0 */ (before: (entries: NonEmptyArray>>) => Effect.Effect>, after: (entries: NonEmptyArray>>, a: A2) => Effect.Effect>): (self: RequestResolver) => RequestResolver; /** * Wraps request resolver execution between `before` and `after` effects. * * **Example** (Running effects around request resolution) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const events: Array = [] * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category combinators * @since 2.0.0 */ (self: RequestResolver, before: (entries: NonEmptyArray>>) => Effect.Effect>, after: (entries: NonEmptyArray>>, a: A2) => Effect.Effect>): RequestResolver; }; /** * 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 {@link make} for constructing a resolver that executes batches and completes request entries * * @stability stable * @category constructors * @since 2.0.0 */ export declare const never: RequestResolver; /** * 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. Finite fractional values of `n` are rounded down. `NaN` and * non-positive values are treated as `1` so that every batch remains non-empty. * * **Example** (Limiting parallel request batches) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const batchSizes: Array = [] * * const resolver = RequestResolver.make((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"] * ``` * * @stability stable * @category combinators * @since 2.0.0 */ export declare const batchN: { /** * 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. Finite fractional values of `n` are rounded down. `NaN` and * non-positive values are treated as `1` so that every batch remains non-empty. * * **Example** (Limiting parallel request batches) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const batchSizes: Array = [] * * const resolver = RequestResolver.make((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"] * ``` * * @stability stable * @category combinators * @since 2.0.0 */ (n: number): (self: RequestResolver) => RequestResolver; /** * 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. Finite fractional values of `n` are rounded down. `NaN` and * non-positive values are treated as `1` so that every batch remains non-empty. * * **Example** (Limiting parallel request batches) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const batchSizes: Array = [] * * const resolver = RequestResolver.make((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"] * ``` * * @stability stable * @category combinators * @since 2.0.0 */ (self: RequestResolver, n: number): RequestResolver; }; /** * Transforms a request resolver by grouping requests using the specified key * function. * * **Example** (Grouping resolver requests) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserRequest extends Request.Request { * readonly _tag: "GetUserRequest" * readonly userId: number * readonly department: string * } * const GetUserRequest = Request.tagged("GetUserRequest") * * const batchSizes: Array = [] * * const resolver = RequestResolver.make((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"] * ``` * * @stability stable * @category combinators * @since 4.0.0 */ export declare const grouped: { /** * Transforms a request resolver by grouping requests using the specified key * function. * * **Example** (Grouping resolver requests) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserRequest extends Request.Request { * readonly _tag: "GetUserRequest" * readonly userId: number * readonly department: string * } * const GetUserRequest = Request.tagged("GetUserRequest") * * const batchSizes: Array = [] * * const resolver = RequestResolver.make((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"] * ``` * * @stability stable * @category combinators * @since 4.0.0 */ (f: (entry: Request.Entry) => K): (self: RequestResolver) => RequestResolver; /** * Transforms a request resolver by grouping requests using the specified key * function. * * **Example** (Grouping resolver requests) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetUserRequest extends Request.Request { * readonly _tag: "GetUserRequest" * readonly userId: number * readonly department: string * } * const GetUserRequest = Request.tagged("GetUserRequest") * * const batchSizes: Array = [] * * const resolver = RequestResolver.make((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"] * ``` * * @stability stable * @category combinators * @since 4.0.0 */ (self: RequestResolver, f: (entry: Request.Entry) => K): RequestResolver; }; /** * 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. * * **Example** (Racing request resolvers) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * // Fast resolver (simulating cache) * const fastResolver = RequestResolver.make((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((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" * ``` * * @stability stable * @category combinators * @since 2.0.0 */ export declare const race: { /** * 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. * * **Example** (Racing request resolvers) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * // Fast resolver (simulating cache) * const fastResolver = RequestResolver.make((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((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" * ``` * * @stability stable * @category combinators * @since 2.0.0 */ (that: RequestResolver): (self: RequestResolver) => RequestResolver; /** * 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. * * **Example** (Racing request resolvers) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * // Fast resolver (simulating cache) * const fastResolver = RequestResolver.make((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((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" * ``` * * @stability stable * @category combinators * @since 2.0.0 */ (self: RequestResolver, that: RequestResolver): RequestResolver; }; /** * Adds a tracing span to the request resolver, which will also add any span * links from the request's. * * **Example** (Adding a tracing span) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category combinators * @since 4.0.0 */ export declare const withSpan: { /** * Adds a tracing span to the request resolver, which will also add any span * links from the request's. * * **Example** (Adding a tracing span) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category combinators * @since 4.0.0 */ (name: string, options?: Tracer.SpanOptions | ((entries: NonEmptyArray>) => Tracer.SpanOptions) | undefined): (self: RequestResolver) => RequestResolver; /** * Adds a tracing span to the request resolver, which will also add any span * links from the request's. * * **Example** (Adding a tracing span) * * ```ts import.meta.vitest * import { Effect, Exit, Request, RequestResolver } from "effect" * * interface GetDataRequest extends Request.Request { * readonly _tag: "GetDataRequest" * readonly id: number * } * const GetDataRequest = Request.tagged("GetDataRequest") * * const resolver = RequestResolver.make((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" * ``` * * @stability stable * @category combinators * @since 4.0.0 */ (self: RequestResolver, name: string, options?: Tracer.SpanOptions | ((entries: NonEmptyArray>) => Tracer.SpanOptions) | undefined): RequestResolver; }; /** * 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 {@link withCache} for keeping caching behind a resolver used with `Effect.request` * @see {@link persisted} for storing persistable request results outside process memory * @see {@link Cache.Cache} for operations available on the returned cache * * @stability stable * @category caching * @since 4.0.0 */ export declare const asCache: { /** * 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 {@link withCache} for keeping caching behind a resolver used with `Effect.request` * @see {@link persisted} for storing persistable request results outside process memory * @see {@link Cache.Cache} for operations available on the returned cache * * @stability stable * @category caching * @since 4.0.0 */ (options: { readonly capacity: number; readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined; readonly requireServicesAt?: ServiceMode | undefined; }): (self: RequestResolver) => Effect.Effect, Request.Error, "construction" extends ServiceMode ? never : Request.Services>, never, "construction" extends ServiceMode ? Request.Services : never>; /** * 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 {@link withCache} for keeping caching behind a resolver used with `Effect.request` * @see {@link persisted} for storing persistable request results outside process memory * @see {@link Cache.Cache} for operations available on the returned cache * * @stability stable * @category caching * @since 4.0.0 */ (self: RequestResolver, options: { readonly capacity: number; readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined; readonly requireServicesAt?: ServiceMode | undefined; }): Effect.Effect, Request.Error, "construction" extends ServiceMode ? never : Request.Services>, never, "construction" extends ServiceMode ? Request.Services : never>; }; /** * 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 without interruptions * are cached the same as successes. Results containing interruptions are not * cached. Request equality controls cache hits. * * @see {@link asCache} for exposing the resolver as a `Cache` with time-to-live and service lookup controls * @see {@link persisted} for backing persistable requests with the configured persistence store * * @stability stable * @category caching * @since 4.0.0 */ export declare const withCache: { /** * 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 without interruptions * are cached the same as successes. Results containing interruptions are not * cached. Request equality controls cache hits. * * @see {@link asCache} for exposing the resolver as a `Cache` with time-to-live and service lookup controls * @see {@link persisted} for backing persistable requests with the configured persistence store * * @stability stable * @category caching * @since 4.0.0 */ (options: { readonly capacity: number; readonly strategy?: "lru" | "fifo" | undefined; }): (self: RequestResolver) => Effect.Effect>; /** * 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 without interruptions * are cached the same as successes. Results containing interruptions are not * cached. Request equality controls cache hits. * * @see {@link asCache} for exposing the resolver as a `Cache` with time-to-live and service lookup controls * @see {@link persisted} for backing persistable requests with the configured persistence store * * @stability stable * @category caching * @since 4.0.0 */ (self: RequestResolver, options: { readonly capacity: number; readonly strategy?: "lru" | "fifo" | undefined; }): Effect.Effect>; }; /** * 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 {@link withCache} for in-memory resolver caching that does not require persistable request values or a persistence store * @see {@link asCache} for exposing resolver results through a `Cache` instead of returning another resolver * * @stability unstable * @category caching * @since 4.0.0 */ export declare const persisted: { /** * 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 {@link withCache} for in-memory resolver caching that does not require persistable request values or a persistence store * @see {@link asCache} for exposing resolver results through a `Cache` instead of returning another resolver * * @stability unstable * @category caching * @since 4.0.0 */ & Persistable.Any>(options: { readonly storeId: string; readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined; readonly staleWhileRevalidate?: ((exit: Request.Result, request: A) => boolean) | undefined; }): (self: RequestResolver) => Effect.Effect, never, Persistence.Persistence | Scope>; /** * 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 {@link withCache} for in-memory resolver caching that does not require persistable request values or a persistence store * @see {@link asCache} for exposing resolver results through a `Cache` instead of returning another resolver * * @stability unstable * @category caching * @since 4.0.0 */ & Persistable.Any>(self: RequestResolver, options: { readonly storeId: string; readonly timeToLive?: ((exit: Request.Result, request: A) => Duration.Input) | undefined; readonly staleWhileRevalidate?: ((exit: Request.Result, request: A) => boolean) | undefined; }): Effect.Effect, never, Persistence.Persistence | Scope>; }; export {}; //# sourceMappingURL=RequestResolver.d.ts.map