| /** | |
| * 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. | |
| * | |
| * @since 2.0.0 | |
| */ | |
| import type { NonEmptyArray } from "./Array.ts" | |
| import * as Arr from "./Array.ts" | |
| import * as Cache from "./Cache.ts" | |
| import * as Context from "./Context.ts" | |
| import type * as Duration from "./Duration.ts" | |
| import * as Effect from "./Effect.ts" | |
| import * as Exit from "./Exit.ts" | |
| import { constTrue, dual, identity } from "./Function.ts" | |
| import { exitFail, exitSucceed } from "./internal/core.ts" | |
| import * as effect from "./internal/effect.ts" | |
| import * as internal from "./internal/request.ts" | |
| import * as Iterable from "./Iterable.ts" | |
| import * as MutableHashMap from "./MutableHashMap.ts" | |
| import { type Pipeable, pipeArguments } from "./Pipeable.ts" | |
| import { hasProperty } from "./Predicate.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" | |
| import type * as Persistable from "./unstable/persistence/Persistable.ts" | |
| import * as Persistence from "./unstable/persistence/Persistence.ts" | |
| 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 { Effect, Exit, RequestResolver } from "effect" | |
| * import type { Request } from "effect" | |
| * | |
| * interface GetUserRequest extends Request.Request<string, Error> { | |
| * readonly _tag: "GetUserRequest" | |
| * readonly id: number | |
| * } | |
| * | |
| * // 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}`)) | |
| * } | |
| * }) | |
| * ) | |
| * ``` | |
| * | |
| * @category models | |
| * @since 2.0.0 | |
| */ | |
| export interface RequestResolver<in A extends Request.Any> extends RequestResolver.Variance<A>, Pipeable { | |
| readonly delay: Effect.Effect<void> | |
| /** | |
| * Get a batch key for the given request. | |
| */ | |
| batchKey(entry: Request.Entry<A>): 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<A>) => boolean) | undefined | |
| /** | |
| * Should the resolver continue collecting requests? Otherwise, it will | |
| * immediately execute the collected requests cutting the delay short. | |
| */ | |
| collectWhile(entries: ReadonlySet<Request.Entry<A>>): boolean | |
| /** | |
| * Execute a collection of requests. | |
| */ | |
| runAll(entries: NonEmptyArray<Request.Entry<A>>, key: unknown): Effect.Effect<void, Request.Error<A>> | |
| } | |
| /** | |
| * Namespace containing type-level helpers associated with `RequestResolver`. | |
| * | |
| * @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 | |
| */ | |
| export interface Variance<in A> { | |
| readonly [TypeId]: { | |
| readonly _A: Types.Contravariant<A> | |
| } | |
| } | |
| } | |
| const RequestResolverProto = { | |
| [TypeId]: { | |
| _A: identity, | |
| _R: identity | |
| }, | |
| pipe() { | |
| return pipeArguments(this, arguments) | |
| } | |
| } | |
| /** | |
| * 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 | |
| * | |
| * @category guards | |
| * @since 2.0.0 | |
| */ | |
| export const isRequestResolver = (u: unknown): u is RequestResolver<any> => hasProperty(u, TypeId) | |
| /** | |
| * 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 {@link make} for constructing a resolver from a batch runner | |
| * @see {@link makeGrouped} for constructing a resolver that groups requests by key | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const makeWith = <A extends Request.Any>(options: { | |
| readonly batchKey: (request: Request.Entry<A>) => unknown | |
| readonly preCheck?: ((entry: Request.Entry<A>) => boolean) | undefined | |
| readonly delay: Effect.Effect<void> | |
| readonly collectWhile: (requests: ReadonlySet<Request.Entry<A>>) => boolean | |
| readonly runAll: (entries: NonEmptyArray<Request.Entry<A>>, key: unknown) => Effect.Effect<void, Request.Error<A>> | |
| }): RequestResolver<A> => { | |
| const self = Object.create(RequestResolverProto) | |
| self.batchKey = options.batchKey | |
| self.preCheck = options.preCheck | |
| self.delay = options.delay | |
| self.collectWhile = options.collectWhile | |
| self.runAll = options.runAll | |
| return self | |
| } | |
| const defaultKeyObject = {} | |
| const defaultKey = (_request: unknown): unknown => defaultKeyObject | |
| /** | |
| * Constructs a request resolver with the specified method to run requests. | |
| * | |
| * **Example** (Creating a request resolver) | |
| * | |
| * ```ts | |
| * 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) | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 2.0.0 | |
| */ | |
| export const make = <A extends Request.Any>( | |
| runAll: (entries: NonEmptyArray<Request.Entry<A>>, key: unknown) => Effect.Effect<void, Request.Error<A>> | |
| ): RequestResolver<A> => | |
| makeWith({ | |
| batchKey: defaultKey, | |
| delay: Effect.yieldNow, | |
| collectWhile: constTrue, | |
| runAll | |
| }) | |
| /** | |
| * 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. | |
| * | |
| * **Example** (Grouping requests by key) | |
| * | |
| * ```ts | |
| * 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") | |
| * | |
| * // Group requests by role for efficient batch processing | |
| * const UserByRoleResolver = RequestResolver.makeGrouped<GetUserByRole, string>({ | |
| * key: ({ request }) => request.role, | |
| * resolver: (entries, role) => | |
| * Effect.sync(() => { | |
| * console.log(`Processing ${entries.length} requests for role: ${role}`) | |
| * for (const entry of entries) { | |
| * entry.completeUnsafe( | |
| * Exit.succeed(`User ${entry.request.id} with role ${role}`) | |
| * ) | |
| * } | |
| * }) | |
| * }) | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 4.0.0 | |
| */ | |
| export const makeGrouped = <A extends Request.Any, K>(options: { | |
| readonly key: (entry: Request.Entry<A>) => K | |
| readonly resolver: (entries: NonEmptyArray<Request.Entry<A>>, key: K) => Effect.Effect<void, Request.Error<A>> | |
| }): RequestResolver<A> => | |
| makeWith({ | |
| batchKey: hashGroupKey(options.key), | |
| delay: Effect.yieldNow, | |
| collectWhile: constTrue, | |
| runAll: options.resolver as any | |
| }) | |
| const hashGroupKey = <A, K>(get: (entry: Request.Entry<A>) => K) => { | |
| const groupKeys = MutableHashMap.empty<K, K>() | |
| return (entry: Request.Entry<A>): unknown => { | |
| const key = get(entry) | |
| const okey = MutableHashMap.get(groupKeys, key) | |
| if (okey._tag === "Some") { | |
| return okey.value | |
| } | |
| MutableHashMap.set(groupKeys, key, key) | |
| return key | |
| } | |
| } | |
| /** | |
| * Constructs a request resolver from a pure function. | |
| * | |
| * **Example** (Creating a resolver from a pure function) | |
| * | |
| * ```ts | |
| * 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 | |
| * ) | |
| * // Will resolve to 25 | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 2.0.0 | |
| */ | |
| export const fromFunction = <A extends Request.Any>( | |
| f: (entry: Request.Entry<A>) => Request.Success<A> | |
| ): RequestResolver<A> => | |
| make( | |
| (entries) => | |
| Effect.sync(() => { | |
| for (let i = 0; i < entries.length; i++) { | |
| const entry = entries[i] | |
| entry.completeUnsafe(exitSucceed(f(entry))) | |
| } | |
| }) | |
| ) | |
| /** | |
| * 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 { 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) // [2, 4, 6] | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 2.0.0 | |
| */ | |
| export const fromFunctionBatched = <A extends Request.Any>( | |
| f: (entries: NonEmptyArray<Request.Entry<A>>) => Iterable<Request.Success<A>> | |
| ): RequestResolver<A> => | |
| make( | |
| (entries) => | |
| Effect.sync(() => { | |
| let i = 0 | |
| for (const result of f(entries)) { | |
| const entry = entries[i++] | |
| entry.completeUnsafe(exitSucceed(result)) | |
| } | |
| }) | |
| ) | |
| /** | |
| * Constructs a request resolver from an effectual function. | |
| * | |
| * **Example** (Creating a resolver from an effectful function) | |
| * | |
| * ```ts | |
| * 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.gen(function*() { | |
| * // Simulate an API call | |
| * yield* Effect.sleep("100 millis") | |
| * // Just return the result without error handling for simplicity | |
| * return `User ${entry.request.id} from API` | |
| * }) | |
| * ) | |
| * | |
| * // Usage | |
| * const getUserEffect = Effect.request( | |
| * GetUserFromAPIRequest({ id: 123 }), | |
| * UserAPIResolver | |
| * ) | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 2.0.0 | |
| */ | |
| export const fromEffect = <A extends Request.Any>( | |
| f: (entry: Request.Entry<A>) => Effect.Effect<Request.Success<A>, Request.Error<A>> | |
| ): RequestResolver<A> => { | |
| effect.interruptChildrenPatch() // ensure middleware is registered | |
| return make((entries) => | |
| Effect.callback<void>((resume) => { | |
| const parent = effect.getCurrentFiber()! | |
| let done = 0 | |
| for (let i = 0; i < entries.length; i++) { | |
| const entry = entries[i] | |
| const fiber = effect.forkUnsafe(parent as any, f(entry), true) | |
| fiber.addObserver((exit) => { | |
| entry.completeUnsafe(exit) | |
| done++ | |
| if (done === entries.length) { | |
| resume(effect.void) | |
| } | |
| }) | |
| } | |
| }) | |
| ) | |
| } | |
| /** | |
| * 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 { Effect, RequestResolver } from "effect" | |
| * import type { Request } 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 | |
| * | |
| * // 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}`)) | |
| * }) | |
| * ``` | |
| * | |
| * @category constructors | |
| * @since 2.0.0 | |
| */ | |
| export const fromEffectTagged = <A extends Request.Any & { readonly _tag: string }>() => | |
| < | |
| Fns extends { | |
| readonly [Tag in A["_tag"]]: [Extract<A, { readonly _tag: Tag }>] extends [infer Req] | |
| ? Req extends Request.Request<infer ReqA, infer ReqE, infer _ReqR> ? | |
| (requests: Array<Request.Entry<Req>>) => Effect.Effect<Iterable<ReqA>, ReqE> | |
| : never | |
| : never | |
| } | |
| >( | |
| fns: Fns | |
| ): RequestResolver<A> => | |
| make<A>( | |
| (entries): Effect.Effect<void> => { | |
| const grouped = new Map<A["_tag"], Array<Request.Entry<A>>>() | |
| for (let i = 0, len = entries.length; i < len; i++) { | |
| const group = grouped.get(entries[i].request._tag) | |
| if (group) { | |
| group.push(entries[i]) | |
| } else { | |
| grouped.set(entries[i].request._tag, [entries[i]]) | |
| } | |
| } | |
| return Effect.forEach( | |
| grouped, | |
| ([tag, requests]) => | |
| Effect.matchCause((fns[tag] as any)(requests) as Effect.Effect<Array<any>, unknown, unknown>, { | |
| onFailure: (cause) => { | |
| for (let i = 0; i < requests.length; i++) { | |
| const entry = requests[i] | |
| entry.completeUnsafe(exitFail(cause) as any) | |
| } | |
| }, | |
| onSuccess: (res) => { | |
| for (let i = 0; i < res.length; i++) { | |
| const entry = requests[i] | |
| entry.completeUnsafe(exitSucceed(res[i]) as any) | |
| } | |
| } | |
| }), | |
| { concurrency: "unbounded", discard: true } | |
| ) as Effect.Effect<void> | |
| } | |
| ) as any | |
| /** | |
| * Sets the batch delay effect for this request resolver. | |
| * | |
| * **Example** (Setting an effectful batch delay) | |
| * | |
| * ```ts | |
| * 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")) | |
| * } | |
| * }) | |
| * ) | |
| * | |
| * // Set a custom delay effect (e.g., with logging) | |
| * const resolverWithCustomDelay = RequestResolver.setDelayEffect( | |
| * resolver, | |
| * Effect.gen(function*() { | |
| * yield* Effect.log("Waiting before processing batch...") | |
| * yield* Effect.sleep("50 millis") | |
| * }) | |
| * ) | |
| * ``` | |
| * | |
| * @category delay | |
| * @since 4.0.0 | |
| */ | |
| export const setDelayEffect: { | |
| /** | |
| * Sets the batch delay effect for this request resolver. | |
| * | |
| * **Example** (Setting an effectful batch delay) | |
| * | |
| * ```ts | |
| * 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")) | |
| * } | |
| * }) | |
| * ) | |
| * | |
| * // Set a custom delay effect (e.g., with logging) | |
| * const resolverWithCustomDelay = RequestResolver.setDelayEffect( | |
| * resolver, | |
| * Effect.gen(function*() { | |
| * yield* Effect.log("Waiting before processing batch...") | |
| * yield* Effect.sleep("50 millis") | |
| * }) | |
| * ) | |
| * ``` | |
| * | |
| * @category delay | |
| * @since 4.0.0 | |
| */ | |
| (delay: Effect.Effect<void>): <A extends Request.Any>(self: RequestResolver<A>) => RequestResolver<A> | |
| /** | |
| * Sets the batch delay effect for this request resolver. | |
| * | |
| * **Example** (Setting an effectful batch delay) | |
| * | |
| * ```ts | |
| * 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")) | |
| * } | |
| * }) | |
| * ) | |
| * | |
| * // Set a custom delay effect (e.g., with logging) | |
| * const resolverWithCustomDelay = RequestResolver.setDelayEffect( | |
| * resolver, | |
| * Effect.gen(function*() { | |
| * yield* Effect.log("Waiting before processing batch...") | |
| * yield* Effect.sleep("50 millis") | |
| * }) | |
| * ) | |
| * ``` | |
| * | |
| * @category delay | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Any>(self: RequestResolver<A>, delay: Effect.Effect<void>): RequestResolver<A> | |
| } = dual( | |
| 2, | |
| <A extends Request.Any>(self: RequestResolver<A>, delay: Effect.Effect<void>): RequestResolver<A> => | |
| makeWith({ | |
| ...self, | |
| delay | |
| }) | |
| ) | |
| /** | |
| * Sets the batch delay window for this request resolver to the specified duration. | |
| * | |
| * **Example** (Setting a batch delay) | |
| * | |
| * ```ts | |
| * 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") | |
| * | |
| * // Can also use number for milliseconds | |
| * const delayedResolver2 = RequestResolver.setDelay(resolver, 100) | |
| * ``` | |
| * | |
| * @category delay | |
| * @since 4.0.0 | |
| */ | |
| export const setDelay: { | |
| /** | |
| * Sets the batch delay window for this request resolver to the specified duration. | |
| * | |
| * **Example** (Setting a batch delay) | |
| * | |
| * ```ts | |
| * 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") | |
| * | |
| * // Can also use number for milliseconds | |
| * const delayedResolver2 = RequestResolver.setDelay(resolver, 100) | |
| * ``` | |
| * | |
| * @category delay | |
| * @since 4.0.0 | |
| */ | |
| (duration: Duration.Input): <A extends Request.Any>(self: RequestResolver<A>) => RequestResolver<A> | |
| /** | |
| * Sets the batch delay window for this request resolver to the specified duration. | |
| * | |
| * **Example** (Setting a batch delay) | |
| * | |
| * ```ts | |
| * 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") | |
| * | |
| * // Can also use number for milliseconds | |
| * const delayedResolver2 = RequestResolver.setDelay(resolver, 100) | |
| * ``` | |
| * | |
| * @category delay | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Any>(self: RequestResolver<A>, duration: Duration.Input): RequestResolver<A> | |
| } = dual( | |
| 2, | |
| <A extends Request.Any>(self: RequestResolver<A>, duration: Duration.Input): RequestResolver<A> => | |
| makeWith({ | |
| ...self, | |
| delay: Effect.sleep(duration) | |
| }) | |
| ) | |
| /** | |
| * Wraps request resolver execution between `before` and `after` effects. | |
| * | |
| * **Example** (Running effects around request resolution) | |
| * | |
| * ```ts | |
| * 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 setup and cleanup around request execution | |
| * const resolverWithAround = RequestResolver.around( | |
| * resolver, | |
| * (entries) => | |
| * Effect.gen(function*() { | |
| * yield* Effect.log(`Starting batch of ${entries.length} requests`) | |
| * return entries.length | |
| * }), | |
| * (entries, initialSize) => | |
| * Effect.gen(function*() { | |
| * yield* Effect.log( | |
| * `Batch completed with ${entries.length} requests (started with ${initialSize})` | |
| * ) | |
| * }) | |
| * ) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| export const around: { | |
| /** | |
| * Wraps request resolver execution between `before` and `after` effects. | |
| * | |
| * **Example** (Running effects around request resolution) | |
| * | |
| * ```ts | |
| * 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 setup and cleanup around request execution | |
| * const resolverWithAround = RequestResolver.around( | |
| * resolver, | |
| * (entries) => | |
| * Effect.gen(function*() { | |
| * yield* Effect.log(`Starting batch of ${entries.length} requests`) | |
| * return entries.length | |
| * }), | |
| * (entries, initialSize) => | |
| * Effect.gen(function*() { | |
| * yield* Effect.log( | |
| * `Batch completed with ${entries.length} requests (started with ${initialSize})` | |
| * ) | |
| * }) | |
| * ) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| <A extends Request.Any, A2, X>( | |
| before: (entries: NonEmptyArray<Request.Entry<NoInfer<A>>>) => Effect.Effect<A2, Request.Error<A>>, | |
| after: (entries: NonEmptyArray<Request.Entry<NoInfer<A>>>, a: A2) => Effect.Effect<X, Request.Error<A>> | |
| ): (self: RequestResolver<A>) => RequestResolver<A> | |
| /** | |
| * Wraps request resolver execution between `before` and `after` effects. | |
| * | |
| * **Example** (Running effects around request resolution) | |
| * | |
| * ```ts | |
| * 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 setup and cleanup around request execution | |
| * const resolverWithAround = RequestResolver.around( | |
| * resolver, | |
| * (entries) => | |
| * Effect.gen(function*() { | |
| * yield* Effect.log(`Starting batch of ${entries.length} requests`) | |
| * return entries.length | |
| * }), | |
| * (entries, initialSize) => | |
| * Effect.gen(function*() { | |
| * yield* Effect.log( | |
| * `Batch completed with ${entries.length} requests (started with ${initialSize})` | |
| * ) | |
| * }) | |
| * ) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| <A extends Request.Any, A2, X>( | |
| self: RequestResolver<A>, | |
| before: (entries: NonEmptyArray<Request.Entry<NoInfer<A>>>) => Effect.Effect<A2, Request.Error<A>>, | |
| after: (entries: NonEmptyArray<Request.Entry<NoInfer<A>>>, a: A2) => Effect.Effect<X, Request.Error<A>> | |
| ): RequestResolver<A> | |
| } = dual(3, <A extends Request.Any, A2, X>( | |
| self: RequestResolver<A>, | |
| before: (entries: NonEmptyArray<Request.Entry<NoInfer<A>>>) => Effect.Effect<A2, Request.Error<A>>, | |
| after: (entries: NonEmptyArray<Request.Entry<NoInfer<A>>>, a: A2) => Effect.Effect<X, Request.Error<A>> | |
| ): RequestResolver<A> => | |
| makeWith({ | |
| ...self, | |
| runAll: (entries, key) => | |
| Effect.acquireUseRelease( | |
| before(entries), | |
| () => self.runAll(entries, key), | |
| (a) => after(entries, a) | |
| ) | |
| })) | |
| /** | |
| * 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 | |
| * | |
| * @category constructors | |
| * @since 2.0.0 | |
| */ | |
| export const never: RequestResolver<never> = make(() => Effect.never) | |
| /** | |
| * 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. | |
| * | |
| * **Example** (Limiting parallel request batches) | |
| * | |
| * ```ts | |
| * 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(() => { | |
| * console.log(`Processing batch of ${entries.length} requests`) | |
| * 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) | |
| * ) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| export 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. | |
| * | |
| * **Example** (Limiting parallel request batches) | |
| * | |
| * ```ts | |
| * 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(() => { | |
| * console.log(`Processing batch of ${entries.length} requests`) | |
| * 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) | |
| * ) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| (n: number): <A extends Request.Any>(self: RequestResolver<A>) => RequestResolver<A> | |
| /** | |
| * 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. | |
| * | |
| * **Example** (Limiting parallel request batches) | |
| * | |
| * ```ts | |
| * 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(() => { | |
| * console.log(`Processing batch of ${entries.length} requests`) | |
| * 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) | |
| * ) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| <A extends Request.Any>(self: RequestResolver<A>, n: number): RequestResolver<A> | |
| } = dual(2, <A extends Request.Any>(self: RequestResolver<A>, n: number): RequestResolver<A> => | |
| makeWith({ | |
| ...self, | |
| collectWhile: (requests) => requests.size < n | |
| })) | |
| /** | |
| * Transforms a request resolver by grouping requests using the specified key | |
| * function. | |
| * | |
| * **Example** (Grouping resolver requests) | |
| * | |
| * ```ts | |
| * 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 resolver = RequestResolver.make<GetUserRequest>((entries) => | |
| * Effect.sync(() => { | |
| * console.log(`Processing ${entries.length} users`) | |
| * 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 | |
| * ) | |
| * ] | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export const grouped: { | |
| /** | |
| * Transforms a request resolver by grouping requests using the specified key | |
| * function. | |
| * | |
| * **Example** (Grouping resolver requests) | |
| * | |
| * ```ts | |
| * 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 resolver = RequestResolver.make<GetUserRequest>((entries) => | |
| * Effect.sync(() => { | |
| * console.log(`Processing ${entries.length} users`) | |
| * 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 | |
| * ) | |
| * ] | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Any, K>(f: (entry: Request.Entry<A>) => K): (self: RequestResolver<A>) => RequestResolver<A> | |
| /** | |
| * Transforms a request resolver by grouping requests using the specified key | |
| * function. | |
| * | |
| * **Example** (Grouping resolver requests) | |
| * | |
| * ```ts | |
| * 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 resolver = RequestResolver.make<GetUserRequest>((entries) => | |
| * Effect.sync(() => { | |
| * console.log(`Processing ${entries.length} users`) | |
| * 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 | |
| * ) | |
| * ] | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Any, K>(self: RequestResolver<A>, f: (entry: Request.Entry<A>) => K): RequestResolver<A> | |
| } = dual( | |
| 2, | |
| <A extends Request.Any, K>(self: RequestResolver<A>, f: (entry: Request.Entry<A>) => K): RequestResolver<A> => | |
| makeWith({ | |
| ...self, | |
| batchKey: hashGroupKey(f) | |
| }) | |
| ) | |
| /** | |
| * 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 { 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) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| export 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 { 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) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| <A2 extends Request.Any>(that: RequestResolver<A2>): <A extends Request.Any>(self: RequestResolver<A>) => RequestResolver<A2 & A> | |
| /** | |
| * 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 { 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) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 2.0.0 | |
| */ | |
| <A extends Request.Any, A2 extends Request.Any>(self: RequestResolver<A>, that: RequestResolver<A2>): RequestResolver<A & A2> | |
| } = dual(2, <A extends Request.Any, A2 extends Request.Any>( | |
| self: RequestResolver<A>, | |
| that: RequestResolver<A2> | |
| ): RequestResolver<A & A2> => | |
| make( | |
| (requests, key) => effect.race(self.runAll(requests, key), that.runAll(requests, key)) | |
| )) | |
| /** | |
| * 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 { 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) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| export 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 { 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) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Any>( | |
| name: string, | |
| options?: Tracer.SpanOptions | ((entries: NonEmptyArray<Request.Entry<A>>) => Tracer.SpanOptions) | undefined | |
| ): (self: RequestResolver<A>) => RequestResolver<A> | |
| /** | |
| * 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 { 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) | |
| * ``` | |
| * | |
| * @category combinators | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Any>( | |
| self: RequestResolver<A>, | |
| name: string, | |
| options?: Tracer.SpanOptions | ((entries: NonEmptyArray<Request.Entry<A>>) => Tracer.SpanOptions) | undefined | |
| ): RequestResolver<A> | |
| } = dual((args) => isRequestResolver(args[0]), <A extends Request.Any>( | |
| self: RequestResolver<A>, | |
| name: string, | |
| options?: Tracer.SpanOptions | ((entries: NonEmptyArray<Request.Entry<A>>) => Tracer.SpanOptions) | undefined | |
| ): RequestResolver<A> => | |
| makeWith({ | |
| ...self, | |
| runAll: (entries, key) => | |
| Effect.suspend(() => { | |
| const opts = typeof options === "function" ? options(entries) : options | |
| const links = opts?.links ? opts.links.slice() : [] | |
| const seen = new Set<Tracer.AnySpan>() | |
| for (const entry of entries) { | |
| const span = Context.getOption(entry.context, Tracer.ParentSpan) | |
| if (span._tag === "None" || seen.has(span.value)) continue | |
| seen.add(span.value) | |
| links.push({ span: span.value, attributes: {} }) | |
| } | |
| return Effect.withSpan(self.runAll(entries, key), name, { | |
| ...options, | |
| links, | |
| attributes: { | |
| batchSize: entries.length, | |
| ...opts?.attributes | |
| } | |
| }) | |
| }) | |
| })) | |
| /** | |
| * 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 | |
| * | |
| * @category caching | |
| * @since 4.0.0 | |
| */ | |
| export 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 | |
| * | |
| * @category caching | |
| * @since 4.0.0 | |
| */ | |
| < | |
| A extends Request.Any, | |
| ServiceMode extends "lookup" | "construction" = never | |
| >( | |
| options: { | |
| readonly capacity: number | |
| readonly timeToLive?: ((exit: Request.Result<A>, request: A) => Duration.Input) | undefined | |
| readonly requireServicesAt?: ServiceMode | undefined | |
| } | |
| ): (self: RequestResolver<A>) => Effect.Effect< | |
| Cache.Cache< | |
| A, | |
| Request.Success<A>, | |
| Request.Error<A>, | |
| "construction" extends ServiceMode ? never : Request.Services<A> | |
| >, | |
| never, | |
| "construction" extends ServiceMode ? Request.Services<A> : 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 | |
| * | |
| * @category caching | |
| * @since 4.0.0 | |
| */ | |
| < | |
| A extends Request.Any, | |
| ServiceMode extends "lookup" | "construction" = never | |
| >( | |
| self: RequestResolver<A>, | |
| options: { | |
| readonly capacity: number | |
| readonly timeToLive?: ((exit: Request.Result<A>, request: A) => Duration.Input) | undefined | |
| readonly requireServicesAt?: ServiceMode | undefined | |
| } | |
| ): Effect.Effect< | |
| Cache.Cache< | |
| A, | |
| Request.Success<A>, | |
| Request.Error<A>, | |
| "construction" extends ServiceMode ? never : Request.Services<A> | |
| >, | |
| never, | |
| "construction" extends ServiceMode ? Request.Services<A> : never | |
| > | |
| } = dual(2, < | |
| A extends Request.Any, | |
| ServiceMode extends "lookup" | "construction" = never | |
| >(self: RequestResolver<A>, options: { | |
| readonly capacity: number | |
| readonly timeToLive?: ((exit: Request.Result<A>, request: A) => Duration.Input) | undefined | |
| readonly requireServicesAt?: ServiceMode | undefined | |
| }): Effect.Effect< | |
| Cache.Cache< | |
| A, | |
| Request.Success<A>, | |
| Request.Error<A>, | |
| "construction" extends ServiceMode ? never : Request.Services<A> | |
| >, | |
| never, | |
| "construction" extends ServiceMode ? Request.Services<A> : never | |
| > => | |
| Cache.makeWith((req: A) => internal.request(req, self), { | |
| capacity: options.capacity, | |
| timeToLive: options.timeToLive as any, | |
| requireServicesAt: options.requireServicesAt ?? "lookup" as ServiceMode | |
| }) as any) | |
| /** | |
| * 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 {@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 | |
| * | |
| * @category caching | |
| * @since 4.0.0 | |
| */ | |
| export 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 are cached the same as | |
| * successes. 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 | |
| * | |
| * @category caching | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Any>( | |
| options: { | |
| readonly capacity: number | |
| readonly strategy?: "lru" | "fifo" | undefined | |
| } | |
| ): (self: RequestResolver<A>) => Effect.Effect<RequestResolver<A>> | |
| /** | |
| * 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 {@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 | |
| * | |
| * @category caching | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Any>( | |
| self: RequestResolver<A>, | |
| options: { | |
| readonly capacity: number | |
| readonly strategy?: "lru" | "fifo" | undefined | |
| } | |
| ): Effect.Effect<RequestResolver<A>> | |
| } = dual(2, <A extends Request.Any>(self: RequestResolver<A>, options: { | |
| readonly capacity: number | |
| readonly strategy?: "lru" | "fifo" | undefined | |
| }): Effect.Effect<RequestResolver<A>> => | |
| Effect.sync(() => { | |
| const strategy = options.strategy ?? "lru" | |
| const cache = MutableHashMap.empty</** | |
| * 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 {@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 | |
| * | |
| * @category caching | |
| * @since 4.0.0 | |
| */ | |
| A, /** | |
| * 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 {@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 | |
| * | |
| * @category caching | |
| * @since 4.0.0 | |
| */ | |
| { | |
| readonly entry: Request.Entry<A>; | |
| exit: Request.Result<A> | undefined; | |
| }>() | |
| return makeWith({ | |
| ...self, | |
| runAll(entries, key) { | |
| return Effect.onExit(self.runAll(entries, key), () => { | |
| let toRemove = MutableHashMap.size(cache) - options.capacity | |
| if (toRemove <= 0) return Effect.void | |
| for (const k of MutableHashMap.keys(cache)) { | |
| MutableHashMap.remove(cache, k) | |
| toRemove-- | |
| if (toRemove <= 0) break | |
| } | |
| return Effect.void | |
| }) | |
| }, | |
| preCheck(entry) { | |
| const ocached = MutableHashMap.get(cache, entry.request) | |
| if (ocached._tag === "None") { | |
| const cached = { entry, exit: undefined as Request.Result<A> | undefined } | |
| MutableHashMap.set(cache, entry.request, cached) | |
| const prevComplete = entry.completeUnsafe | |
| entry.completeUnsafe = function(exit) { | |
| cached.exit = exit as any | |
| prevComplete(exit) | |
| } | |
| return true | |
| } | |
| const cached = ocached.value | |
| if (cached.exit) { | |
| if (strategy === "lru") { | |
| MutableHashMap.remove(cache, cached.entry.request) | |
| MutableHashMap.set(cache, cached.entry.request, cached) | |
| } | |
| entry.completeUnsafe(cached.exit as any) | |
| } else { | |
| cached.entry.uninterruptible = true | |
| const prevComplete = cached.entry.completeUnsafe | |
| cached.entry.completeUnsafe = function(exit) { | |
| prevComplete(exit) | |
| entry.completeUnsafe(exit) | |
| } | |
| } | |
| return false | |
| } | |
| }) | |
| })) | |
| /** | |
| * 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 | |
| * | |
| * @category Persistence | |
| * @since 4.0.0 | |
| */ | |
| export 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 | |
| * | |
| * @category Persistence | |
| * @since 4.0.0 | |
| */ | |
| <A extends Request.Request<any, Persistence.PersistenceError | Schema.SchemaError, any> & Persistable.Any>( | |
| options: { | |
| readonly storeId: string | |
| readonly timeToLive?: ((exit: Request.Result<A>, request: A) => Duration.Input) | undefined | |
| readonly staleWhileRevalidate?: ((exit: Request.Result<A>, request: A) => boolean) | undefined | |
| } | |
| ): (self: RequestResolver<A>) => Effect.Effect< | |
| RequestResolver<A>, | |
| 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 | |
| * | |
| * @category Persistence | |
| * @since 4.0.0 | |
| */ | |
| < | |
| A extends Request.Request<any, Persistence.PersistenceError | Schema.SchemaError, any> & Persistable.Any | |
| >( | |
| self: RequestResolver<A>, | |
| options: { | |
| readonly storeId: string | |
| readonly timeToLive?: ((exit: Request.Result<A>, request: A) => Duration.Input) | undefined | |
| readonly staleWhileRevalidate?: ((exit: Request.Result<A>, request: A) => boolean) | undefined | |
| } | |
| ): Effect.Effect< | |
| RequestResolver<A>, | |
| never, | |
| Persistence.Persistence | Scope | |
| > | |
| } = dual( | |
| 2, | |
| Effect.fnUntraced(function*< | |
| A extends Request.Request<any, Persistence.PersistenceError | Schema.SchemaError, any> & Persistable.Any | |
| >( | |
| self: RequestResolver<A>, | |
| options: { | |
| readonly storeId: string | |
| readonly timeToLive?: ((exit: Request.Result<A>, request: A) => Duration.Input) | undefined | |
| readonly staleWhileRevalidate?: ((exit: Request.Result<A>, request: A) => boolean) | undefined | |
| } | |
| ) { | |
| const store = yield* (yield* Persistence.Persistence).make(options as any) | |
| return makeWith</** | |
| * 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 | |
| * | |
| * @category Persistence | |
| * @since 4.0.0 | |
| */ | |
| A>({ | |
| ...self, | |
| runAll: Effect.fnUntraced(function*(entries, key) { | |
| const results = yield* (store.getMany(Iterable.map(entries, (_) => _.request)).pipe( | |
| Effect.provideContext(entries[0].context) | |
| ) as Effect.Effect< | |
| Array<Exit.Exit<unknown, unknown> | undefined>, | |
| Request.Error<A> | |
| >) | |
| const leftover: Array<Request.Entry<A>> = [] | |
| const toPersist = new Map<A, Request.Result<A>>() | |
| for (let i = 0; i < results.length; i++) { | |
| const entry = entries[i] | |
| const exit = results[i] | |
| if ( | |
| exit === undefined || | |
| (options.staleWhileRevalidate && options.staleWhileRevalidate(exit as any, entry.request)) | |
| ) { | |
| const prevComplete = entry.completeUnsafe | |
| entry.completeUnsafe = function(exit) { | |
| toPersist.set(entry.request, exit as any) | |
| prevComplete(exit) | |
| } | |
| leftover.push(entry) | |
| if (exit === undefined) continue | |
| } | |
| entry.completeUnsafe(exit as any) | |
| } | |
| if (!Arr.isArrayNonEmpty(leftover)) { | |
| return | |
| } | |
| yield* Effect.catchCause(self.runAll(leftover, key), (cause) => { | |
| for (let i = 0; i < leftover.length; i++) { | |
| const entry = leftover[i] | |
| if (!toPersist.has(entry.request)) continue | |
| entry.completeUnsafe(Exit.failCause(cause) as any) | |
| } | |
| return Effect.void | |
| }) | |
| yield* (store.setMany(toPersist).pipe( | |
| Effect.provideContext(entries[0].context) | |
| ) as Effect.Effect<void, Request.Error<A>>) | |
| }) | |
| }); | |
| }) | |
| ) | |
Xet Storage Details
- Size:
- 67.7 kB
- Xet hash:
- 0d954501b3b9098e1adf1d5ba4ab7d559bb03cb0db8d5042b522dc8ebd498d2d
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.