# LayerMap

Ordinary layers build a *fixed* set of services. But some applications need a
service instance *per key* that isn't known ahead of time: a database client per
tenant, a connection per region, an API client per account. `LayerMap` is a
scoped, reference-counted cache that builds a layer on demand for each key,
reuses it while it's live, and finalizes it when it's no longer needed.

```ts
import { Context, Effect, Layer, LayerMap } from "effect"

// The per-key service we want a distinct instance of.
class Greeter extends Context.Service<Greeter, {
  readonly greet: Effect.Effect<string>
}>()("myapp/Greeter") {}

// A LayerMap.Service wraps the cache and exposes accessor helpers on the class.
// `lookup` is called once per distinct key to produce that key's layer.
class GreeterMap extends LayerMap.Service<GreeterMap>()("myapp/GreeterMap", {
  lookup: (name: string) =>
    Layer.succeed(Greeter, {
      greet: Effect.succeed(`Hello, ${name}!`)
    }),

  // Entries unused for this long are released automatically.
  idleTimeToLive: "5 seconds"
}) {}
```

To use a keyed resource, provide the layer for a specific key with
`GreeterMap.get(key)`. That layer makes the `Greeter` service available, built
from the cache:

```ts
import { Console, Effect } from "effect"
import { Greeter } from "./Greeter.ts"
import { GreeterMap } from "./GreeterMap.ts"

const program = Effect.gen(function*() {
  const greeter = yield* Greeter
  yield* Console.log(yield* greeter.greet)
}).pipe(
  // `get("John")` provides a Greeter built for the key "John". Asking again for
  // "John" reuses the cached instance; a different key builds a new one.
  Effect.provide(GreeterMap.get("John")),
  // The map itself is a layer too — provide it once at the top.
  Effect.provide(GreeterMap.layer)
)

Effect.runFork(program)
```

## How it works

`LayerMap.Service<Self>()("id", options)` produces a class that is both a normal
service (so it has a `.layer`) and a façade over the underlying cache. The
accessor helpers it generates:

- **`GreeterMap.get(key)`** — a `Layer` that provides the key's services,
  building them on first use and reusing the cached instance afterwards.
- **`GreeterMap.contextEffect(key)`** — the acquired `Context` directly, for when
  you want the services as a value rather than a layer (requires a `Scope`).
- **`GreeterMap.invalidate(key)`** — finalize the current cached entry for a key;
  the next access rebuilds it.

The cache is reference-counted: while anything is using a key's resources they
stay live, and they're finalized once nobody holds them and `idleTimeToLive`
elapses.
**Note:** Because entries own resources, a `LayerMap` is scoped. The `GreeterMap.layer`
manages that scope for you — provide it once, near the root of your application.
**LayerMap vs a plain Cache:** A [`Cache`](https://effect.plants.sh/caching/) memoizes *values* by key with size/TTL eviction, but it
does not own resource lifecycles — an evicted value is just dropped. A `LayerMap`
caches *services built from layers*, tracks how many fibers reference each key,
and runs the layer's finalizers (closing connections, releasing handles) when
the last reference goes away and the idle window passes. Reach for `LayerMap`
when each key owns a resource that must be acquired and released, not merely
computed.

## A realistic example: per-tenant database

The lookup function returns a full layer, so each key can have its own
configured resource — including one with dependencies and cleanup:

```ts
import { Context, Effect, Layer, LayerMap } from "effect"

class TenantDb extends Context.Service<TenantDb, {
  query(sql: string): Effect.Effect<ReadonlyArray<unknown>>
}>()("myapp/TenantDb") {}

class TenantDbMap extends LayerMap.Service<TenantDbMap>()("myapp/TenantDbMap", {
  // Build a distinct, resource-owning layer per tenant id.
  lookup: (tenantId: string) =>
    Layer.effect(
      TenantDb,
      Effect.gen(function*() {
        // Each tenant gets its own connection, released when the entry expires.
        const conn = yield* Effect.acquireRelease(
          Effect.sync(() => ({ tenantId, close: () => {} })),
          (c) => Effect.sync(() => c.close())
        )

        return TenantDb.of({
          query: (sql) => Effect.succeed([{ tenantId: conn.tenantId, sql }])
        })
      })
    ),
  idleTimeToLive: "30 seconds"
}) {}

// Handle a request scoped to one tenant.
const handle = (tenantId: string) =>
  Effect.gen(function*() {
    const db = yield* TenantDb
    return yield* db.query("SELECT * FROM orders")
  }).pipe(
    Effect.provide(TenantDbMap.get(tenantId)),
    Effect.provide(TenantDbMap.layer)
  )
```

## Fixed sets of keys

When the keys are known and finite, pass a `layers` record instead of a
`lookup` function. The key type becomes the union of record keys:

```ts
import { Context, Effect, Layer, LayerMap } from "effect"
import { Greeter } from "./Greeter.ts"

class RegionGreeters extends LayerMap.Service<RegionGreeters>()("myapp/RegionGreeters", {
  layers: {
    us: Layer.succeed(Greeter, { greet: Effect.succeed("Hi from US") }),
    eu: Layer.succeed(Greeter, { greet: Effect.succeed("Hallo from EU") })
  }
}) {}

// RegionGreeters.get("us") | "eu" — anything else is a compile error.
```

## Dependencies, idle eviction, and preloading

The service options accept a few more fields that shape how the map is built and
how its entries live and die.

- **`dependencies`** — layers the lookup/record layers need. They are provided to
  `.layer` (but not `.layerNoDeps`), so the resulting `LayerMap` layer has no
  outstanding requirements.
- **`idleTimeToLive`** — how long an entry survives after its reference count hits
  zero before being finalized. Omit it (the default is `0`) and an entry is
  released the instant its last reference is gone. Pass a `Duration.Input` for a
  fixed window, or a function `(key) => Duration.Input` to vary it per key.
- **`preloadKeys`** (lookup form) / **`preload: true`** (record form) — eagerly
  build the listed entries when the map is created. This moves layer construction
  errors to `LayerMap` creation time instead of first use, which is why preloading
  adds the layers' error type to `.layer`'s error channel.

```ts
import { Context, Effect, Layer, LayerMap } from "effect"

class Config extends Context.Service<Config, { readonly url: string }>()(
  "myapp/Config"
) {}

class ClientMap extends LayerMap.Service<ClientMap>()("myapp/ClientMap", {
  lookup: (region: "us" | "eu") =>
    Layer.succeed(Config, { url: `https://${region}.example.com` }),

  // Provided to `ClientMap.layer`, so the layer requires nothing extra.
  dependencies: [],

  // "us" lives 1 minute idle; "eu" lives 5 minutes idle.
  idleTimeToLive: (region) => (region === "us" ? "1 minute" : "5 minutes"),

  // Build both entries up front; failures surface here, not on first get.
  preloadKeys: ["us", "eu"]
}) {}
```

## Reference counting and idle expiration

A `LayerMap` is, under the hood, an `RcMap<K, Context<I>, E>`: a scoped,
reference-counted map. Each `get(key)` increments the entry's reference count for
the *current* scope and adds a release finalizer to it. When that scope closes
the reference is released; when the count reaches zero the entry is either closed
immediately (default, `idleTimeToLive` of `0`) or kept warm until the idle window
elapses. Building the same key while it is live shares the cached `Context`
instead of rebuilding it.

`idleTimeToLive` controls that warm window: a fixed `Duration.Input`, a
`(key) => Duration.Input` function, or — when infinite — an entry that never
expires on its own (only `invalidate` or the map's scope closing releases it).
**Note:** The layers a `LayerMap` builds share the *current layer memoization map*, so a
shared dependency layer referenced by multiple keyed layers is built once and
reused across keys, not rebuilt per key.

### Underlying RcMap

`LayerMap` delegates lifecycle management to the `RcMap` module. You rarely need
it directly, but `.rcMap` lets you use these combinators on the live entries:

#### `RcMap.keys`

Returns an iterable of all keys that currently have a stored entry. Interrupts if
the map's scope has closed.

```ts
import { Effect, RcMap } from "effect"

declare const map: RcMap.RcMap<string, unknown>
Effect.gen(function*() {
  const ks = yield* RcMap.keys(map)
  console.log([...ks]) // => ["us", "eu"]
})
```

#### `RcMap.has`

Checks whether a key currently has a stored entry, without running the lookup.
Returns `false` for both missing keys and closed maps.

```ts
import { Effect, RcMap } from "effect"

declare const map: RcMap.RcMap<string, unknown>
Effect.gen(function*() {
  const present = yield* RcMap.has(map, "us")
  console.log(present) // => true
})
```

#### `RcMap.touch`

Resets the idle expiration timer for a key (when `idleTimeToLive` is configured),
keeping a warm entry alive longer without acquiring a new reference.

```ts
import { Effect, RcMap } from "effect"

declare const map: RcMap.RcMap<string, unknown>
Effect.gen(function*() {
  yield* RcMap.touch(map, "us") // => idle timer reset for "us"
})
```

#### `RcMap.invalidate`

Removes a key and releases it if it has no live references. This is what
`LayerMap`'s `.invalidate(key)` calls under the hood.

```ts
import { Effect, RcMap } from "effect"

declare const map: RcMap.RcMap<string, unknown>
Effect.gen(function*() {
  yield* RcMap.invalidate(map, "us") // => "us" removed; rebuilt on next get
})
```
**Tip:** `RcMap.make` also accepts a `capacity` option (a max number of live entries,
failing with `ExceededCapacityError` when exceeded). `LayerMap` does not surface
`capacity`, so use `RcMap` directly when you need a bounded keyed resource pool.

`LayerMap` builds on the same caching machinery as
[Caching](https://effect.plants.sh/caching/) and the scope rules from
[Resource Management](https://effect.plants.sh/resource-management/); reach for it specifically when the
*identity* of a resource is a runtime key.

## API reference

### `LayerMap.make`

Standalone constructor: builds a scoped `LayerMap` from a `lookup` function. Use
this when you want the `LayerMap` value directly (e.g. inside another layer)
rather than as its own service class. Returns an `Effect` that yields the map and
requires a `Scope`.

```ts
import { Context, Effect, Layer, LayerMap } from "effect"

class Db extends Context.Service<Db, {
  readonly query: (sql: string) => Effect.Effect<string>
}>()("Db") {}

const program = Effect.gen(function*() {
  const map = yield* LayerMap.make(
    (env: string) =>
      Layer.succeed(Db, {
        query: (sql) => Effect.succeed(`${env}: ${sql}`)
      }),
    { idleTimeToLive: "5 seconds" }
  )

  const result = yield* Effect.gen(function*() {
    const db = yield* Db
    return yield* db.query("SELECT 1")
  }).pipe(Effect.provide(map.get("development")))

  console.log(result) // => "development: SELECT 1"
}).pipe(Effect.scoped)
```

### `LayerMap.fromRecord`

Standalone constructor for a fixed set of keys: the record's keys become the
map's key type, and its values are the layers built for those keys. Accepts
`idleTimeToLive` and `preload`.

```ts
import { Context, Effect, Layer, LayerMap } from "effect"
import { Db } from "./Db.ts"

const program = Effect.gen(function*() {
  const map = yield* LayerMap.fromRecord(
    {
      dev: Layer.succeed(Db, { query: (s) => Effect.succeed(`DEV: ${s}`) }),
      prod: Layer.succeed(Db, { query: (s) => Effect.succeed(`PROD: ${s}`) })
    },
    { idleTimeToLive: "10 seconds" }
  )

  const layer = map.get("dev") // => Layer<Db>
}).pipe(Effect.scoped)
```

### `LayerMap.Service`

Defines a service *class* that wraps a `LayerMap`. Call it as
`LayerMap.Service<Self>()(id, options)` where `options` is either the lookup form
`{ lookup, dependencies?, idleTimeToLive?, preloadKeys? }` or the record form
`{ layers, dependencies?, idleTimeToLive?, preload? }`. The class is a
`Context.Service` tag and carries the static accessor helpers below.

```ts
import { Context, Effect, Layer, LayerMap } from "effect"

class Greeter extends Context.Service<Greeter, {
  readonly greet: Effect.Effect<string>
}>()("Greeter") {}

class GreeterMap extends LayerMap.Service<GreeterMap>()("GreeterMap", {
  lookup: (name: string) =>
    Layer.succeed(Greeter, { greet: Effect.succeed(`Hello, ${name}!`) }),
  idleTimeToLive: "5 seconds"
}) {}

// The class is itself a service tag plus the static helpers .layer/.get/etc.
```

### `.get(key)`

A `Layer` that provides the key's services, built on first access and reused
while cached. Provide it to the program that needs the keyed services.

```ts
import { Console, Effect } from "effect"
import { Greeter } from "./Greeter.ts"
import { GreeterMap } from "./GreeterMap.ts"

const useGreeter = Effect.gen(function*() {
  const greeter = yield* Greeter
  yield* Console.log(yield* greeter.greet) // => "Hello, John!"
}).pipe(
  Effect.provide(GreeterMap.get("John")),
  Effect.provide(GreeterMap.layer)
)
```

### `.contextEffect(key)`

Returns the acquired `Context` for a key directly, as an effectful value rather
than a layer. Requires a `Scope` (plus the service tag), since the returned
context's resources stay live for the surrounding scope.

```ts
import { Context, Effect } from "effect"
import { Greeter } from "./Greeter.ts"
import { GreeterMap } from "./GreeterMap.ts"

const program = Effect.gen(function*() {
  // The acquired Context<Greeter>, kept live for the surrounding scope.
  const ctx = yield* GreeterMap.contextEffect("Jane")

  // Read a service out of it directly...
  const greeter = Context.get(ctx, Greeter)
  console.log(yield* greeter.greet) // => "Hello, Jane!"

  // ...or provide the whole context to an effect.
  const msg = yield* Greeter.pipe(
    Effect.flatMap((g) => g.greet),
    Effect.provideContext(ctx)
  )
  console.log(msg) // => "Hello, Jane!"
}).pipe(Effect.scoped, Effect.provide(GreeterMap.layer))
```

### `.invalidate(key)`

Finalizes the current cached entry for a key and removes it from the map; the
next `get`/`contextEffect` rebuilds it. Useful for forcing a reconnect or
discarding a stale resource. If the entry still has live references it is removed
from the map but only finalized once those references are released.

```ts
import { Effect } from "effect"
import { GreeterMap } from "./GreeterMap.ts"

const refresh = Effect.gen(function*() {
  // Drop the cached "John" entry; the next get("John") builds a fresh one.
  yield* GreeterMap.invalidate("John")
}).pipe(Effect.provide(GreeterMap.layer))
```

### `.layer`

The default layer that constructs the `LayerMap` service, with any
`dependencies` already provided. Provide this once near the root so the accessor
layers (`get`, etc.) have a map to read from.

```ts
import { Effect } from "effect"
import { GreeterMap } from "./GreeterMap.ts"

const main = Effect.void.pipe(Effect.provide(GreeterMap.layer))
// => Layer<GreeterMap> (dependencies, if any, already satisfied)
```

### `.layerNoDeps`

Same as `.layer` but *without* the configured `dependencies` provided — so its
requirement channel still lists those dependency services. Use it when you want
to wire the dependencies yourself (e.g. share a single dependency layer across
several services).

```ts
import { Layer } from "effect"
import { ClientMap } from "./ClientMap.ts"

// Provide dependencies your own way instead of relying on options.dependencies.
const wired = Layer.provide(ClientMap.layerNoDeps, [/* your dep layers */])
```

### `.rcMap`

On the `LayerMap` value (from `make` / `fromRecord`), exposes the underlying
[`RcMap`](#underlying-rcmap) that stores the reference-counted contexts. Drop to
this layer to inspect keys or extend idle lifetimes with the `RcMap` combinators.

```ts
import { Effect, LayerMap, RcMap } from "effect"

const inspect = (map: LayerMap.LayerMap<string, never>) =>
  Effect.gen(function*() {
    const keys = yield* RcMap.keys(map.rcMap)
    console.log([...keys]) // => the keys with live/cached entries
  })
```