/** * @since 3.14.0 */ import * as Context from "./Context.ts" import type * as Duration from "./Duration.ts" import * as Effect from "./Effect.ts" import { identity } from "./Function.ts" import * as Layer from "./Layer.ts" import * as RcMap from "./RcMap.ts" import * as Scope from "./Scope.ts" import type { Mutable, NoExcessProperties } from "./Types.ts" const TypeId = "~effect/LayerMap" type IdleTimeToLiveInput = Duration.Input | ((key: K) => Duration.Input) /** * @since 3.14.0 * @category Models * @example * ```ts * import { Effect, Layer, LayerMap, Context } from "effect" * * // Define a service key * const DatabaseService = Context.Service<{ * readonly query: (sql: string) => Effect.Effect * }>("Database") * * // Create a LayerMap that provides different database configurations * const createDatabaseLayerMap = LayerMap.make((env: string) => * Layer.succeed(DatabaseService)({ * query: Effect.fn("DatabaseService.query")((sql) => Effect.succeed(`${env}: ${sql}`)) * }) * ) * * // Use the LayerMap * const program = Effect.gen(function*() { * const layerMap = yield* createDatabaseLayerMap * * // Get a layer for a specific environment * const devLayer = layerMap.get("development") * * // Get context directly * const context = yield* layerMap.contextEffect("production") * * // Invalidate a cached layer * yield* layerMap.invalidate("development") * }) * ``` */ export interface LayerMap { readonly [TypeId]: typeof TypeId /** * The internal RcMap that stores the resources. */ readonly rcMap: RcMap.RcMap, E> /** * Retrieves a Layer for the resources associated with the key. */ get(key: K): Layer.Layer /** * Retrieves the context associated with the key. */ contextEffect(key: K): Effect.Effect, E, Scope.Scope> /** * Invalidates the resource associated with the key. */ invalidate(key: K): Effect.Effect } /** * @since 3.14.0 * @category Constructors * * A `LayerMap` allows you to create a map of Layer's that can be used to * dynamically access resources based on a key. * * @example * ```ts * import { Effect, Layer, LayerMap, Context } from "effect" * * // Define a service key * const DatabaseService = Context.Service<{ * readonly query: (sql: string) => Effect.Effect * }>("Database") * * // Create a LayerMap that provides different database configurations * const program = Effect.gen(function*() { * const layerMap = yield* LayerMap.make( * (env: string) => * Layer.succeed(DatabaseService)({ * query: Effect.fn("DatabaseService.query")((sql) => Effect.succeed(`${env}: ${sql}`)) * }), * { idleTimeToLive: "5 seconds" } * ) * * // Get a layer for a specific environment * const devLayer = layerMap.get("development") * * // Use the layer to provide the service * const result = yield* Effect.provide( * Effect.gen(function*() { * const db = yield* DatabaseService * return yield* db.query("SELECT * FROM users") * }), * devLayer * ) * * console.log(result) // "development: SELECT * FROM users" * }) * ``` */ export const make: < K, L extends Layer.Layer, PreloadKeys extends Iterable | undefined = undefined >( lookup: (key: K) => L, options?: { readonly idleTimeToLive?: IdleTimeToLiveInput | undefined readonly preloadKeys?: PreloadKeys } | undefined ) => Effect.Effect< LayerMap, Layer.Error>, PreloadKeys extends undefined ? never : Layer.Error, Scope.Scope | Layer.Services > = Effect.fnUntraced(function*( lookup: (key: K) => Layer.Layer, options?: { readonly idleTimeToLive?: IdleTimeToLiveInput | undefined } | undefined ) { const context = yield* Effect.context() const memoMap = Layer.CurrentMemoMap.getOrCreate(context) const rcMap = yield* RcMap.make({ lookup: (key: K) => Effect.contextWith((_: Context.Context) => Layer.buildWithMemoMap(lookup(key), memoMap, Context.get(_, Scope.Scope)) ), idleTimeToLive: options?.idleTimeToLive }) return identity>({ [TypeId]: TypeId, rcMap, get: (key) => Layer.effectContext(RcMap.get(rcMap, key)), contextEffect: (key) => RcMap.get(rcMap, key), invalidate: (key) => RcMap.invalidate(rcMap, key) }) }) /** * @since 3.14.0 * @category Constructors * @example * ```ts * import { Effect, Layer, LayerMap, Context } from "effect" * * // Define service keys * const DevDatabase = Context.Service<{ * readonly query: (sql: string) => Effect.Effect * }>("DevDatabase") * * const ProdDatabase = Context.Service<{ * readonly query: (sql: string) => Effect.Effect * }>("ProdDatabase") * * // Create predefined layers * const layers = { * development: Layer.succeed(DevDatabase)({ * query: Effect.fn("DevDatabase.query")((sql) => Effect.succeed(`DEV: ${sql}`)) * }), * production: Layer.succeed(ProdDatabase)({ * query: Effect.fn("ProdDatabase.query")((sql) => Effect.succeed(`PROD: ${sql}`)) * }) * } as const * * // Create a LayerMap from the record * const program = Effect.gen(function*() { * const layerMap = yield* LayerMap.fromRecord(layers, { * idleTimeToLive: "10 seconds" * }) * * // Get layers by key * const devLayer = layerMap.get("development") * const prodLayer = layerMap.get("production") * * console.log("LayerMap created from record") * }) * ``` */ export const fromRecord = < const Layers extends Record>, const Preload extends boolean = false >( layers: Layers, options?: { readonly idleTimeToLive?: IdleTimeToLiveInput | undefined readonly preload?: Preload | undefined } | undefined ): Effect.Effect< LayerMap< keyof Layers, Layer.Success, Layer.Error >, Preload extends true ? Layer.Error : never, Scope.Scope | (Layers[keyof Layers] extends Layer.Layer ? _R : never) > => make((key: keyof Layers) => layers[key], { ...options, preloadKeys: options?.preload ? Object.keys(layers) : undefined }) as any /** * @since 3.14.0 * @category Service */ export interface TagClass< in out Self, in out Id extends string, in out K, in out I, in out E, in out R, in out LE, in out Deps extends Layer.Layer > extends Context.ServiceClass> { /** * A default layer for the `LayerMap` service. */ readonly layer: Layer.Layer< Self, (Deps extends Layer.Layer ? _E : never) | LE, | Exclude ? _A : never)> | (Deps extends Layer.Layer ? _R : never) > /** * A default layer for the `LayerMap` service without the dependencies provided. */ readonly layerNoDeps: Layer.Layer /** * Retrieves a Layer for the resources associated with the key. */ readonly get: (key: K) => Layer.Layer /** * Retrieves the context associated with the key. */ readonly contextEffect: (key: K) => Effect.Effect, E, Scope.Scope | Self> /** * Invalidates the resource associated with the key. */ readonly invalidate: (key: K) => Effect.Effect } /** * @since 3.14.0 * @category Service * * Create a `LayerMap` service that provides a dynamic set of resources based on * a key. * * @example * ```ts * import { Console, Effect, Layer, LayerMap, Context } from "effect" * * // Define a service key * const Greeter = Context.Service<{ * readonly greet: Effect.Effect * }>("Greeter") * * // Create a service that wraps a LayerMap * class GreeterMap extends LayerMap.Service()("GreeterMap", { * // Define the lookup function for the layer map * lookup: (name: string) => * Layer.succeed(Greeter)({ * greet: Effect.succeed(`Hello, ${name}!`) * }), * * // If a layer is not used for a certain amount of time, it can be removed * idleTimeToLive: "5 seconds" * }) {} * * // Usage * const program = Effect.gen(function*() { * // Access and use the Greeter service * const greeter = yield* Greeter * yield* Console.log(yield* greeter.greet) * }).pipe( * // Use the GreeterMap service to provide a variant of the Greeter service * Effect.provide(GreeterMap.get("John")) * ).pipe( * // Provide the GreeterMap layer * Effect.provide(GreeterMap.layer) * ) * ``` */ export const Service = () => < const Id extends string, const Options extends | NoExcessProperties<{ readonly lookup: (key: any) => Layer.Layer readonly dependencies?: ReadonlyArray> | undefined readonly idleTimeToLive?: IdleTimeToLiveInput | undefined readonly preloadKeys?: | Iterable any } ? K : never> | undefined }, Options> | NoExcessProperties<{ readonly layers: Record> readonly dependencies?: ReadonlyArray> | undefined readonly idleTimeToLive?: IdleTimeToLiveInput | undefined readonly preload?: boolean | undefined }, Options> >( id: Id, options: Options ): TagClass< Self, Id, Options extends { readonly lookup: (key: infer K) => any } ? K : Options extends { readonly layers: infer Layers } ? keyof Layers : never, Service.Success, Options extends { readonly preload: true } ? never : Service.Error, Service.Services, Options extends { readonly preload: true } ? Service.Error : Options extends { readonly preloadKeys: Iterable } ? Service.Error : never, Options extends { readonly dependencies: ReadonlyArray> } ? Options["dependencies"][number] : never > => { const Err = globalThis.Error as any const limit = Err.stackTraceLimit Err.stackTraceLimit = 2 const creationError = new Err() Err.stackTraceLimit = limit function TagClass() {} const TagClass_ = TagClass as any as Mutable> Object.setPrototypeOf(TagClass, Object.getPrototypeOf(Context.Service(id))) TagClass.key = id Object.defineProperty(TagClass, "stack", { get() { return creationError.stack } }) TagClass_.layerNoDeps = Layer.effect(TagClass_)( "lookup" in options ? make(options.lookup, options) : fromRecord(options.layers as any, options) as any ) TagClass_.layer = options.dependencies && options.dependencies.length > 0 ? Layer.provide(TagClass_.layerNoDeps, options.dependencies as any) : TagClass_.layerNoDeps TagClass_.get = (key: string) => Layer.unwrap(Effect.map(TagClass_, (layerMap) => layerMap.get(key))) TagClass_.contextEffect = (key: string) => Effect.flatMap(TagClass_, (layerMap) => layerMap.contextEffect(key)) TagClass_.invalidate = (key: string) => Effect.flatMap(TagClass_, (layerMap) => layerMap.invalidate(key)) return TagClass as any } /** * @since 3.14.0 * @category Service */ export declare namespace Service { /** * @since 3.14.0 * @category Service */ export type Key = Options extends { readonly lookup: (key: infer K) => any } ? K : Options extends { readonly layers: infer Layers } ? keyof Layers : never /** * @since 3.14.0 * @category Service */ export type Layers = Options extends { readonly lookup: (key: infer _K) => infer Layers } ? Layers : Options extends { readonly layers: infer Layers } ? Layers[keyof Layers] : never /** * @since 3.14.0 * @category Service */ export type Success = Layers extends Layer.Layer ? _A : never /** * @since 3.14.0 * @category Service */ export type Error = Layers extends Layer.Layer ? _E : never /** * @since 3.14.0 * @category Service */ export type Services = Layers extends Layer.Layer ? _R : never }