| 1 | /** |
| 2 | * Caches scoped services selected by key and built from layers. |
| 3 | * |
| 4 | * A `LayerMap<K, I, E>` turns a key into a cached service `Context<I>` and |
| 5 | * exposes that context as either a `Layer` or a scoped effect. Entries can be |
| 6 | * invalidated explicitly or released after they sit unused. This is useful for |
| 7 | * keyed resource families such as tenant clients, regional connections, or |
| 8 | * environment-specific services. |
| 9 | * |
| 10 | * @since 3.14.0 |
| 11 | */ |
| 12 | import * as Context from "./Context.ts" |
| 13 | import * as Duration from "./Duration.ts" |
| 14 | import * as Effect from "./Effect.ts" |
| 15 | import { identity } from "./Function.ts" |
| 16 | import { getStackTraceLimit, setStackTraceLimit } from "./internal/stackTraceLimit.ts" |
| 17 | import * as Layer from "./Layer.ts" |
| 18 | import type * as Option from "./Option.ts" |
| 19 | import * as RcMap from "./RcMap.ts" |
| 20 | import * as Scope from "./Scope.ts" |
| 21 | import type { Mutable, NoExcessProperties } from "./Types.ts" |
| 22 | |
| 23 | const TypeId = "~effect/LayerMap" |
| 24 | |
| 25 | type IdleTimeToLiveInput<K> = Duration.Input | ((key: K) => Duration.Input) |
| 26 | |
| 27 | /** |
| 28 | * A scoped, keyed map of layer-built service contexts. |
| 29 | * |
| 30 | * **Details** |
| 31 | * |
| 32 | * A `LayerMap` builds resources for a key on demand, exposes them as a `Layer` |
| 33 | * or scoped `Context`, and can invalidate cached resources for a key. |
| 34 | * |
| 35 | * **Example** (Managing keyed layers) |
| 36 | * |
| 37 | * ```ts import.meta.vitest |
| 38 | * import { Context, Effect, Layer, LayerMap } from "effect" |
| 39 | * |
| 40 | * // Define a service key |
| 41 | * const DatabaseService = Context.Service<{ |
| 42 | * readonly query: (sql: string) => Effect.Effect<string> |
| 43 | * }>("Database") |
| 44 | * |
| 45 | * // Create a LayerMap that provides different database configurations |
| 46 | * const createDatabaseLayerMap = LayerMap.make((env: string) => |
| 47 | * Layer.succeed(DatabaseService)({ |
| 48 | * query: Effect.fn("DatabaseService.query")((sql) => Effect.succeed(`${env}: ${sql}`)) |
| 49 | * }) |
| 50 | * ) |
| 51 | * |
| 52 | * // Use the LayerMap |
| 53 | * const program = Effect.gen(function*() { |
| 54 | * const layerMap = yield* createDatabaseLayerMap |
| 55 | * |
| 56 | * // Get a layer for a specific environment |
| 57 | * const development = yield* Effect.provide( |
| 58 | * DatabaseService.use((database) => database.query("SELECT 1")), |
| 59 | * layerMap.get("development") |
| 60 | * ) |
| 61 | * |
| 62 | * // Get context directly |
| 63 | * const productionContext = yield* layerMap.contextEffect("production") |
| 64 | * const production = yield* Context.get(productionContext, DatabaseService).query("SELECT 1") |
| 65 | * |
| 66 | * // Invalidate a cached layer |
| 67 | * yield* layerMap.invalidate("development") |
| 68 | * |
| 69 | * return { development, production } |
| 70 | * }) |
| 71 | * |
| 72 | * await Effect.runPromise(Effect.scoped(program)) // => { development: "development: SELECT 1", production: "production: SELECT 1" } |
| 73 | * ``` |
| 74 | * |
| 75 | * @category models |
| 76 | * @since 3.14.0 |
| 77 | */ |
| 78 | export interface LayerMap<in out K, in out I, in out E = never> { |
| 79 | readonly [TypeId]: typeof TypeId |
| 80 | |
| 81 | /** |
| 82 | * The internal RcMap that stores the resources. |
| 83 | */ |
| 84 | readonly rcMap: RcMap.RcMap<K, Context.Context<I>, E> |
| 85 | |
| 86 | /** |
| 87 | * Retrieves a Layer for the resources associated with the key. |
| 88 | */ |
| 89 | get(key: K): Layer.Layer<I, E> |
| 90 | |
| 91 | /** |
| 92 | * Retrieves the context associated with the key. |
| 93 | */ |
| 94 | contextEffect(key: K): Effect.Effect<Context.Context<I>, E, Scope.Scope> |
| 95 | |
| 96 | /** |
| 97 | * Retains and returns the context for a key only when it is currently cached. |
| 98 | * |
| 99 | * **Details** |
| 100 | * |
| 101 | * `Option.none` means no entry is currently cached or the `LayerMap` is closed; |
| 102 | * no layer is built for a missing key. An existing in-flight entry is awaited. |
| 103 | * |
| 104 | * @since 4.0.0 |
| 105 | */ |
| 106 | contextEffectOption(key: K): Effect.Effect<Option.Option<Context.Context<I>>, E, Scope.Scope> |
| 107 | |
| 108 | /** |
| 109 | * Invalidates the resource associated with the key. |
| 110 | */ |
| 111 | invalidate(key: K): Effect.Effect<void> |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Creates a `LayerMap` that dynamically provides resources based on a key. |
| 116 | * |
| 117 | * **Example** (Creating a layer map) |
| 118 | * |
| 119 | * ```ts import.meta.vitest |
| 120 | * import { Context, Effect, Layer, LayerMap } from "effect" |
| 121 | * |
| 122 | * // Define a service key |
| 123 | * const DatabaseService = Context.Service<{ |
| 124 | * readonly query: (sql: string) => Effect.Effect<string> |
| 125 | * }>("Database") |
| 126 | * |
| 127 | * // Create a LayerMap that provides different database configurations |
| 128 | * const program = Effect.gen(function*() { |
| 129 | * const layerMap = yield* LayerMap.make( |
| 130 | * (env: string) => |
| 131 | * Layer.succeed(DatabaseService)({ |
| 132 | * query: Effect.fn("DatabaseService.query")((sql) => Effect.succeed(`${env}: ${sql}`)) |
| 133 | * }), |
| 134 | * { idleTimeToLive: "5 seconds" } |
| 135 | * ) |
| 136 | * |
| 137 | * // Get a layer for a specific environment |
| 138 | * const devLayer = layerMap.get("development") |
| 139 | * |
| 140 | * // Use the layer to provide the service |
| 141 | * return yield* Effect.provide( |
| 142 | * Effect.gen(function*() { |
| 143 | * const db = yield* DatabaseService |
| 144 | * return yield* db.query("SELECT * FROM users") |
| 145 | * }), |
| 146 | * devLayer |
| 147 | * ) |
| 148 | * }) |
| 149 | * |
| 150 | * await Effect.runPromise(Effect.scoped(program)) // => "development: SELECT * FROM users" |
| 151 | * ``` |
| 152 | * |
| 153 | * @category constructors |
| 154 | * @since 3.14.0 |
| 155 | */ |
| 156 | export const make: < |
| 157 | K, |
| 158 | L extends Layer.Layer<any, any, any>, |
| 159 | PreloadKeys extends Iterable<K> | undefined = undefined |
| 160 | >( |
| 161 | lookup: (key: K) => L, |
| 162 | options?: { |
| 163 | readonly idleTimeToLive?: IdleTimeToLiveInput<K> | undefined |
| 164 | /** |
| 165 | * Preloaded entries are retained only for their idle TTL. Keys whose idle |
| 166 | * TTL is zero are not preloaded (including when no TTL is specified). |
| 167 | */ |
| 168 | readonly preloadKeys?: PreloadKeys |
| 169 | } | undefined |
| 170 | ) => Effect.Effect< |
| 171 | LayerMap<K, Layer.Success<L>, Layer.Error<L>>, |
| 172 | PreloadKeys extends undefined ? never : Layer.Error<L>, |
| 173 | Scope.Scope | Layer.Services<L> |
| 174 | > = Effect.fnUntraced(function*<I, K, EL, RL>( |
| 175 | lookup: (key: K) => Layer.Layer<I, EL, RL>, |
| 176 | options?: { |
| 177 | readonly idleTimeToLive?: IdleTimeToLiveInput<K> | undefined |
| 178 | readonly preloadKeys?: Iterable<K> | undefined |
| 179 | } | undefined |
| 180 | ) { |
| 181 | const context = yield* Effect.context<never>() |
| 182 | const memoMap = Layer.CurrentMemoMap.forkOrCreate(context) |
| 183 | |
| 184 | const rcMap = yield* RcMap.make({ |
| 185 | lookup: (key: K) => |
| 186 | Effect.contextWith((_: Context.Context<Scope.Scope>) => |
| 187 | Layer.buildWithMemoMap(lookup(key), memoMap, Context.get(_, Scope.Scope)) |
| 188 | ), |
| 189 | idleTimeToLive: options?.idleTimeToLive |
| 190 | }) |
| 191 | |
| 192 | if (options?.preloadKeys) { |
| 193 | for (const key of options.preloadKeys) { |
| 194 | if (!Duration.isZero(rcMap.idleTimeToLive(key))) { |
| 195 | yield* Effect.scoped(RcMap.get(rcMap, key)) |
| 196 | } |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | return identity<LayerMap<K, I, any>>({ |
| 201 | [TypeId]: TypeId, |
| 202 | rcMap, |
| 203 | get: (key) => Layer.effectContext(RcMap.get(rcMap, key)), |
| 204 | contextEffect: (key) => RcMap.get(rcMap, key), |
| 205 | contextEffectOption: (key) => RcMap.getOption(rcMap, key), |
| 206 | invalidate: (key) => RcMap.invalidate(rcMap, key) |
| 207 | }) |
| 208 | }) |
| 209 | |
| 210 | /** |
| 211 | * Creates a `LayerMap` from a record of predefined layers. |
| 212 | * |
| 213 | * **Details** |
| 214 | * |
| 215 | * The record keys become the keys accepted by the returned `LayerMap`, and the |
| 216 | * record values are the layers built for those keys. |
| 217 | * |
| 218 | * **Example** (Creating a layer map from a record) |
| 219 | * |
| 220 | * ```ts import.meta.vitest |
| 221 | * import { Context, Effect, Layer, LayerMap } from "effect" |
| 222 | * |
| 223 | * // Define a service key |
| 224 | * const Database = Context.Service<{ |
| 225 | * readonly query: (sql: string) => Effect.Effect<string> |
| 226 | * }>("Database") |
| 227 | * |
| 228 | * // Create predefined layers |
| 229 | * const layers = { |
| 230 | * development: Layer.succeed(Database)({ |
| 231 | * query: Effect.fn("DevDatabase.query")((sql) => Effect.succeed(`DEV: ${sql}`)) |
| 232 | * }), |
| 233 | * production: Layer.succeed(Database)({ |
| 234 | * query: Effect.fn("ProdDatabase.query")((sql) => Effect.succeed(`PROD: ${sql}`)) |
| 235 | * }) |
| 236 | * } as const |
| 237 | * |
| 238 | * // Create a LayerMap from the record |
| 239 | * const program = Effect.gen(function*() { |
| 240 | * const layerMap = yield* LayerMap.fromRecord(layers, { |
| 241 | * idleTimeToLive: "10 seconds" |
| 242 | * }) |
| 243 | * |
| 244 | * const development = yield* Effect.provide( |
| 245 | * Database.use((database) => database.query("SELECT 1")), |
| 246 | * layerMap.get("development") |
| 247 | * ) |
| 248 | * const production = yield* Effect.provide( |
| 249 | * Database.use((database) => database.query("SELECT 1")), |
| 250 | * layerMap.get("production") |
| 251 | * ) |
| 252 | * |
| 253 | * return { development, production } |
| 254 | * }) |
| 255 | * |
| 256 | * await Effect.runPromise(Effect.scoped(program)) // => { development: "DEV: SELECT 1", production: "PROD: SELECT 1" } |
| 257 | * ``` |
| 258 | * |
| 259 | * @category constructors |
| 260 | * @since 3.14.0 |
| 261 | */ |
| 262 | export const fromRecord = < |
| 263 | const Layers extends Record<string, Layer.Layer<any, any, any>>, |
| 264 | const Preload extends boolean = false |
| 265 | >( |
| 266 | layers: Layers, |
| 267 | options?: { |
| 268 | readonly idleTimeToLive?: IdleTimeToLiveInput<keyof Layers> | undefined |
| 269 | /** |
| 270 | * Preloaded entries are retained only for their idle TTL. Keys whose idle |
| 271 | * TTL is zero are not preloaded (including when no TTL is specified). |
| 272 | */ |
| 273 | readonly preload?: Preload | undefined |
| 274 | } | undefined |
| 275 | ): Effect.Effect< |
| 276 | LayerMap< |
| 277 | keyof Layers, |
| 278 | Layer.Success<Layers[keyof Layers]>, |
| 279 | Layer.Error<Layers[keyof Layers]> |
| 280 | >, |
| 281 | Preload extends true ? Layer.Error<Layers[keyof Layers]> : never, |
| 282 | Scope.Scope | (Layers[keyof Layers] extends Layer.Layer<infer _A, infer _E, infer _R> ? _R : never) |
| 283 | > => |
| 284 | make((key: keyof Layers) => layers[key], { |
| 285 | ...options, |
| 286 | preloadKeys: options?.preload ? Object.keys(layers) : undefined |
| 287 | }) as any |
| 288 | |
| 289 | /** |
| 290 | * Service class shape produced by `LayerMap.Service`. |
| 291 | * |
| 292 | * **When to use** |
| 293 | * |
| 294 | * Use as the public type for classes returned by `LayerMap.Service` when an API |
| 295 | * needs to accept, return, or alias the generated service class and its static |
| 296 | * helpers. |
| 297 | * |
| 298 | * **Details** |
| 299 | * |
| 300 | * It combines a `Context.Service` tag for the `LayerMap` with default layers |
| 301 | * and helper accessors for retrieving, using, and invalidating keyed resources. |
| 302 | * |
| 303 | * @see {@link Service} for creating concrete `LayerMap` service classes |
| 304 | * |
| 305 | * @category services |
| 306 | * @since 3.14.0 |
| 307 | */ |
| 308 | export interface TagClass< |
| 309 | in out Self, |
| 310 | in out Id extends string, |
| 311 | in out K, |
| 312 | in out I, |
| 313 | in out E, |
| 314 | in out R, |
| 315 | in out LE, |
| 316 | in out Deps extends Layer.Layer<any, any, any> |
| 317 | > extends Context.ServiceClass<Self, Id, LayerMap<K, I, E>> { |
| 318 | /** |
| 319 | * A default layer for the `LayerMap` service. |
| 320 | */ |
| 321 | readonly layer: Layer.Layer< |
| 322 | Self, |
| 323 | (Deps extends Layer.Layer<infer _A, infer _E, infer _R> ? _E : never) | LE, |
| 324 | | Exclude<R, (Deps extends Layer.Layer<infer _A, infer _E, infer _R> ? _A : never)> |
| 325 | | (Deps extends Layer.Layer<infer _A, infer _E, infer _R> ? _R : never) |
| 326 | > |
| 327 | |
| 328 | /** |
| 329 | * A default layer for the `LayerMap` service without the dependencies provided. |
| 330 | */ |
| 331 | readonly layerNoDeps: Layer.Layer<Self, LE, R> |
| 332 | |
| 333 | /** |
| 334 | * Retrieves a Layer for the resources associated with the key. |
| 335 | */ |
| 336 | readonly get: (key: K) => Layer.Layer<I, E, Self> |
| 337 | |
| 338 | /** |
| 339 | * Retrieves the context associated with the key. |
| 340 | */ |
| 341 | readonly contextEffect: (key: K) => Effect.Effect<Context.Context<I>, E, Scope.Scope | Self> |
| 342 | |
| 343 | /** |
| 344 | * Retains and returns the context for a key only when it is currently cached. |
| 345 | * |
| 346 | * **Details** |
| 347 | * |
| 348 | * `Option.none` means no entry is currently cached or the `LayerMap` is closed; |
| 349 | * no layer is built for a missing key. An existing in-flight entry is awaited. |
| 350 | * |
| 351 | * @since 4.0.0 |
| 352 | */ |
| 353 | readonly contextEffectOption: ( |
| 354 | key: K |
| 355 | ) => Effect.Effect<Option.Option<Context.Context<I>>, E, Scope.Scope | Self> |
| 356 | |
| 357 | /** |
| 358 | * Invalidates the resource associated with the key. |
| 359 | */ |
| 360 | readonly invalidate: (key: K) => Effect.Effect<void, never, Self> |
| 361 | } |
| 362 | |
| 363 | /** |
| 364 | * Create a `LayerMap` service that provides a dynamic set of resources based on |
| 365 | * a key. |
| 366 | * |
| 367 | * **Example** (Defining a layer map service) |
| 368 | * |
| 369 | * ```ts import.meta.vitest |
| 370 | * import { Context, Effect, Layer, LayerMap } from "effect" |
| 371 | * |
| 372 | * // Define a service key |
| 373 | * const Greeter = Context.Service<{ |
| 374 | * readonly greet: Effect.Effect<string> |
| 375 | * }>("Greeter") |
| 376 | * |
| 377 | * // Create a service that wraps a LayerMap |
| 378 | * class GreeterMap extends LayerMap.Service<GreeterMap>()("GreeterMap", { |
| 379 | * // Define the lookup function for the layer map |
| 380 | * lookup: (name: string) => |
| 381 | * Layer.succeed(Greeter)({ |
| 382 | * greet: Effect.succeed(`Hello, ${name}!`) |
| 383 | * }), |
| 384 | * |
| 385 | * // If a layer is not used for a certain amount of time, it can be removed |
| 386 | * idleTimeToLive: "5 seconds" |
| 387 | * }) {} |
| 388 | * |
| 389 | * // Usage |
| 390 | * const program = Effect.gen(function*() { |
| 391 | * // Access and use the Greeter service |
| 392 | * const greeter = yield* Greeter |
| 393 | * return yield* greeter.greet |
| 394 | * }).pipe( |
| 395 | * // Use the GreeterMap service to provide a variant of the Greeter service |
| 396 | * Effect.provide(GreeterMap.get("John")) |
| 397 | * ).pipe( |
| 398 | * // Provide the GreeterMap layer |
| 399 | * Effect.provide(GreeterMap.layer) |
| 400 | * ) |
| 401 | * |
| 402 | * await Effect.runPromise(program) // => "Hello, John!" |
| 403 | * ``` |
| 404 | * |
| 405 | * @category services |
| 406 | * @since 3.14.0 |
| 407 | */ |
| 408 | export const Service = <Self>() => |
| 409 | < |
| 410 | const Id extends string, |
| 411 | const Options extends |
| 412 | | NoExcessProperties<{ |
| 413 | readonly lookup: (key: any) => Layer.Layer<any, any, any> |
| 414 | readonly dependencies?: ReadonlyArray<Layer.Layer<any, any, any>> | undefined |
| 415 | readonly idleTimeToLive?: IdleTimeToLiveInput<any> | undefined |
| 416 | /** |
| 417 | * Preloaded entries are retained only for their idle TTL. Keys whose idle |
| 418 | * TTL is zero are not preloaded (including when no TTL is specified). |
| 419 | */ |
| 420 | readonly preloadKeys?: |
| 421 | | Iterable<Options extends { readonly lookup: (key: infer K) => any } ? K : never> |
| 422 | | undefined |
| 423 | }, Options> |
| 424 | | NoExcessProperties<{ |
| 425 | readonly layers: Record<string, Layer.Layer<any, any, any>> |
| 426 | readonly dependencies?: ReadonlyArray<Layer.Layer<any, any, any>> | undefined |
| 427 | readonly idleTimeToLive?: IdleTimeToLiveInput<any> | undefined |
| 428 | /** |
| 429 | * Preloaded entries are retained only for their idle TTL. Keys whose idle |
| 430 | * TTL is zero are not preloaded (including when no TTL is specified). |
| 431 | */ |
| 432 | readonly preload?: boolean | undefined |
| 433 | }, Options> |
| 434 | >( |
| 435 | id: Id, |
| 436 | options: Options |
| 437 | ): TagClass< |
| 438 | Self, |
| 439 | Id, |
| 440 | Options extends { readonly lookup: (key: infer K) => any } ? K |
| 441 | : Options extends { readonly layers: infer Layers } ? keyof Layers |
| 442 | : never, |
| 443 | Service.Success<Options>, |
| 444 | Service.Error<Options>, |
| 445 | Service.Services<Options>, |
| 446 | Options extends { readonly preload: true } ? Service.Error<Options> |
| 447 | : Options extends { readonly preloadKeys: Iterable<any> } ? Service.Error<Options> |
| 448 | : never, |
| 449 | Options extends { readonly dependencies: ReadonlyArray<Layer.Layer<any, any, any>> } ? Options["dependencies"][number] |
| 450 | : never |
| 451 | > => { |
| 452 | const limit = getStackTraceLimit() |
| 453 | let creationError: Error | undefined |
| 454 | if (limit !== 0) { |
| 455 | setStackTraceLimit(2) |
| 456 | creationError = new globalThis.Error() |
| 457 | setStackTraceLimit(limit) |
| 458 | } |
| 459 | |
| 460 | function TagClass() {} |
| 461 | const TagClass_ = TagClass as any as Mutable<TagClass<Self, Id, string, any, any, any, any, any>> |
| 462 | Object.setPrototypeOf(TagClass, Object.getPrototypeOf(Context.Service<Self, any>(id))) |
| 463 | TagClass.key = id |
| 464 | Object.defineProperty(TagClass, "stack", { |
| 465 | get() { |
| 466 | return creationError?.stack |
| 467 | } |
| 468 | }) |
| 469 | |
| 470 | TagClass_.layerNoDeps = Layer.effect(TagClass_)( |
| 471 | "lookup" in options |
| 472 | ? make(options.lookup, options) |
| 473 | : fromRecord(options.layers as any, options) as any |
| 474 | ) |
| 475 | TagClass_.layer = options.dependencies && options.dependencies.length > 0 ? |
| 476 | Layer.provide(TagClass_.layerNoDeps, options.dependencies as any) : |
| 477 | TagClass_.layerNoDeps |
| 478 | |
| 479 | TagClass_.get = (key: string) => Layer.unwrap(Effect.map(TagClass_, (layerMap) => layerMap.get(key))) |
| 480 | TagClass_.contextEffect = (key: string) => Effect.flatMap(TagClass_, (layerMap) => layerMap.contextEffect(key)) |
| 481 | TagClass_.contextEffectOption = (key: string) => |
| 482 | Effect.flatMap(TagClass_, (layerMap) => layerMap.contextEffectOption(key)) |
| 483 | TagClass_.invalidate = (key: string) => Effect.flatMap(TagClass_, (layerMap) => layerMap.invalidate(key)) |
| 484 | |
| 485 | return TagClass as any |
| 486 | } |
| 487 | |
| 488 | /** |
| 489 | * Type helpers for values created with `LayerMap.Service`. |
| 490 | * |
| 491 | * @since 3.14.0 |
| 492 | */ |
| 493 | export declare namespace Service { |
| 494 | /** |
| 495 | * Extracts the key type accepted by a `LayerMap.Service` definition. |
| 496 | * |
| 497 | * @category utility types |
| 498 | * @since 3.14.0 |
| 499 | */ |
| 500 | export type Key<Options> = Options extends { readonly lookup: (key: infer K) => any } ? K |
| 501 | : Options extends { readonly layers: infer Layers } ? keyof Layers |
| 502 | : never |
| 503 | |
| 504 | /** |
| 505 | * Extracts the layer type produced by a `LayerMap.Service` definition. |
| 506 | * |
| 507 | * @category utility types |
| 508 | * @since 3.14.0 |
| 509 | */ |
| 510 | export type Layers<Options> = Options extends { readonly lookup: (key: infer _K) => infer Layers } ? Layers |
| 511 | : Options extends { readonly layers: infer Layers } ? Layers[keyof Layers] |
| 512 | : never |
| 513 | |
| 514 | /** |
| 515 | * Extracts the services provided by the layers in a `LayerMap.Service` |
| 516 | * definition. |
| 517 | * |
| 518 | * @category utility types |
| 519 | * @since 3.14.0 |
| 520 | */ |
| 521 | export type Success<Options> = Layers<Options> extends Layer.Layer<infer _A, infer _E, infer _R> ? _A : never |
| 522 | |
| 523 | /** |
| 524 | * Extracts the error type of the layers in a `LayerMap.Service` definition. |
| 525 | * |
| 526 | * @category utility types |
| 527 | * @since 3.14.0 |
| 528 | */ |
| 529 | export type Error<Options> = Layers<Options> extends Layer.Layer<infer _A, infer _E, infer _R> ? _E : never |
| 530 | |
| 531 | /** |
| 532 | * Extracts the service requirements of the layers in a `LayerMap.Service` |
| 533 | * definition. |
| 534 | * |
| 535 | * @category utility types |
| 536 | * @since 4.0.0 |
| 537 | */ |
| 538 | export type Services<Options> = Layers<Options> extends Layer.Layer<infer _A, infer _E, infer _R> ? _R : never |
| 539 | } |