/** * This module provides a data structure called `Context` that can be used for dependency injection in effectful * programs. It is essentially a table mapping `Tag`s to their implementations (called `Service`s), and can be used to * manage dependencies in a type-safe way. The `Context` data structure is essentially a way of providing access to a set * of related services that can be passed around as a single unit. This module provides functions to create, modify, and * query the contents of a `Context`, as well as a number of utility types for working with tags and services. * * @since 2.0.0 */ import type * as Effect from "./Effect.js" import type { Equal } from "./Equal.js" import type { LazyArg } from "./Function.js" import type { Inspectable } from "./Inspectable.js" import * as internal from "./internal/context.js" import type { Option } from "./Option.js" import type { Pipeable } from "./Pipeable.js" import type * as Types from "./Types.js" import type * as Unify from "./Unify.js" /** * @since 2.0.0 * @category symbol */ export const TagTypeId: unique symbol = internal.TagTypeId /** * @since 2.0.0 * @category symbol */ export type TagTypeId = typeof TagTypeId /** * @since 3.5.9 * @category models */ export interface Tag extends Pipeable, Inspectable, ReadonlyTag { readonly _op: "Tag" readonly Service: Value readonly Identifier: Id readonly [TagTypeId]: { readonly _Service: Types.Invariant readonly _Identifier: Types.Invariant } of(self: Value): Value context(self: Value): Context readonly stack?: string | undefined readonly key: string [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: TagUnify [Unify.ignoreSymbol]?: TagUnifyIgnore } /** * @since 3.5.9 * @category models */ export interface ReadonlyTag extends Pipeable, Inspectable, Effect.Effect { readonly _op: "Tag" readonly Service: Value readonly Identifier: Id readonly [TagTypeId]: { readonly _Service: Types.Covariant readonly _Identifier: Types.Invariant } readonly stack?: string | undefined readonly key: string } /** * @since 3.11.0 * @category symbol */ export const ReferenceTypeId: unique symbol = internal.ReferenceTypeId /** * @since 3.11.0 * @category symbol */ export type ReferenceTypeId = typeof ReferenceTypeId /** * @since 3.11.0 * @category models */ export interface Reference extends Pipeable, Inspectable { readonly [ReferenceTypeId]: ReferenceTypeId readonly defaultValue: () => Value readonly _op: "Tag" readonly Service: Value readonly Identifier: Id readonly [TagTypeId]: { readonly _Service: Types.Invariant readonly _Identifier: Types.Invariant } of(self: Value): Value context(self: Value): Context readonly stack?: string | undefined readonly key: string [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: TagUnify [Unify.ignoreSymbol]?: TagUnifyIgnore } /** * @since 2.0.0 * @category models */ export interface TagClassShape { readonly [TagTypeId]: TagTypeId readonly Type: Shape readonly Id: Id } // TODO(4.0): move key narrowing to the Tag interface /** * @since 2.0.0 * @category models */ export interface TagClass extends Tag { new(_: never): TagClassShape readonly key: Id } // TODO(4.0): move key narrowing to the Reference interface /** * @since 3.11.0 * @category models */ export interface ReferenceClass extends Reference { new(_: never): TagClassShape readonly key: Id } /** * @category models * @since 2.0.0 */ export interface TagUnify { Tag?: () => Extract> } /** * @category models * @since 2.0.0 */ export interface TagUnifyIgnore {} /** * @since 2.0.0 */ export declare namespace Tag { /** * @since 2.0.0 */ export type Service | TagClassShape> = T extends Tag ? T["Service"] : T extends TagClassShape ? A : never /** * @since 2.0.0 */ export type Identifier | TagClassShape> = T extends Tag ? T["Identifier"] : T extends TagClassShape ? T : never } /** * Creates a new `Tag` instance with an optional key parameter. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * assert.strictEqual(Context.GenericTag("PORT").key === Context.GenericTag("PORT").key, true) * ``` * * @since 2.0.0 * @category constructors */ export const GenericTag: (key: string) => Tag = internal.makeGenericTag const TypeId: unique symbol = internal.TypeId as TypeId /** * @since 2.0.0 * @category symbol */ export type TypeId = typeof TypeId /** * @since 2.0.0 * @category models */ export type ValidTagsById = R extends infer S ? Tag : never /** * @since 2.0.0 * @category models */ export interface Context extends Equal, Pipeable, Inspectable { readonly [TypeId]: { readonly _Services: Types.Contravariant } readonly unsafeMap: Map } /** * @since 2.0.0 * @category constructors */ export const unsafeMake: (unsafeMap: Map) => Context = internal.makeContext /** * Checks if the provided argument is a `Context`. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * assert.strictEqual(Context.isContext(Context.empty()), true) * ``` * * @since 2.0.0 * @category guards */ export const isContext: (input: unknown) => input is Context = internal.isContext /** * Checks if the provided argument is a `Tag`. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * assert.strictEqual(Context.isTag(Context.GenericTag("Tag")), true) * ``` * * @since 2.0.0 * @category guards */ export const isTag: (input: unknown) => input is Tag = internal.isTag /** * Checks if the provided argument is a `Reference`. * * @since 3.11.0 * @category guards * @experimental */ export const isReference: (u: unknown) => u is Reference = internal.isReference /** * Returns an empty `Context`. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * assert.strictEqual(Context.isContext(Context.empty()), true) * ``` * * @since 2.0.0 * @category constructors */ export const empty: () => Context = internal.empty /** * Creates a new `Context` with a single service associated to the tag. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * ``` * * @since 2.0.0 * @category constructors */ export const make: (tag: Tag, service: Types.NoInfer) => Context = internal.make /** * Adds a service to a given `Context`. * * @example * ```ts * import * as assert from "node:assert" * import { Context, pipe } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const someContext = Context.make(Port, { PORT: 8080 }) * * const Services = pipe( * someContext, * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 */ export const add: { /** * Adds a service to a given `Context`. * * @example * ```ts * import * as assert from "node:assert" * import { Context, pipe } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const someContext = Context.make(Port, { PORT: 8080 }) * * const Services = pipe( * someContext, * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 */ (tag: Tag, service: Types.NoInfer): (self: Context) => Context /** * Adds a service to a given `Context`. * * @example * ```ts * import * as assert from "node:assert" * import { Context, pipe } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const someContext = Context.make(Port, { PORT: 8080 }) * * const Services = pipe( * someContext, * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 */ (self: Context, tag: Tag, service: Types.NoInfer): Context } = internal.add /** * Get a service from the context that corresponds to the given tag. * * @example * ```ts * import * as assert from "node:assert" * import { pipe, Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = pipe( * Context.make(Port, { PORT: 8080 }), * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 * @category getters */ export const get: { /** * Get a service from the context that corresponds to the given tag. * * @example * ```ts * import * as assert from "node:assert" * import { pipe, Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = pipe( * Context.make(Port, { PORT: 8080 }), * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 * @category getters */ (tag: Reference): (self: Context) => S /** * Get a service from the context that corresponds to the given tag. * * @example * ```ts * import * as assert from "node:assert" * import { pipe, Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = pipe( * Context.make(Port, { PORT: 8080 }), * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 * @category getters */ (tag: Tag): (self: Context) => S /** * Get a service from the context that corresponds to the given tag. * * @example * ```ts * import * as assert from "node:assert" * import { pipe, Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = pipe( * Context.make(Port, { PORT: 8080 }), * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 * @category getters */ (self: Context, tag: Reference): S /** * Get a service from the context that corresponds to the given tag. * * @example * ```ts * import * as assert from "node:assert" * import { pipe, Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = pipe( * Context.make(Port, { PORT: 8080 }), * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 * @category getters */ (self: Context, tag: Tag): S } = internal.get /** * Get a service from the context that corresponds to the given tag, or * use the fallback value. * * @since 3.7.0 * @category getters */ export const getOrElse: { /** * Get a service from the context that corresponds to the given tag, or * use the fallback value. * * @since 3.7.0 * @category getters */ (tag: Tag, orElse: LazyArg): (self: Context) => S | B /** * Get a service from the context that corresponds to the given tag, or * use the fallback value. * * @since 3.7.0 * @category getters */ (self: Context, tag: Tag, orElse: LazyArg): S | B } = internal.getOrElse /** * Get a service from the context that corresponds to the given tag. * This function is unsafe because if the tag is not present in the context, a runtime error will be thrown. * * For a safer version see {@link getOption}. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.unsafeGet(Services, Port), { PORT: 8080 }) * assert.throws(() => Context.unsafeGet(Services, Timeout)) * ``` * * @since 2.0.0 * @category unsafe */ export const unsafeGet: { /** * Get a service from the context that corresponds to the given tag. * This function is unsafe because if the tag is not present in the context, a runtime error will be thrown. * * For a safer version see {@link getOption}. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.unsafeGet(Services, Port), { PORT: 8080 }) * assert.throws(() => Context.unsafeGet(Services, Timeout)) * ``` * * @since 2.0.0 * @category unsafe */ (tag: Tag): (self: Context) => S /** * Get a service from the context that corresponds to the given tag. * This function is unsafe because if the tag is not present in the context, a runtime error will be thrown. * * For a safer version see {@link getOption}. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.unsafeGet(Services, Port), { PORT: 8080 }) * assert.throws(() => Context.unsafeGet(Services, Timeout)) * ``` * * @since 2.0.0 * @category unsafe */ (self: Context, tag: Tag): S } = internal.unsafeGet /** * Get the value associated with the specified tag from the context wrapped in an `Option` object. If the tag is not * found, the `Option` object will be `None`. * * @example * ```ts * import * as assert from "node:assert" * import { Context, Option } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.getOption(Services, Port), Option.some({ PORT: 8080 })) * assert.deepStrictEqual(Context.getOption(Services, Timeout), Option.none()) * ``` * * @since 2.0.0 * @category getters */ export const getOption: { /** * Get the value associated with the specified tag from the context wrapped in an `Option` object. If the tag is not * found, the `Option` object will be `None`. * * @example * ```ts * import * as assert from "node:assert" * import { Context, Option } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.getOption(Services, Port), Option.some({ PORT: 8080 })) * assert.deepStrictEqual(Context.getOption(Services, Timeout), Option.none()) * ``` * * @since 2.0.0 * @category getters */ (tag: Tag): (self: Context) => Option /** * Get the value associated with the specified tag from the context wrapped in an `Option` object. If the tag is not * found, the `Option` object will be `None`. * * @example * ```ts * import * as assert from "node:assert" * import { Context, Option } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.getOption(Services, Port), Option.some({ PORT: 8080 })) * assert.deepStrictEqual(Context.getOption(Services, Timeout), Option.none()) * ``` * * @since 2.0.0 * @category getters */ (self: Context, tag: Tag): Option } = internal.getOption /** * Merges two `Context`s, returning a new `Context` containing the services of both. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const firstContext = Context.make(Port, { PORT: 8080 }) * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 }) * * const Services = Context.merge(firstContext, secondContext) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 */ export const merge: { /** * Merges two `Context`s, returning a new `Context` containing the services of both. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const firstContext = Context.make(Port, { PORT: 8080 }) * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 }) * * const Services = Context.merge(firstContext, secondContext) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 */ (that: Context): (self: Context) => Context /** * Merges two `Context`s, returning a new `Context` containing the services of both. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const firstContext = Context.make(Port, { PORT: 8080 }) * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 }) * * const Services = Context.merge(firstContext, secondContext) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * ``` * * @since 2.0.0 */ (self: Context, that: Context): Context } = internal.merge /** * Merges any number of `Context`s, returning a new `Context` containing the services of all. * * @example * ```ts * import * as assert from "node:assert" * import { Context } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * const Host = Context.GenericTag<{ HOST: string }>("Host") * * const firstContext = Context.make(Port, { PORT: 8080 }) * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 }) * const thirdContext = Context.make(Host, { HOST: "localhost" }) * * const Services = Context.mergeAll(firstContext, secondContext, thirdContext) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * assert.deepStrictEqual(Context.get(Services, Host), { HOST: "localhost" }) * ``` * * @since 3.12.0 */ export const mergeAll: >( ...ctxs: [...{ [K in keyof T]: Context }] ) => Context = internal.mergeAll /** * Returns a new `Context` that contains only the specified services. * * @example * ```ts * import * as assert from "node:assert" * import { pipe, Context, Option } from "effect" * * const Port = Context.GenericTag<{ PORT: number }>("Port") * const Timeout = Context.GenericTag<{ TIMEOUT: number }>("Timeout") * * const someContext = pipe( * Context.make(Port, { PORT: 8080 }), * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * const Services = pipe(someContext, Context.pick(Port)) * * assert.deepStrictEqual(Context.getOption(Services, Port), Option.some({ PORT: 8080 })) * assert.deepStrictEqual(Context.getOption(Services, Timeout), Option.none()) * ``` * * @since 2.0.0 */ export const pick: >>( ...tags: Tags ) => (self: Context) => Context> = internal.pick /** * @since 2.0.0 */ export const omit: >>( ...tags: Tags ) => (self: Context) => Context>> = internal.omit /** * @example * ```ts * import * as assert from "node:assert" * import { Context, Layer } from "effect" * * class MyTag extends Context.Tag("MyTag")< * MyTag, * { readonly myNum: number } * >() { * static Live = Layer.succeed(this, { myNum: 108 }) * } * ``` * * @since 2.0.0 * @category constructors */ export const Tag: (id: Id) => () => TagClass = internal.Tag /** * Creates a context tag with a default value. * * **Details** * * `Context.Reference` allows you to create a tag that can hold a value. You can * provide a default value for the service, which will automatically be used * when the context is accessed, or override it with a custom implementation * when needed. * * **Example** (Declaring a Tag with a default value) * * ```ts * import * as assert from "node:assert" * import { Context, Effect } from "effect" * * class SpecialNumber extends Context.Reference()( * "SpecialNumber", * { defaultValue: () => 2048 } * ) {} * * // ┌─── Effect * // ▼ * const program = Effect.gen(function* () { * const specialNumber = yield* SpecialNumber * console.log(`The special number is ${specialNumber}`) * }) * * // No need to provide the SpecialNumber implementation * Effect.runPromise(program) * // Output: The special number is 2048 * ``` * * **Example** (Overriding the default value) * * ```ts * import { Context, Effect } from "effect" * * class SpecialNumber extends Context.Reference()( * "SpecialNumber", * { defaultValue: () => 2048 } * ) {} * * const program = Effect.gen(function* () { * const specialNumber = yield* SpecialNumber * console.log(`The special number is ${specialNumber}`) * }) * * Effect.runPromise(program.pipe(Effect.provideService(SpecialNumber, -1))) * // Output: The special number is -1 * ``` * * @since 3.11.0 * @category constructors * @experimental */ export const Reference: () => ( id: Id, options: { readonly defaultValue: () => Service } ) => ReferenceClass = internal.Reference