RequestResolver.ts
RequestResolver.ts overview
Section titled “RequestResolver.ts overview”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 v2.0.0
Exports Grouped by Category
Section titled “Exports Grouped by Category”Persistence
Section titled “Persistence”persisted
Section titled “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
withCachefor in-memory resolver caching that does not require persistable request values or a persistence storeasCachefor exposing resolver results through aCacheinstead of returning another resolver
Signature
declare const persisted: { <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> <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>}Since v4.0.0
caching
Section titled “caching”asCache
Section titled “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
withCachefor keeping caching behind a resolver used withEffect.requestpersistedfor storing persistable request results outside process memoryCache.Cachefor operations available on the returned cache
Signature
declare const asCache: { <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 > <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 >}Since v4.0.0
withCache
Section titled “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
asCachefor exposing the resolver as aCachewith time-to-live and service lookup controlspersistedfor backing persistable requests with the configured persistence store
Signature
declare const withCache: { <A extends Request.Any>(options: { readonly capacity: number readonly strategy?: "lru" | "fifo" | undefined }): (self: RequestResolver<A>) => Effect.Effect<RequestResolver<A>> <A extends Request.Any>( self: RequestResolver<A>, options: { readonly capacity: number; readonly strategy?: "lru" | "fifo" | undefined } ): Effect.Effect<RequestResolver<A>>}Since v4.0.0
combinators
Section titled “combinators”around
Section titled “around”Wraps request resolver execution between before and after effects.
Example (Running effects around request resolution)
import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> { readonly _tag: "GetDataRequest"}const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
const resolver = RequestResolver.make<GetDataRequest>((entries) => Effect.sync(() => { for (const entry of entries) { entry.completeUnsafe(Exit.succeed("data")) } }))
// Add setup and cleanup around request executionconst 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})`) }))Signature
declare const around: { <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> <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>}Since v2.0.0
batchN
Section titled “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)
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 requestsconst limitedResolver = RequestResolver.batchN(resolver, 5)
// When more than 5 requests are made, they'll be split into multiple batchesconst requests = Array.from({ length: 12 }, (_, i) => Effect.request(GetDataRequest({ id: i }), limitedResolver))Signature
declare const batchN: { (n: number): <A extends Request.Any>(self: RequestResolver<A>) => RequestResolver<A> <A extends Request.Any>(self: RequestResolver<A>, n: number): RequestResolver<A>}Since v2.0.0
grouped
Section titled “grouped”Transforms a request resolver by grouping requests using the specified key function.
Example (Grouping resolver requests)
import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetUserRequest extends Request.Request<string> { readonly _tag: "GetUserRequest" readonly userId: number readonly department: string}const GetUserRequest = Request.tagged<GetUserRequest>("GetUserRequest")
const 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 processingconst groupedResolver = RequestResolver.grouped(resolver, ({ request }) => request.department)
// Requests for the same department will be batched togetherconst 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)]Signature
declare const grouped: { <A extends Request.Any, K>(f: (entry: Request.Entry<A>) => K): (self: RequestResolver<A>) => RequestResolver<A> <A extends Request.Any, K>(self: RequestResolver<A>, f: (entry: Request.Entry<A>) => K): RequestResolver<A>}Since v4.0.0
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)
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 firstconst racingResolver = RequestResolver.race(fastResolver, slowResolver)Signature
declare const race: { <A2 extends Request.Any>( that: RequestResolver<A2> ): <A extends Request.Any>(self: RequestResolver<A>) => RequestResolver<A2 & A> <A extends Request.Any, A2 extends Request.Any>( self: RequestResolver<A>, that: RequestResolver<A2> ): RequestResolver<A & A2>}Since v2.0.0
withSpan
Section titled “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)
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 attributesconst tracedResolver = RequestResolver.withSpan(resolver, "user-data-resolver", { attributes: { "resolver.type": "user-data", "resolver.version": "1.0" }})
// Spans will automatically include batch size and request linksconst effect = Effect.request(GetDataRequest({ id: 123 }), tracedResolver)Signature
declare const withSpan: { <A extends Request.Any>( name: string, options?: Tracer.SpanOptions | ((entries: NonEmptyArray<Request.Entry<A>>) => Tracer.SpanOptions) | undefined ): (self: RequestResolver<A>) => RequestResolver<A> <A extends Request.Any>( self: RequestResolver<A>, name: string, options?: Tracer.SpanOptions | ((entries: NonEmptyArray<Request.Entry<A>>) => Tracer.SpanOptions) | undefined ): RequestResolver<A>}Since v4.0.0
constructors
Section titled “constructors”fromEffect
Section titled “fromEffect”Constructs a request resolver from an effectual function.
Example (Creating a resolver from an effectful function)
import { Effect, Request, RequestResolver } from "effect"
interface GetUserFromAPIRequest extends Request.Request<string> { readonly _tag: "GetUserFromAPIRequest" readonly id: number}const GetUserFromAPIRequest = Request.tagged<GetUserFromAPIRequest>("GetUserFromAPIRequest")
// Create a resolver that uses effects (like HTTP calls)const UserAPIResolver = RequestResolver.fromEffect<GetUserFromAPIRequest>((entry) => Effect.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` }))
// Usageconst getUserEffect = Effect.request(GetUserFromAPIRequest({ id: 123 }), UserAPIResolver)Signature
declare const fromEffect: <A extends Request.Any>( f: (entry: Request.Entry<A>) => Effect.Effect<Request.Success<A>, Request.Error<A>>) => RequestResolver<A>Since v2.0.0
fromEffectTagged
Section titled “fromEffectTagged”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)
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 typesconst 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}`))})Signature
declare 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>Since v2.0.0
fromFunction
Section titled “fromFunction”Constructs a request resolver from a pure function.
Example (Creating a resolver from a pure function)
import { Effect, Request, RequestResolver } from "effect"
interface GetSquareRequest extends Request.Request<number> { readonly _tag: "GetSquareRequest" readonly value: number}const GetSquareRequest = Request.tagged<GetSquareRequest>("GetSquareRequest")
// Create a resolver from a pure functionconst SquareResolver = RequestResolver.fromFunction<GetSquareRequest>( (entry) => entry.request.value * entry.request.value)
// Usageconst getSquareEffect = Effect.request(GetSquareRequest({ value: 5 }), SquareResolver)// Will resolve to 25Signature
declare const fromFunction: <A extends Request.Any>( f: (entry: Request.Entry<A>) => Request.Success<A>) => RequestResolver<A>Since v2.0.0
fromFunctionBatched
Section titled “fromFunctionBatched”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)
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 batchconst DoubleResolver = RequestResolver.fromFunctionBatched<GetDoubleRequest>((entries) => entries.map((entry) => entry.request.value * 2))
// Usage with multiple requestsconst effects = [1, 2, 3].map((value) => Effect.request(GetDoubleRequest({ value }), DoubleResolver))const batchedEffect = Effect.all(effects) // [2, 4, 6]Signature
declare const fromFunctionBatched: <A extends Request.Any>( f: (entries: NonEmptyArray<Request.Entry<A>>) => Iterable<Request.Success<A>>) => RequestResolver<A>Since v2.0.0
Constructs a request resolver with the specified method to run requests.
Example (Creating a request resolver)
import { Effect, Exit, Request, RequestResolver } from "effect"
// Define a request typeinterface GetUserRequest extends Request.Request<string, Error> { readonly _tag: "GetUserRequest" readonly id: number}const GetUserRequest = Request.tagged<GetUserRequest>("GetUserRequest")
// Create a resolver that handles the requestsconst 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 requestsconst getUserEffect = Effect.request(GetUserRequest({ id: 123 }), UserResolver)Signature
declare const make: <A extends Request.Any>( runAll: (entries: NonEmptyArray<Request.Entry<A>>, key: unknown) => Effect.Effect<void, Request.Error<A>>) => RequestResolver<A>Since v2.0.0
makeGrouped
Section titled “makeGrouped”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)
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 processingconst 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}`)) } })})Signature
declare 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>Since v4.0.0
makeWith
Section titled “makeWith”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
makefor constructing a resolver from a batch runnermakeGroupedfor constructing a resolver that groups requests by key
Signature
declare 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>Since v4.0.0
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
makefor constructing a resolver that executes batches and completes request entries
Signature
declare const never: RequestResolver<never>Since v2.0.0
setDelay
Section titled “setDelay”Sets the batch delay window for this request resolver to the specified duration.
Example (Setting a batch delay)
import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> { readonly _tag: "GetDataRequest"}const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
const resolver = RequestResolver.make<GetDataRequest>((entries) => Effect.sync(() => { for (const entry of entries) { entry.completeUnsafe(Exit.succeed("data")) } }))
// Add a 100ms delay to batch requests togetherconst delayedResolver = RequestResolver.setDelay(resolver, "100 millis")
// Can also use number for millisecondsconst delayedResolver2 = RequestResolver.setDelay(resolver, 100)Signature
declare const setDelay: { (duration: Duration.Input): <A extends Request.Any>(self: RequestResolver<A>) => RequestResolver<A> <A extends Request.Any>(self: RequestResolver<A>, duration: Duration.Input): RequestResolver<A>}Since v4.0.0
setDelayEffect
Section titled “setDelayEffect”Sets the batch delay effect for this request resolver.
Example (Setting an effectful batch delay)
import { Effect, Exit, Request, RequestResolver } from "effect"
interface GetDataRequest extends Request.Request<string> { readonly _tag: "GetDataRequest"}const GetDataRequest = Request.tagged<GetDataRequest>("GetDataRequest")
const resolver = RequestResolver.make<GetDataRequest>((entries) => Effect.sync(() => { for (const entry of entries) { entry.completeUnsafe(Exit.succeed("data")) } }))
// 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") }))Signature
declare const setDelayEffect: { (delay: Effect.Effect<void>): <A extends Request.Any>(self: RequestResolver<A>) => RequestResolver<A> <A extends Request.Any>(self: RequestResolver<A>, delay: Effect.Effect<void>): RequestResolver<A>}Since v4.0.0
guards
Section titled “guards”isRequestResolver
Section titled “isRequestResolver”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
RequestResolverfor the type narrowed by this guard
Signature
declare const isRequestResolver: (u: unknown) => u is RequestResolver<any>Since v2.0.0
models
Section titled “models”RequestResolver (interface)
Section titled “RequestResolver (interface)”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)
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() insteadconst resolver = RequestResolver.make<GetUserRequest>((entries) => Effect.sync(() => { for (const entry of entries) { entry.completeUnsafe(Exit.succeed(`User ${entry.request.id}`)) } }))Signature
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>>}Since v2.0.0
RequestResolver (namespace)
Section titled “RequestResolver (namespace)”Namespace containing type-level helpers associated with RequestResolver.
Since v2.0.0
Variance (interface)
Section titled “Variance (interface)”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.
Signature
export interface Variance<in A> { readonly [TypeId]: { readonly _A: Types.Contravariant<A> }}Since v2.0.0