/** * 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 1.0.0 */ import type { Equal } from "@effect/data/Equal" import type { Inspectable } from "@effect/data/Inspectable" import * as C from "@effect/data/internal/Context" import type { Option } from "@effect/data/Option" import type { Pipeable } from "@effect/data/Pipeable" import type * as Unify from "@effect/data/Unify" const TagTypeId: unique symbol = C.TagTypeId /** * @since 1.0.0 * @category symbol */ export type TagTypeId = typeof TagTypeId /** * @since 1.0.0 * @category models */ export interface Tag extends Pipeable, Inspectable { readonly _tag: "Tag" readonly [TagTypeId]: { readonly _S: (_: Service) => Service readonly _I: (_: Identifier) => Identifier } of(self: Service): Service context(self: Service): Context readonly stack?: string | undefined readonly identifier?: unknown | undefined [Unify.typeSymbol]?: unknown [Unify.unifySymbol]?: TagUnify [Unify.blacklistSymbol]?: TagUnifyBlacklist } /** * @category models * @since 1.0.0 */ export interface TagUnify { Tag?: () => A[Unify.typeSymbol] extends Tag | infer _ ? Tag : never } /** * @category models * @since 1.0.0 */ export interface TagUnifyBlacklist {} /** * @since 1.0.0 */ export declare namespace Tag { /** * @since 1.0.0 */ export type Service> = T extends Tag ? A : never /** * @since 1.0.0 */ export type Identifier> = T extends Tag ? A : never } /** * Creates a new `Tag` instance with an optional key parameter. * * Specifying the `key` will make the `Tag` global, meaning two tags with the same * key will map to the same instance. * * Note: this is useful for cases where live reload can happen and it is * desireable to preserve the instance across reloads. * * @param key - An optional key that makes the `Tag` global. * * @example * import * as Context from "@effect/data/Context" * * assert.strictEqual(Context.Tag() === Context.Tag(), false) * assert.strictEqual(Context.Tag("PORT") === Context.Tag("PORT"), true) * * @since 1.0.0 * @category constructors */ export const Tag: (identifier?: unknown) => Tag = C.makeTag const TypeId: unique symbol = C.TypeId as TypeId /** * @since 1.0.0 * @category symbol */ export type TypeId = typeof TypeId /** * @since 1.0.0 * @category models */ export type ValidTagsById = R extends infer S ? Tag : never /** * @since 1.0.0 * @category models */ export interface Context extends Equal, Pipeable, Inspectable { readonly [TypeId]: { readonly _S: (_: Services) => unknown } readonly unsafeMap: Map, any> } /** * @since 1.0.0 * @category constructors */ export const unsafeMake: (unsafeMap: Map, any>) => Context = C.makeContext /** * Checks if the provided argument is a `Context`. * * @param input - The value to be checked if it is a `Context`. * * @example * import * as Context from "@effect/data/Context" * * assert.strictEqual(Context.isContext(Context.empty()), true) * * @since 1.0.0 * @category guards */ export const isContext: (input: unknown) => input is Context = C.isContext /** * Checks if the provided argument is a `Tag`. * * @param input - The value to be checked if it is a `Tag`. * * @example * import * as Context from "@effect/data/Context" * * assert.strictEqual(Context.isTag(Context.Tag()), true) * * @since 1.0.0 * @category guards */ export const isTag: (input: unknown) => input is Tag = C.isTag /** * Returns an empty `Context`. * * @example * import * as Context from "@effect/data/Context" * * assert.strictEqual(Context.isContext(Context.empty()), true) * * @since 1.0.0 * @category constructors */ export const empty: () => Context = C.empty /** * Creates a new `Context` with a single service associated to the tag. * * @example * import * as Context from "@effect/data/Context" * * const Port = Context.Tag<{ PORT: number }>() * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 }) * * @since 1.0.0 * @category constructors */ export const make: >(tag: T, service: Tag.Service) => Context> = C.make /** * Adds a service to a given `Context`. * * @example * import * as Context from "@effect/data/Context" * import { pipe } from "@effect/data/Function" * * const Port = Context.Tag<{ PORT: number }>() * const Timeout = Context.Tag<{ TIMEOUT: number }>() * * 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 1.0.0 */ export const add: { >( tag: T, service: Tag.Service ): (self: Context) => Context> >( self: Context, tag: T, service: Tag.Service ): Context> } = C.add /** * Get a service from the context that corresponds to the given tag. * * @param self - The `Context` to search for the service. * @param tag - The `Tag` of the service to retrieve. * * @example * import * as Context from "@effect/data/Context" * import { pipe } from "@effect/data/Function" * * const Port = Context.Tag<{ PORT: number }>() * const Timeout = Context.Tag<{ TIMEOUT: number }>() * * const Services = pipe( * Context.make(Port, { PORT: 8080 }), * Context.add(Timeout, { TIMEOUT: 5000 }) * ) * * assert.deepStrictEqual(Context.get(Services, Timeout), { TIMEOUT: 5000 }) * * @since 1.0.0 * @category getters */ export const get: { >(tag: T): (self: Context) => Tag.Service >(self: Context, tag: T): Tag.Service } = C.get /** * 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}. * * @param self - The `Context` to search for the service. * @param tag - The `Tag` of the service to retrieve. * * @example * import * as Context from "@effect/data/Context" * * const Port = Context.Tag<{ PORT: number }>() * const Timeout = Context.Tag<{ TIMEOUT: number }>() * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.unsafeGet(Services, Port), { PORT: 8080 }) * assert.throws(() => Context.unsafeGet(Services, Timeout)) * * @since 1.0.0 * @category unsafe */ export const unsafeGet: { (tag: Tag): (self: Context) => S (self: Context, tag: Tag): S } = C.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`. * * @param self - The `Context` to search for the service. * @param tag - The `Tag` of the service to retrieve. * * @example * import * as Context from "@effect/data/Context" * import * as O from "@effect/data/Option" * * const Port = Context.Tag<{ PORT: number }>() * const Timeout = Context.Tag<{ TIMEOUT: number }>() * * const Services = Context.make(Port, { PORT: 8080 }) * * assert.deepStrictEqual(Context.getOption(Services, Port), O.some({ PORT: 8080 })) * assert.deepStrictEqual(Context.getOption(Services, Timeout), O.none()) * * @since 1.0.0 * @category getters */ export const getOption: { (tag: Tag): (self: Context) => Option (self: Context, tag: Tag): Option } = C.getOption /** * Merges two `Context`s, returning a new `Context` containing the services of both. * * @param self - The first `Context` to merge. * @param that - The second `Context` to merge. * * @example * import * as Context from "@effect/data/Context" * * const Port = Context.Tag<{ PORT: number }>() * const Timeout = Context.Tag<{ TIMEOUT: number }>() * * 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 1.0.0 */ export const merge: { (that: Context): (self: Context) => Context (self: Context, that: Context): Context } = C.merge /** * Returns a new `Context` that contains only the specified services. * * @param self - The `Context` to prune services from. * @param tags - The list of `Tag`s to be included in the new `Context`. * * @example * import * as Context from "@effect/data/Context" * import { pipe } from "@effect/data/Function" * import * as O from "@effect/data/Option" * * const Port = Context.Tag<{ PORT: number }>() * const Timeout = Context.Tag<{ TIMEOUT: number }>() * * 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), O.some({ PORT: 8080 })) * assert.deepStrictEqual(Context.getOption(Services, Timeout), O.none()) * * @since 1.0.0 */ export const pick: >>( ...tags: S ) => (self: Context) => Context<{ [k in keyof S]: Tag.Identifier }[number]> = C.pick /** * @since 1.0.0 */ export const omit: >>( ...tags: S ) => (self: Context) => Context }[keyof S]>> = C.omit