import type * as C from "@effect/data/Context" import * as Equal from "@effect/data/Equal" import { dual } from "@effect/data/Function" import { globalValue } from "@effect/data/GlobalValue" import * as Hash from "@effect/data/Hash" import { NodeInspectSymbol, toJSON, toString } from "@effect/data/Inspectable" import { type Effectable, EffectTypeId, effectVariance } from "@effect/data/internal/Effect" import type * as O from "@effect/data/Option" import * as option from "@effect/data/Option" import { pipeArguments } from "@effect/data/Pipeable" /** @internal */ export const TagTypeId: C.TagTypeId = Symbol.for("@effect/data/Context/Tag") as C.TagTypeId /** @internal */ export const TagProto: C.Tag & Effectable = { _tag: "Tag", [EffectTypeId]: effectVariance, [TagTypeId]: { _S: (_: unknown) => _, _I: (_: unknown) => _ }, [Equal.symbol](this: {}, that: unknown) { return this === that }, [Hash.symbol](this: {}) { return Hash.random(this) }, toString() { return toString(this.toJSON()) }, toJSON(this: C.Tag) { return { _id: "Tag", identifier: this.identifier, stack: this.stack } }, [NodeInspectSymbol]() { return this.toJSON() }, pipe() { return pipeArguments(this, arguments) }, of(self: Service): Service { return self }, context( this: C.Tag, self: Service ): C.Context { return make(this, self) } } const tagRegistry = globalValue("@effect/data/Context/Tag/tagRegistry", () => new Map>()) /** @internal */ export const makeTag = (identifier?: unknown): C.Tag => { if (identifier && tagRegistry.has(identifier)) { return tagRegistry.get(identifier)! } const limit = Error.stackTraceLimit Error.stackTraceLimit = 2 const creationError = new Error() Error.stackTraceLimit = limit const tag = Object.create(TagProto) Object.defineProperty(tag, "stack", { get() { return creationError.stack } }) if (identifier) { tag.identifier = identifier tagRegistry.set(identifier, tag) } return tag } /** @internal */ export const TypeId: C.TypeId = Symbol.for("@effect/data/Context") as C.TypeId /** @internal */ export const ContextProto: Omit, "unsafeMap"> = { [TypeId]: { _S: (_: unknown) => _ }, [Equal.symbol](this: C.Context, that: unknown): boolean { if (isContext(that)) { if (this.unsafeMap.size === that.unsafeMap.size) { for (const k of this.unsafeMap.keys()) { if (!that.unsafeMap.has(k) || !Equal.equals(this.unsafeMap.get(k), that.unsafeMap.get(k))) { return false } } return true } } return false }, [Hash.symbol](this: C.Context): number { return Hash.number(this.unsafeMap.size) }, pipe(this: C.Context) { return pipeArguments(this, arguments) }, toString() { return toString(this.toJSON()) }, toJSON(this: C.Context) { return { _id: "Context", services: Array.from(this.unsafeMap).map(toJSON) } }, [NodeInspectSymbol]() { return (this as any).toJSON() } } /** @internal */ export const makeContext = (unsafeMap: Map, any>): C.Context => { const context = Object.create(ContextProto) context.unsafeMap = unsafeMap return context } const serviceNotFoundError = (tag: C.Tag) => { const error = new Error(`Service not found${tag.identifier ? `: ${String(tag.identifier)}` : ""}`) if (tag.stack) { const lines = tag.stack.split("\n") if (lines.length > 2) { const afterAt = lines[2].match(/at (.*)/) if (afterAt) { error.message = error.message + ` (defined at ${afterAt[1]})` } } } if (error.stack) { const lines = error.stack.split("\n") lines.splice(1, 3) error.stack = lines.join("\n") } return error } /** @internal */ export const isContext = (u: unknown): u is C.Context => typeof u === "object" && u !== null && TypeId in u /** @internal */ export const isTag = (u: unknown): u is C.Tag => typeof u === "object" && u !== null && TagTypeId in u const _empty = makeContext(new Map()) /** @internal */ export const empty = (): C.Context => _empty /** @internal */ export const make = >( tag: T, service: C.Tag.Service ): C.Context> => makeContext(new Map([[tag, service]])) /** @internal */ export const add = dual< >( tag: T, service: C.Tag.Service ) => ( self: C.Context ) => C.Context>, >( self: C.Context, tag: T, service: C.Tag.Service ) => C.Context> >(3, (self, tag, service) => { const map = new Map(self.unsafeMap) map.set(tag as C.Tag, service) return makeContext(map) }) /** @internal */ export const unsafeGet = dual< (tag: C.Tag) => (self: C.Context) => S, (self: C.Context, tag: C.Tag) => S >(2, (self, tag) => { if (!self.unsafeMap.has(tag)) { throw serviceNotFoundError(tag as any) } return self.unsafeMap.get(tag)! as any }) /** @internal */ export const get: { >(tag: T): (self: C.Context) => C.Tag.Service >(self: C.Context, tag: T): C.Tag.Service } = unsafeGet /** @internal */ export const getOption = dual< (tag: C.Tag) => (self: C.Context) => O.Option, (self: C.Context, tag: C.Tag) => O.Option >(2, (self, tag) => { if (!self.unsafeMap.has(tag)) { return option.none() } return option.some(self.unsafeMap.get(tag)! as any) }) /** @internal */ export const merge = dual< (that: C.Context) => (self: C.Context) => C.Context, (self: C.Context, that: C.Context) => C.Context >(2, (self, that) => { const map = new Map(self.unsafeMap) for (const [tag, s] of that.unsafeMap) { map.set(tag, s) } return makeContext(map) }) /** @internal */ export const pick = >>(...tags: S) => (self: C.Context): C.Context< { [k in keyof S]: C.Tag.Identifier }[number] > => { const tagSet = new Set(tags) const newEnv = new Map() for (const [tag, s] of self.unsafeMap.entries()) { if (tagSet.has(tag)) { newEnv.set(tag, s) } } return makeContext<{ [k in keyof S]: C.Tag.Identifier }[number]>(newEnv) } /** @internal */ export const omit = >>(...tags: S) => (self: C.Context): C.Context< Exclude }[keyof S]> > => { const newEnv = new Map(self.unsafeMap) for (const tag of tags) { newEnv.delete(tag) } return makeContext(newEnv) }