RcMap
Shares scoped resources by key and releases them when no one is using them.
An RcMap runs a lookup effect the first time a key is requested, shares the
in-progress or acquired resource with other callers for the same key, and
tracks each caller through its current Scope. When the last scope for a key
closes, the resource can be released, kept alive for an idle time, or removed
by capacity limits or explicit invalidation. It is meant for resource
lifecycles such as clients, sessions, and connections, not as a general
mutable cache.
Combinators
Gets the resource for a key, acquiring it with the map's lookup function when the key is not already cached.
When to use
Use to acquire or retain the resource for a key within the current scope.
Details
The resource's reference count is incremented for the current Scope, and a
release finalizer is added to that scope. When the current scope closes, the
reference is released; the resource is closed when the last reference is
released, subject to the map's idle time-to-live setting.
See
- make for creating the reference-counted map
- invalidate for removing a resource by key
Signature
declare const get: { <K>(key: K): <A, E>(self: RcMap<K, A, E>) => Effect<A, E, Scope>; <K, A, E>(self: RcMap<K, A, E>, key: K): Effect<A, E, Scope>;}Example
(Acquiring a resource)
import { Effect, RcMap } from "effect"
const events: Array<string> = []
const program = Effect.gen(function*() { const map = yield* RcMap.make({ lookup: (key: string) => Effect.acquireRelease( Effect.succeed(`Resource: ${key}`), () => Effect.sync(() => events.push(`released ${key}`)) ) })
// Get a resource - it will be acquired on first access const resource = yield* RcMap.get(map, "database") return [resource, events] as const})
await Effect.runPromise(Effect.scoped(program)) // => ["Resource: database", ["released database"]]Retains and returns an existing resource without invoking the map's lookup function when the key is missing.
When to use
Use when you only want to acquire a reference to a resource that is currently cached.
Details
Returns Option.none when the key is not currently stored or the map is
closed. If an entry exists, its reference count is incremented for the current
Scope before awaiting its result. A successful entry returns
Option.some(value), while an in-flight or cached failure fails with the same
error as get.
See
Signature
declare const getOption: { <K>(key: K): <A, E>(self: RcMap<K, A, E>) => Effect<Option<A>, E, Scope>; <K, A, E>(self: RcMap<K, A, E>, key: K): Effect<Option<A>, E, Scope>;}Example
(Retaining only cached resources)
import { Effect, Option, RcMap } from "effect"
const program = Effect.gen(function*() { const map = yield* RcMap.make({ lookup: (key: string) => Effect.succeed(`Resource: ${key}`), idleTimeToLive: "1 minute" })
const missing = yield* RcMap.getOption(map, "database") yield* Effect.scoped(RcMap.get(map, "database")) const cached = yield* Effect.scoped(RcMap.getOption("database")(map))
return [missing, cached] as const})
await Effect.runPromise(Effect.scoped(program)) // => [Option.none(), Option.some("Resource: database")]Returns whether the RcMap currently contains an entry for the specified
key.
When to use
Use to check whether a key is already present in an RcMap without running
the lookup function or acquiring a missing resource.
Details
This operation only checks the current map state.
Gotchas
Closed maps return false, so false does not distinguish a missing key
from a closed map.
See
Signature
declare const has: { <K>(key: K): <A, E>(self: RcMap<K, A, E>) => Effect<boolean>; <K, A, E>(self: RcMap<K, A, E>, key: K): Effect<boolean>;}invalidate
Invalidates and removes a specific key from the RcMap. If the resource is not currently in use (reference count is 0), it will be immediately released.
When to use
Use to remove a resource by key so the next access performs a fresh lookup.
See
Signature
declare const invalidate: { <K>(key: K): <A, E>(self: RcMap<K, A, E>) => Effect<void>; <K, A, E>(self: RcMap<K, A, E>, key: K): Effect<void>;}Example
(Invalidating a resource)
import { Effect, RcMap } from "effect"
const events: Array<string> = []
const program = Effect.gen(function*() { const map = yield* RcMap.make({ lookup: (key: string) => Effect.acquireRelease( Effect.succeed(`Resource: ${key}`), () => Effect.sync(() => events.push(`released ${key}`)) ) })
// Get a resource yield* RcMap.get(map, "cache")
// Invalidate the resource - it will be removed from the map // and released if no longer in use yield* RcMap.invalidate(map, "cache")
// Next access will create a new resource yield* RcMap.get(map, "cache")})
await Effect.runPromise(Effect.scoped(program))events // => ["released cache", "released cache"]Returns an iterable of all keys currently stored in the RcMap.
When to use
Use to inspect which keys currently have stored resources in an RcMap.
Details
If the RcMap has been closed, the effect is interrupted.
See
- has for checking one key without enumerating all keys
Signature
declare function keys<K, A, E>(self: RcMap<K, A, E>): Effect<Iterable<K, any, any>>Example
(Listing keys)
import { Effect, RcMap } from "effect"
const program = Effect.gen(function*() { const map = yield* RcMap.make({ lookup: (key: string) => Effect.succeed(`value-${key}`) })
// Add some resources to the map yield* RcMap.get(map, "foo") yield* RcMap.get(map, "bar") yield* RcMap.get(map, "baz")
// Get all keys currently in the map const allKeys = yield* RcMap.keys(map) return Array.from(allKeys)})
await Effect.runPromise(Effect.scoped(program)) // => ["foo", "bar", "baz"]Extends the idle time for a resource in the RcMap. If the RcMap has an
idleTimeToLive configured, calling touch will reset the expiration
timer for the specified key.
When to use
Use to keep an idle resource alive longer without acquiring a new reference.
See
- invalidate for removing the resource instead of extending it
Signature
declare const touch: { <K>(key: K): <A, E>(self: RcMap<K, A, E>) => Effect<void>; <K, A, E>(self: RcMap<K, A, E>, key: K): Effect<void>;}Example
(Extending resource idle time)
import { Effect, RcMap } from "effect"
const events: Array<string> = []
const program = Effect.gen(function*() { const map = yield* RcMap.make({ lookup: (key: string) => Effect.acquireRelease( Effect.succeed(`Resource: ${key}`), () => Effect.sync(() => events.push(`released ${key}`)) ), idleTimeToLive: "10 seconds" })
// Get a resource yield* RcMap.get(map, "session")
// Touch the resource to extend its idle time // This resets the 10-second expiration timer yield* RcMap.touch(map, "session")
// The resource will now live for another 10 seconds // from the time it was touched})
await Effect.runPromise(Effect.scoped(program))events // => ["released session"]Constructors
Creates an RcMap that can contain multiple reference counted resources that can be indexed
by a key. The resources are lazily acquired on the first call to get and
released when the last reference is released.
When to use
Use to create a scoped reference-counted map for resources that should be acquired once per key and shared while in use.
Details
Complex keys can extend Equal and Hash to allow lookups by value.
capacity: The maximum number of resources that can be held in the map.idleTimeToLive: When the reference count reaches zero, the resource will be released after this duration.
See
- get for acquiring or retaining a resource by key
- invalidate for removing a resource from the map
Signature
declare const make: { <K, A, E, R>(options: { readonly capacity?: undefined; readonly idleTimeToLive?: Duration.Input | (key: K) => Duration.Input; readonly lookup: (key: K) => Effect.Effect<A, E, R>; }): Effect<RcMap<K, A, E>, never, Scope | R>; <K, A, E, R>(options: { readonly capacity: number; readonly idleTimeToLive?: Duration.Input | (key: K) => Duration.Input; readonly lookup: (key: K) => Effect.Effect<A, E, R>; }): Effect<RcMap<K, A, ExceededCapacityError | E>, never, Scope | R>;}Example
(Creating a reference-counted map)
import { Effect, RcMap } from "effect"
const events: Array<string> = []
const program = Effect.gen(function*() { const map = yield* RcMap.make({ lookup: (key: string) => Effect.acquireRelease( Effect.succeed(`acquired ${key}`), () => Effect.sync(() => events.push(`released ${key}`)) ) })
// Get "foo" from the map twice, which will only acquire it once. // It will then be released once the scope closes. yield* RcMap.get(map, "foo").pipe( Effect.andThen(RcMap.get(map, "foo")), Effect.scoped )})
await Effect.runPromise(Effect.scoped(program))events // => ["released foo"]Models
An RcMap is a reference-counted map data structure that manages the lifecycle
of resources indexed by keys. Resources are lazily acquired and automatically
released when no longer in use.
When to use
Use to share scoped resources by key while automatically releasing them after their last active reference is gone.
See
Signature
interface RcMap<in out K, in out A, in out E = never> extends Pipeable { readonly "~effect/RcMap": "~effect/RcMap"; readonly capacity: number; readonly context: Context<never>; readonly idleTimeToLive: (key: K) => Duration; readonly lookup: (key: K) => Effect<A, E, Scope>; readonly scope: Scope; state: State<K, A, E>;}Example
(Inspecting a reference-counted map)
import { Effect, RcMap } from "effect"
const program = Effect.gen(function*() { // Create an RcMap that manages database connections const dbConnectionMap = yield* RcMap.make({ lookup: (dbName: string) => Effect.acquireRelease(Effect.succeed(`Connection to ${dbName}`), () => Effect.void), capacity: 10, idleTimeToLive: "5 minutes" })
// The RcMap interface provides access to: // - lookup: Function to acquire resources // - capacity: Maximum number of resources // - idleTimeToLive: Time before idle resources are released // - state: Current state of the map
return dbConnectionMap.capacity})
await Effect.runPromise(Effect.scoped(program)) // => 10Represents the internal state of an RcMap, which can be either Open (active) or Closed (shutdown and no longer accepting operations).
When to use
Use when typing code that inspects an RcMap's state field and narrows
between open and closed lifecycle states.
See
- RcMap for the map value that exposes this state
- State.Open for the active state with entries
- State.Closed for the shutdown state
Signature
type State<K, A, E> = State.Open<K, A, E> | State.Closed