1/**
2 * Stores Effect services in typed maps.
3 *
4 * A `Context` holds service implementations under `Context.Service` or
5 * `Context.Reference` keys, and its type records which keys are present.
6 * Effects use contexts as their environment, so services can be provided once
7 * instead of passed through every function call. This module includes helpers
8 * for creating keys, building contexts, adding and reading services, merging
9 * contexts, and selecting or removing services.
10 *
11 * @since 4.0.0
12 */
13import type { Effect, EffectIterator } from "./Effect.ts"
14import * as Effectable from "./Effectable.ts"
15import * as Equal from "./Equal.ts"
16import { dual, type LazyArg } from "./Function.ts"
17import * as Hash from "./Hash.ts"
18import type { Inspectable } from "./Inspectable.ts"
19import { exitSucceed, PipeInspectableProto, withFiber } from "./internal/core.ts"
20import * as Option from "./Option.ts"
21import type { Pipeable } from "./Pipeable.ts"
22import { hasProperty } from "./Predicate.ts"
23import type * as Types from "./Types.ts"
24
25/**
26 * String literal type used as the runtime type identifier for `Context`
27 * service keys.
28 *
29 * @category type IDs
30 * @since 4.0.0
31 */
32export type ServiceTypeId = "~effect/Context/Service"
33
34/**
35 * Runtime type identifier attached to `Context` service keys and used by
36 * `isKey` to recognize them.
37 *
38 * @category type IDs
39 * @since 4.0.0
40 */
41export const ServiceTypeId: ServiceTypeId = "~effect/Context/Service"
42
43/**
44 * Typed identifier for a service stored in a `Context`.
45 *
46 * **When to use**
47 *
48 * Use as the typed handle for storing, retrieving, and requiring a specific
49 * service in a `Context`.
50 *
51 * **Details**
52 *
53 * `Identifier` tracks the requirement in Effect types, while `Shape` is the
54 * service implementation retrieved by the key. A key is also an Effect value,
55 * so yielding it inside `Effect.gen` retrieves the service from the current
56 * fiber context.
57 *
58 * @see {@link Service} for creating required service keys
59 * @see {@link Reference} for creating service keys with default values
60 *
61 * @category models
62 * @since 4.0.0
63 */
64export interface Key<out Identifier, out Shape> extends Effect<Shape, never, Identifier> {
65 readonly [ServiceTypeId]: ServiceTypeId
66 readonly Service: Shape
67 readonly Identifier: Identifier
68 readonly key: string
69}
70
71/**
72 * Context key with helper methods for working with a service.
73 *
74 * **Details**
75 *
76 * `context` creates a one-service `Context`, `use` and `useSync` retrieve the
77 * service from the current Effect context before applying a function, and `of`
78 * is a type-level helper for service values.
79 *
80 * **Example** (Defining a service key)
81 *
82 * ```ts import.meta.vitest
83 * import { Context } from "effect"
84 *
85 * // Define an identifier for a database service
86 * const Database = Context.Service<{ query: (sql: string) => string }>(
87 * "Database"
88 * )
89 *
90 * // The key can be used to store and retrieve services
91 * const context = Context.make(Database, { query: (sql) => `Result: ${sql}` })
92 * Context.get(context, Database).query("SELECT 1") // => "Result: SELECT 1"
93 * ```
94 *
95 * @category services
96 * @since 4.0.0
97 */
98export interface Service<in out Identifier, in out Shape> extends Key<Identifier, Shape> {
99 of(this: void, self: Shape): Shape
100 context(self: Shape): Context<Identifier>
101 use<A, E, R>(f: (service: Shape) => Effect<A, E, R>): Effect<A, E, R | Identifier>
102 useSync<A>(f: (service: Shape) => A): Effect<A, never, Identifier>
103}
104
105/**
106 * Class-style service key produced by `Context.Service<Self, Shape>()("Id")`.
107 *
108 * **When to use**
109 *
110 * Use when declaring a service as a class so the class value can serve as the
111 * `Context` key.
112 *
113 * **Details**
114 *
115 * The class itself is the `Context` key, and its string `key` identifies the
116 * service at runtime.
117 *
118 * @see {@link Service} for creating function-style keys or class-style service keys
119 *
120 * @category services
121 * @since 4.0.0
122 */
123export interface ServiceClass<in out Self, in out Identifier extends string, in out Shape>
124 extends Service<Self, Shape>
125{
126 new(_: never): ServiceClass.Shape<Identifier, Shape>
127 readonly key: Identifier
128}
129
130/**
131 * Namespace containing helper types for class-style `Context.Service`
132 * declarations.
133 *
134 * @since 4.0.0
135 */
136export declare namespace ServiceClass {
137 /**
138 * Runtime and type-level metadata carried by a class-style service key,
139 * including its service type identifier, string key, and service shape.
140 *
141 * @category services
142 * @since 4.0.0
143 */
144 export interface Shape<Identifier extends string, Service> {
145 readonly [ServiceTypeId]: typeof ServiceTypeId
146 readonly key: Identifier
147 readonly Service: Service
148 }
149}
150
151/**
152 * Creates a `Context` service key.
153 *
154 * **When to use**
155 *
156 * Use when you need to define a context service key for a dependency that must
157 * be provided by the surrounding context.
158 *
159 * **Details**
160 *
161 * Call `Context.Service("Key")` for a function-style key, or use the two-stage
162 * form `Context.Service<Self, Shape>()("Key")` for class-style service
163 * declarations. The returned key can be yielded as an Effect and passed to
164 * `Context.make`, `Context.add`, and the Context getter functions.
165 *
166 * **Gotchas**
167 *
168 * The string key is the runtime identity of the service. Reusing the same key
169 * string for unrelated services makes them occupy the same slot in a
170 * `Context`.
171 *
172 * **Example** (Creating service keys)
173 *
174 * ```ts import.meta.vitest
175 * import { Context } from "effect"
176 *
177 * // Create a simple service
178 * const Database = Context.Service<{
179 * query: (sql: string) => string
180 * }>("Database")
181 *
182 * // Create a service class
183 * class Config extends Context.Service<Config, {
184 * port: number
185 * }>()("Config") {}
186 *
187 * // Use the services to create contexts
188 * const db = Context.make(Database, {
189 * query: (sql) => `Result: ${sql}`
190 * })
191 * const config = Context.make(Config, { port: 8080 })
192 * Context.get(db, Database).query("SELECT 1") // => "Result: SELECT 1"
193 * Context.get(config, Config).port // => 8080
194 * ```
195 *
196 * @see {@link Reference} for service keys with default values
197 *
198 * @category services
199 * @since 4.0.0
200 */
201export const Service: {
202 <Identifier, Shape = Identifier>(
203 key: string,
204 options?: {
205 /** @internal */
206 readonly fiberCached?: boolean | undefined
207 } | undefined
208 ): Service<Identifier, Shape>
209 <Self, Shape>(): <
210 const Identifier extends string,
211 E,
212 R = Types.unassigned,
213 Args extends ReadonlyArray<any> = never
214 >(
215 id: Identifier,
216 options?: {
217 readonly make?: ((...args: Args) => Effect<Shape, E, R>) | Effect<Shape, E, R> | undefined
218 /** @internal */
219 readonly fiberCached?: boolean | undefined
220 } | undefined
221 ) =>
222 & ServiceClass<Self, Identifier, Shape>
223 & ([Types.unassigned] extends [R] ? unknown
224 : { readonly make: [Args] extends [never] ? Effect<Shape, E, R> : (...args: Args) => Effect<Shape, E, R> })
225 <Self>(): <
226 const Identifier extends string,
227 Make extends Effect<any, any, any> | ((...args: any) => Effect<any, any, any>)
228 >(
229 id: Identifier,
230 options: {
231 readonly make: Make
232 /** @internal */
233 readonly fiberCached?: boolean | undefined
234 }
235 ) =>
236 & ServiceClass<
237 Self,
238 Identifier,
239 Make extends
240 Effect<infer _A, infer _E, infer _R> | ((...args: infer _Args) => Effect<infer _A, infer _E, infer _R>) ? _A
241 : never
242 >
243 & { readonly make: Make }
244} = function() {
245 function KeyClass() {}
246 const self = KeyClass as any as Types.Mutable<Reference<any>>
247 Object.setPrototypeOf(self, ServiceProto)
248 const init = (key: string, options?: {
249 readonly defaultValue?: any
250 readonly make?: any
251 readonly fiberCached?: boolean
252 }) => {
253 self.key = key
254 if (options?.defaultValue) {
255 self[ReferenceTypeId] = ReferenceTypeId
256 self.defaultValue = options.defaultValue
257 }
258 if (options?.make) {
259 ;(self as any).make = options.make
260 }
261 if (options?.fiberCached) {
262 cacheKeys.add(key)
263 }
264 return self
265 }
266 return arguments.length > 0 ? init(arguments[0], arguments[1]) : init
267} as any
268
269const ServiceProto: any = {
270 [ServiceTypeId]: ServiceTypeId,
271 ...Effectable.Prototype<Service<never, any>>({
272 label: "Service",
273 evaluate(fiber) {
274 return exitSucceed(get(fiber.context, this))
275 }
276 }),
277 toJSON<I, A>(this: Service<I, A>) {
278 return {
279 _id: "Service",
280 key: this.key
281 }
282 },
283 of<Service>(this: void, self: Service): Service {
284 return self
285 },
286 context<Identifier, Shape>(
287 this: Service<Identifier, Shape>,
288 self: Shape
289 ): Context<Identifier> {
290 return make(this, self)
291 },
292 use<A, E, R>(this: Service<never, any>, f: (service: any) => Effect<A, E, R>): Effect<A, E, R> {
293 return withFiber((fiber) => f(get(fiber.context, this)))
294 },
295 useSync<A>(this: Service<never, any>, f: (service: any) => A): Effect<A, never, never> {
296 return withFiber((fiber) => exitSucceed(f(get(fiber.context, this))))
297 }
298}
299
300const cacheKeys = new Set<string>()
301
302const ReferenceTypeId = "~effect/Context/Reference" as const
303
304/**
305 * Service key with a lazily computed default value.
306 *
307 * **Details**
308 *
309 * When a `Reference` is requested from a `Context` that does not contain an
310 * override, Context getters that resolve references return the cached default
311 * value instead of failing.
312 *
313 * **Example** (Defining a reference with a default value)
314 *
315 * ```ts import.meta.vitest
316 * import { Context } from "effect"
317 *
318 * // Define a reference with a default value
319 * const messages: Array<string> = []
320 * const LoggerRef: Context.Reference<{ log: (msg: string) => void }> =
321 * Context.Reference("Logger", {
322 * defaultValue: () => ({ log: (msg) => { messages.push(msg) } })
323 * })
324 *
325 * // The reference can be used without explicit provision
326 * const context = Context.empty()
327 * const logger = Context.get(context, LoggerRef) // Uses default value
328 * logger.log("default logger")
329 * messages // => ["default logger"]
330 * ```
331 *
332 * @category services
333 * @since 3.11.0
334 */
335export interface Reference<in out Shape> extends Service<never, Shape> {
336 readonly [ReferenceTypeId]: typeof ReferenceTypeId
337 readonly defaultValue: () => Shape
338 [Symbol.iterator](): EffectIterator<Reference<Shape>>
339 new(_: never): {}
340}
341
342/**
343 * Namespace containing utility types for `Context` service keys.
344 *
345 * **Example** (Extracting service types)
346 *
347 * ```ts import.meta.vitest
348 * import { Context } from "effect"
349 *
350 * const Database = Context.Service<{
351 * query: (sql: string) => string
352 * }>("Database")
353 *
354 * // Extract service type from a key
355 * type DatabaseService = Context.Service.Shape<typeof Database>
356 *
357 * // Extract identifier type from a key
358 * type DatabaseId = Context.Service.Identifier<typeof Database>
359 *
360 * Database.key // => "Database"
361 * ```
362 *
363 * @since 2.0.0
364 */
365export declare namespace Service {
366 /**
367 * Type that matches any `Context` service key regardless of its identifier or
368 * service shape.
369 *
370 * **Example** (Typing any service key)
371 *
372 * ```ts import.meta.vitest
373 * import { Context } from "effect"
374 *
375 * // Any represents any possible service type
376 * const services: Array<Context.Service.Any> = [
377 * Context.Service<{ log: (msg: string) => void }>("Logger"),
378 * Context.Service<{ query: (sql: string) => string }>("Database")
379 * ]
380 * services.map((service) => service.key) // => ["Logger", "Database"]
381 * ```
382 *
383 * @category utility types
384 * @since 4.0.0
385 */
386 export type Any = Key<never, any> | Key<any, any>
387
388 /**
389 * Extracts the service implementation type stored behind a `Context` service
390 * key.
391 *
392 * **Example** (Extracting a service shape)
393 *
394 * ```ts import.meta.vitest
395 * import { Context } from "effect"
396 *
397 * const Database = Context.Service<{ query: (sql: string) => string }>(
398 * "Database"
399 * )
400 *
401 * // Extract the service shape from the service
402 * type DatabaseService = Context.Service.Shape<typeof Database>
403 * // DatabaseService is { query: (sql: string) => string }
404 * Database.key // => "Database"
405 * ```
406 *
407 * @category utility types
408 * @since 4.0.0
409 */
410 export type Shape<T> = T extends Key<infer _I, infer S> ? S : never
411
412 /**
413 * Extracts the identifier, or requirement type, associated with a `Context`
414 * service key.
415 *
416 * **Example** (Extracting a service identifier)
417 *
418 * ```ts import.meta.vitest
419 * import { Context } from "effect"
420 *
421 * const Database = Context.Service<{ query: (sql: string) => string }>(
422 * "Database"
423 * )
424 *
425 * // Extract the identifier type from a key
426 * type DatabaseId = Context.Service.Identifier<typeof Database>
427 * // DatabaseId is the identifier type
428 * Database.key // => "Database"
429 * ```
430 *
431 * @category utility types
432 * @since 2.0.0
433 */
434 export type Identifier<T> = T extends Key<infer I, infer _S> ? I : never
435}
436
437const TypeId = "~effect/Context" as const
438
439/**
440 * Immutable collection of service implementations used for dependency
441 * injection in Effect programs.
442 *
443 * **Details**
444 *
445 * The type parameter tracks the service identifiers available in the context.
446 * At runtime, services are stored by each key's string `key`.
447 *
448 * **Example** (Creating a context with multiple services)
449 *
450 * ```ts import.meta.vitest
451 * import { Context } from "effect"
452 *
453 * // Create a context with multiple services
454 * const Logger = Context.Service<{ log: (msg: string) => void }>("Logger")
455 * const Database = Context.Service<{ query: (sql: string) => string }>(
456 * "Database"
457 * )
458 *
459 * const context = Context.make(Logger, { log: (_msg: string) => {} })
460 * .pipe(Context.add(Database, { query: (sql) => `Result: ${sql}` }))
461 * Context.get(context, Database).query("SELECT 1") // => "Result: SELECT 1"
462 * ```
463 *
464 * @category models
465 * @since 2.0.0
466 */
467export interface Context<in Services> extends Equal.Equal, Pipeable, Inspectable {
468 readonly [TypeId]: {
469 readonly _Services: Types.Contravariant<Services>
470 }
471 readonly mapUnsafe: ReadonlyMap<string, any>
472}
473
474interface ContextImpl<in Services> extends Context<Services> {
475 cacheRoot: ContextImpl<any> | undefined
476 base: ReadonlyMap<string, any>
477 baseHits: number
478 overlay: Overlay | undefined
479 depth: number
480 _flat: ReadonlyMap<string, any> | undefined
481}
482
483interface Overlay {
484 readonly key: string
485 readonly value: unknown
486 readonly parent: Overlay | undefined
487}
488
489const MaxDepth = 8
490// Keep small bases cheap to read; larger bases are worth copying only after
491// enough fall-throughs to amortize the copy.
492const FlattenAfterBaseHits = 8
493
494const makeImpl = <Services>(
495 cacheRoot: ContextImpl<any> | undefined,
496 base: ReadonlyMap<string, any>,
497 overlay: Overlay | undefined,
498 depth: number
499): ContextImpl<Services> => {
500 const self: ContextImpl<Services> = Object.create(Proto)
501 self.cacheRoot = cacheRoot ?? self
502 self.base = base
503 self.overlay = overlay
504 self.depth = depth
505 self._flat = undefined
506 self.baseHits = 0
507 return self
508}
509
510const applyOverlays = (map: Map<string, any>, overlay: Overlay | undefined): void => {
511 if (!overlay) return
512 applyOverlays(map, overlay.parent)
513 map.set(overlay.key, overlay.value)
514}
515
516const flatten = (self: ContextImpl<any>): ReadonlyMap<string, any> => {
517 if (self._flat) return self._flat
518 if (!self.overlay) return self._flat = self.base
519 const map = new Map(self.base)
520 applyOverlays(map, self.overlay)
521 return self._flat = map
522}
523
524const withFlat = <B>(self: Context<any>, f: (map: Map<string, any>) => void): Context<B> => {
525 const map = new Map(self.mapUnsafe)
526 f(map)
527 return makeUnsafe(map)
528}
529
530// A private symbol so user code cannot forge a value that reads as absent
531const notFound = Symbol()
532
533const lookup = (self: Context<any>, key: string): unknown => {
534 const impl = self as ContextImpl<any>
535 for (let overlay = impl.overlay; overlay; overlay = overlay.parent) {
536 if (overlay.key === key) return overlay.value
537 }
538 const value = impl.base.get(key)
539 // Misses must not advance the counter: reference-default lookups miss the
540 // base on every fiber cache refresh, which would flatten every short-lived
541 // request context and reintroduce the O(services) per-request cost
542 if (value === undefined && !impl.base.has(key)) return notFound
543 if (impl.overlay && ++impl.baseHits >= impl.base.size && impl.baseHits >= FlattenAfterBaseHits) {
544 impl.base = flatten(impl)
545 impl.overlay = undefined
546 impl.depth = 0
547 }
548 return value
549}
550
551/**
552 * Creates a `Context` from an existing service map.
553 *
554 * **When to use**
555 *
556 * Use when constructing a low-level `Context` from a trusted map whose lifecycle
557 * you control.
558 *
559 * **Gotchas**
560 *
561 * The provided map is retained without copying and must not be mutated after
562 * construction. Prefer `empty`, `make`, `add`, or `merge` for normal Context
563 * construction.
564 *
565 * **Example** (Creating a context from a map)
566 *
567 * ```ts import.meta.vitest
568 * import { Context } from "effect"
569 *
570 * // Create a context from a Map (unsafe)
571 * const map = new Map([
572 * ["Logger", { log: (_msg: string) => {} }]
573 * ])
574 *
575 * const context = Context.makeUnsafe(map)
576 * context.mapUnsafe.size // => 1
577 * ```
578 *
579 * @category constructors
580 * @since 4.0.0
581 */
582export const makeUnsafe = <Services = never>(mapUnsafe: ReadonlyMap<string, any>): Context<Services> =>
583 makeImpl(undefined, mapUnsafe, undefined, 0)
584
585const Proto: Omit<
586 ContextImpl<never>,
587 "cacheRoot" | "base" | "overlay" | "depth" | "_flat" | "baseHits"
588> = {
589 get mapUnsafe() {
590 return flatten(this as any as ContextImpl<any>)
591 },
592 ...PipeInspectableProto,
593 [TypeId]: {
594 _Services: (_: never) => _
595 },
596 toJSON(this: Context<never>) {
597 return {
598 _id: "Context",
599 services: Array.from(this.mapUnsafe).map(([key, value]) => ({ key, value }))
600 }
601 },
602 [Equal.symbol]<A>(this: Context<A>, that: unknown): boolean {
603 if (!isContext(that)) return false
604 const self = this.mapUnsafe
605 const other = that.mapUnsafe
606 if (self.size !== other.size) return false
607 for (const [key, value] of self) {
608 if (!other.has(key) || !Equal.equals(value, other.get(key))) return false
609 }
610 return true
611 },
612 [Hash.symbol]<A>(this: Context<A>): number {
613 return Hash.number(this.mapUnsafe.size)
614 }
615}
616
617/** @internal */
618export const hasSameCache = <Services, Services2>(
619 self: Context<Services>,
620 that: Context<Services2>
621): boolean => (self as ContextImpl<Services>).cacheRoot === (that as ContextImpl<Services2>).cacheRoot
622
623/**
624 * Checks whether the provided argument is a `Context`.
625 *
626 * **When to use**
627 *
628 * Use to narrow an unknown value before passing it to APIs that require a
629 * `Context`.
630 *
631 * **Details**
632 *
633 * This checks the runtime `Context` marker and does not inspect which services
634 * the context contains.
635 *
636 * **Gotchas**
637 *
638 * This guard only proves that the value is a `Context`; it does not prove that
639 * any specific service is present.
640 *
641 * **Example** (Checking for contexts)
642 *
643 * ```ts import.meta.vitest
644 * import { Context } from "effect"
645 * Context.isContext(Context.empty()) // => true
646 * ```
647 *
648 * @see {@link isKey} for checking service keys
649 * @see {@link isReference} for checking references with defaults
650 *
651 * @category guards
652 * @since 2.0.0
653 */
654export const isContext = (u: unknown): u is Context<never> => hasProperty(u, TypeId)
655
656/**
657 * Checks whether the provided argument is a `Key`.
658 *
659 * **Example** (Checking for keys)
660 *
661 * ```ts import.meta.vitest
662 * import { Context } from "effect"
663 * Context.isKey(Context.Service("Service")) // => true
664 * ```
665 *
666 * @category guards
667 * @since 4.0.0
668 */
669export const isKey = (u: unknown): u is Key<any, any> => hasProperty(u, ServiceTypeId)
670
671/**
672 * Checks whether the provided argument is a `Reference`.
673 *
674 * **Example** (Checking for references)
675 *
676 * ```ts import.meta.vitest
677 * import { Context } from "effect"
678 *
679 * const LoggerRef = Context.Reference("Logger", {
680 * defaultValue: () => ({ log: (_msg: string) => {} })
681 * })
682 *
683 * Context.isReference(LoggerRef) // => true
684 * Context.isReference(Context.Service("Key")) // => false
685 * ```
686 *
687 * @category guards
688 * @since 3.11.0
689 */
690export const isReference = <I, S>(u: Key<I, S>): u is Reference<S> => !!(u as Reference<S>)[ReferenceTypeId]
691
692/**
693 * Returns an empty `Context`.
694 *
695 * **Example** (Creating an empty context)
696 *
697 * ```ts import.meta.vitest
698 * import { Context } from "effect"
699 * Context.empty().mapUnsafe.size // => 0
700 * ```
701 *
702 * @category constructors
703 * @since 2.0.0
704 */
705export const empty = (): Context<never> => emptyContext
706const emptyContext = makeUnsafe(new Map())
707
708/**
709 * Creates a new `Context` with a single service associated to the key.
710 *
711 * **Example** (Creating a context with one service)
712 *
713 * ```ts import.meta.vitest
714 * import { Context } from "effect"
715 *
716 * const Port = Context.Service<{ PORT: number }>("Port")
717 *
718 * const context = Context.make(Port, { PORT: 8080 })
719 *
720 * Context.get(context, Port).PORT // => 8080
721 * ```
722 *
723 * @category constructors
724 * @since 2.0.0
725 */
726export const make = <I, S>(
727 key: Key<I, S>,
728 service: Types.NoInfer<S>
729): Context<I> => makeUnsafe(new Map([[key.key, service]]))
730
731/**
732 * Adds a service to a given `Context`.
733 *
734 * **When to use**
735 *
736 * Use when you need to store a known service value in a `Context`.
737 *
738 * **Details**
739 *
740 * If the context already contains the same service key, the new service
741 * replaces the previous one.
742 *
743 * **Example** (Adding a service to a context)
744 *
745 * ```ts import.meta.vitest
746 * import { Context, pipe } from "effect"
747 *
748 * const Port = Context.Service<{ PORT: number }>("Port")
749 * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
750 *
751 * const someContext = Context.make(Port, { PORT: 8080 })
752 *
753 * const context = pipe(
754 * someContext,
755 * Context.add(Timeout, { TIMEOUT: 5000 })
756 * )
757 *
758 * const values = [Context.get(context, Port).PORT, Context.get(context, Timeout).TIMEOUT]
759 * values // => [8080, 5000]
760 * ```
761 *
762 * @see {@link addOrOmit} for adding or removing a service from an `Option`
763 *
764 * @category combining
765 * @since 2.0.0
766 */
767export const add: {
768 <I, S>(
769 key: Key<I, S>,
770 service: Types.NoInfer<S>
771 ): <Services>(self: Context<Services>) => Context<Services | I>
772 <Services, I, S>(
773 self: Context<Services>,
774 key: Key<I, S>,
775 service: Types.NoInfer<S>
776 ): Context<Services | I>
777} = dual(3, <Services, I, S>(
778 self: Context<Services>,
779 key: Key<I, S>,
780 service: Types.NoInfer<S>
781): Context<Services | I> => addUnsafe(self, key.key, service))
782
783/**
784 * Adds a service by key to a given `Context` using a string key.
785 *
786 * @category combining
787 * @since 4.0.0
788 */
789export const addUnsafe = <Services, I, S>(
790 self: Context<Services>,
791 key: string,
792 service: Types.NoInfer<S>
793): Context<Services | I> => {
794 const impl = self as ContextImpl<Services>
795 const cacheRoot = cacheKeys.has(key) ? undefined : impl.cacheRoot
796 if (impl.depth >= MaxDepth) {
797 // Rebase the overlay chain into a flat map, keeping the cacheRoot so a
798 // rebase on an ordinary key does not invalidate fiber caches
799 const map = new Map(impl.mapUnsafe)
800 map.set(key, service)
801 return makeImpl(cacheRoot, map, undefined, 0)
802 }
803
804 return makeImpl(
805 cacheRoot,
806 impl.base,
807 { key, value: service, parent: impl.overlay },
808 impl.depth + 1
809 )
810}
811
812/**
813 * Adds or removes a service depending on an `Option`.
814 *
815 * **When to use**
816 *
817 * Use when you need to add or omit a `Context` service based on an `Option`.
818 *
819 * **Details**
820 *
821 * When `service` is `Option.some`, the value is stored for the key. When it is
822 * `Option.none`, the key is removed from the returned `Context`.
823 *
824 * **Example** (Adding optional services)
825 *
826 * ```ts import.meta.vitest
827 * import { Context, Option } from "effect"
828 *
829 * const Port = Context.Service<{ PORT: number }>("Port")
830 *
831 * const withPort = Context.empty().pipe(
832 * Context.addOrOmit(Port, Option.some({ PORT: 8080 }))
833 * )
834 *
835 * const withoutPort = withPort.pipe(
836 * Context.addOrOmit(Port, Option.none())
837 * )
838 * Context.getOption(withPort, Port) // => Option.some({ PORT: 8080 })
839 * Context.getOption(withoutPort, Port) // => Option.none()
840 * ```
841 *
842 * @see {@link add} for always storing a service value
843 *
844 * @category combining
845 * @since 4.0.0
846 */
847export const addOrOmit: {
848 <I, S>(
849 key: Key<I, S>,
850 service: Option.Option<Types.NoInfer<S>>
851 ): <Services>(self: Context<Services>) => Context<Exclude<Services, I>>
852 <Services, I, S>(
853 self: Context<Services>,
854 key: Key<I, S>,
855 service: Option.Option<Types.NoInfer<S>>
856 ): Context<Exclude<Services, I>>
857} = dual(3, <Services, I, S>(
858 self: Context<Services>,
859 key: Key<I, S>,
860 service: Option.Option<Types.NoInfer<S>>
861): Context<Exclude<Services, I>> =>
862 service._tag === "None"
863 ? omit(key)(self)
864 : add(self, key, service.value) as any)
865
866/**
867 * Gets the service for a key, or evaluates the fallback when a non-reference
868 * key is absent.
869 *
870 * **When to use**
871 *
872 * Use when you need a fallback for a missing `Context.Service` key while still
873 * resolving `Context.Reference` defaults.
874 *
875 * **Details**
876 *
877 * If the key is a `Context.Reference` and no override is stored in the
878 * context, its cached default value is returned instead of the fallback.
879 *
880 * **Gotchas**
881 *
882 * The fallback is not evaluated for missing `Context.Reference` keys because
883 * references resolve to their default value.
884 *
885 * **Example** (Falling back for missing services)
886 *
887 * ```ts import.meta.vitest
888 * import { Context } from "effect"
889 *
890 * const Logger = Context.Service<{ log: (msg: string) => void }>("Logger")
891 * const Database = Context.Service<{ query: (sql: string) => string }>(
892 * "Database"
893 * )
894 *
895 * const context = Context.make(Logger, { log: (_msg: string) => {} })
896 *
897 * const logger = Context.getOrElse(context, Logger, () => ({ log: () => {} }))
898 * const database = Context.getOrElse(
899 * context,
900 * Database,
901 * () => ({ query: () => "fallback" })
902 * )
903 *
904 * logger === Context.get(context, Logger) // => true
905 * database.query("SELECT 1") // => "fallback"
906 * ```
907 *
908 * @see {@link getOption} for returning `Option.none` when a non-reference key is missing
909 *
910 * @category getters
911 * @since 3.7.0
912 */
913export const getOrElse: {
914 <S, I, B>(key: Key<I, S>, orElse: LazyArg<B>): <Services>(self: Context<Services>) => S | B
915 <Services, S, I, B>(self: Context<Services>, key: Key<I, S>, orElse: LazyArg<B>): S | B
916} = dual(3, <Services, S, I, B>(self: Context<Services>, key: Key<I, S>, orElse: LazyArg<B>): S | B => {
917 const value = lookup(self, key.key)
918 if (value !== notFound) return value as any
919 return isReference(key) ? getDefaultValue(key) : orElse()
920})
921
922/**
923 * Returns the service currently stored for a key, or `undefined` when the key
924 * is absent.
925 *
926 * **When to use**
927 *
928 * Use when you need to read the service stored for a key without resolving
929 * `Context.Reference` defaults.
930 *
931 * **Gotchas**
932 *
933 * This is a raw lookup and does not resolve default values for
934 * `Context.Reference` keys.
935 *
936 * @see {@link getOption} for a reference-aware optional lookup
937 *
938 * @category getters
939 * @since 4.0.0
940 */
941export const getOrUndefined: {
942 <S, I>(key: Key<I, S>): <Services>(self: Context<Services>) => S | undefined
943 <Services, S, I>(self: Context<Services>, key: Key<I, S>): S | undefined
944} = dual(
945 2,
946 <Services, S, I>(self: Context<Services>, key: Key<I, S>): S | undefined => getOrUndefinedUnsafe(self, key.key)
947)
948
949/** @internal */
950export const getOrUndefinedUnsafe = <A, Services = never>(self: Context<Services>, key: string): A | undefined => {
951 const value = lookup(self, key)
952 return value === notFound ? undefined : value as A
953}
954
955/**
956 * Gets the service for a key, throwing if an absent non-reference key cannot be
957 * resolved.
958 *
959 * **When to use**
960 *
961 * Use when you need to read a service from a context whose type does not prove
962 * the service is present.
963 *
964 * **Details**
965 *
966 * If the key is a `Context.Reference` and no override is stored in the
967 * context, its cached default value is returned. For absent non-reference keys,
968 * this function throws a runtime error.
969 *
970 * **Example** (Getting services unsafely)
971 *
972 * ```ts import.meta.vitest
973 * import { Context, Option } from "effect"
974 *
975 * const Port = Context.Service<{ PORT: number }>("Port")
976 * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
977 *
978 * const context = Context.make(Port, { PORT: 8080 })
979 *
980 * Context.getUnsafe(context, Port).PORT // => 8080
981 * Context.getOption(context, Timeout) // => Option.none()
982 * ```
983 *
984 * @see {@link get} for type-checked service access
985 * @see {@link getOption} for optional service access
986 *
987 * @category unsafe
988 * @since 4.0.0
989 */
990export const getUnsafe: {
991 <S, I>(service: Key<I, S>): <Services>(self: Context<Services>) => S
992 <Services, S, I>(self: Context<Services>, services: Key<I, S>): S
993} = dual(
994 2,
995 <Services, I extends Services, S>(self: Context<Services>, service: Key<I, S>): S => {
996 const value = lookup(self, service.key)
997 if (value === notFound) {
998 if (isReference(service)) return getDefaultValue(service as any)
999 throw serviceNotFoundError(service)
1000 }
1001 return value as any
1002 }
1003)
1004
1005/**
1006 * Gets a service from the context that corresponds to the given key.
1007 *
1008 * **When to use**
1009 *
1010 * Use when you need type-checked access to a service already included in the
1011 * context type.
1012 *
1013 * **Example** (Getting a service from a context)
1014 *
1015 * ```ts import.meta.vitest
1016 * import { Context, pipe } from "effect"
1017 *
1018 * const Port = Context.Service<{ PORT: number }>("Port")
1019 * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
1020 *
1021 * const context = pipe(
1022 * Context.make(Port, { PORT: 8080 }),
1023 * Context.add(Timeout, { TIMEOUT: 5000 })
1024 * )
1025 *
1026 * Context.get(context, Timeout).TIMEOUT // => 5000
1027 * ```
1028 *
1029 * @see {@link getOption} for optional service access
1030 * @see {@link getOrElse} for fallback values
1031 *
1032 * @category getters
1033 * @since 2.0.0
1034 */
1035export const get: {
1036 <I, S>(service: Key<I, S>): (self: Context<I>) => S
1037 <Services, I extends Services, S>(service: Key<I, S>): (self: Context<Services>) => S
1038 <Services, I extends Services, S>(self: Context<Services>, service: Key<I, S>): S
1039} = getUnsafe
1040
1041const defaultValueCacheKey = "~effect/Context/defaultValue" as const
1042
1043const getDefaultValue = (ref: Reference<any>) => {
1044 if (defaultValueCacheKey in ref) {
1045 return ref[defaultValueCacheKey] as any
1046 }
1047 return (ref as any)[defaultValueCacheKey] = ref.defaultValue()
1048}
1049
1050const serviceNotFoundError = (service: Key<any, any>) => {
1051 const error = new Error(
1052 `Service not found${service.key ? `: ${String(service.key)}` : ""}`
1053 )
1054 if (error.stack) {
1055 const lines = error.stack.split("\n")
1056 lines.splice(1, 3)
1057 error.stack = lines.join("\n")
1058 }
1059 return error
1060}
1061
1062/**
1063 * Gets the service for a key safely wrapped in an `Option`.
1064 *
1065 * **When to use**
1066 *
1067 * Use when you need to read a `Context` service as an `Option` so absence is
1068 * represented as data.
1069 *
1070 * **Details**
1071 *
1072 * Returns `Option.some` when the service is stored in the context. If the key
1073 * is a `Context.Reference` and no override is stored, returns `Option.some` of
1074 * the cached default value. Missing non-reference keys return `Option.none`.
1075 *
1076 * **Example** (Getting optional services)
1077 *
1078 * ```ts import.meta.vitest
1079 * import { Context, Option } from "effect"
1080 *
1081 * const Port = Context.Service<{ PORT: number }>("Port")
1082 * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
1083 *
1084 * const context = Context.make(Port, { PORT: 8080 })
1085 *
1086 * Context.getOption(context, Port) // => Option.some({ PORT: 8080 })
1087 * Context.getOption(context, Timeout) // => Option.none()
1088 * ```
1089 *
1090 * @see {@link getOrElse} for returning a fallback value directly
1091 *
1092 * @category getters
1093 * @since 2.0.0
1094 */
1095export const getOption: {
1096 <S, I>(service: Key<I, S>): <Services>(self: Context<Services>) => Option.Option<S>
1097 <Services, S, I>(self: Context<Services>, service: Key<I, S>): Option.Option<S>
1098} = dual(2, <Services, I extends Services, S>(self: Context<Services>, service: Key<I, S>): Option.Option<S> => {
1099 const value = lookup(self, service.key)
1100 if (value !== notFound) return Option.some(value as any)
1101 return isReference(service) ? Option.some(getDefaultValue(service as any)) : Option.none()
1102})
1103
1104/**
1105 * Merges two `Context`s into one.
1106 *
1107 * **When to use**
1108 *
1109 * Use when you need to combine two contexts.
1110 *
1111 * **Details**
1112 *
1113 * When both contexts contain the same service key, the service from `that`
1114 * overrides the service from `self`.
1115 *
1116 * **Example** (Merging two contexts)
1117 *
1118 * ```ts import.meta.vitest
1119 * import { Context } from "effect"
1120 *
1121 * const Port = Context.Service<{ PORT: number }>("Port")
1122 * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
1123 *
1124 * const firstContext = Context.make(Port, { PORT: 8080 })
1125 * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 })
1126 *
1127 * const context = Context.merge(firstContext, secondContext)
1128 *
1129 * const values = [Context.get(context, Port).PORT, Context.get(context, Timeout).TIMEOUT]
1130 * values // => [8080, 5000]
1131 * ```
1132 *
1133 * @see {@link mergeAll} for merging more than two contexts at once
1134 *
1135 * @category combining
1136 * @since 2.0.0
1137 */
1138export const merge: {
1139 <R1>(that: Context<R1>): <Services>(self: Context<Services>) => Context<R1 | Services>
1140 <Services, R1>(self: Context<Services>, that: Context<R1>): Context<Services | R1>
1141} = dual(2, <Services, R1>(self: Context<Services>, that: Context<R1>): Context<Services | R1> => {
1142 if (self.mapUnsafe.size === 0) return that as any
1143 if (that.mapUnsafe.size === 0) return self as any
1144 return withFlat(self, (map) => that.mapUnsafe.forEach((value, key) => map.set(key, value)))
1145})
1146
1147/**
1148 * Merges any number of `Context`s into one.
1149 *
1150 * **When to use**
1151 *
1152 * Use when you need to combine a variadic list of contexts.
1153 *
1154 * **Details**
1155 *
1156 * When multiple contexts contain the same service key, the service from the
1157 * last context with that key is kept.
1158 *
1159 * **Example** (Merging multiple contexts)
1160 *
1161 * ```ts import.meta.vitest
1162 * import { Context } from "effect"
1163 *
1164 * const Port = Context.Service<{ PORT: number }>("Port")
1165 * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
1166 * const Host = Context.Service<{ HOST: string }>("Host")
1167 *
1168 * const firstContext = Context.make(Port, { PORT: 8080 })
1169 * const secondContext = Context.make(Timeout, { TIMEOUT: 5000 })
1170 * const thirdContext = Context.make(Host, { HOST: "localhost" })
1171 *
1172 * const context = Context.mergeAll(
1173 * firstContext,
1174 * secondContext,
1175 * thirdContext
1176 * )
1177 *
1178 * context.mapUnsafe.size // => 3
1179 * ```
1180 *
1181 * @see {@link merge} for merging two contexts
1182 *
1183 * @category combining
1184 * @since 3.12.0
1185 */
1186export const mergeAll = <T extends Array<unknown>>(
1187 ...ctxs: [...{ [K in keyof T]: Context<T[K]> }]
1188): Context<T[number]> => {
1189 const map = new Map()
1190 for (let i = 0; i < ctxs.length; i++) {
1191 ctxs[i].mapUnsafe.forEach((value, key) => {
1192 map.set(key, value)
1193 })
1194 }
1195 return makeUnsafe(map)
1196}
1197
1198/**
1199 * Returns a new `Context` that contains only the specified services.
1200 *
1201 * **When to use**
1202 *
1203 * Use when you want to keep an allowlist of services in a `Context`.
1204 *
1205 * **Example** (Picking services from a context)
1206 *
1207 * ```ts import.meta.vitest
1208 * import { Context, Option, pipe } from "effect"
1209 *
1210 * const Port = Context.Service<{ PORT: number }>("Port")
1211 * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
1212 *
1213 * const someContext = pipe(
1214 * Context.make(Port, { PORT: 8080 }),
1215 * Context.add(Timeout, { TIMEOUT: 5000 })
1216 * )
1217 *
1218 * const context = pipe(someContext, Context.pick(Port))
1219 *
1220 * Context.getOption(context, Port) // => Option.some({ PORT: 8080 })
1221 * Context.getOption(context, Timeout) // => Option.none()
1222 * ```
1223 *
1224 * @see {@link omit} for removing selected services
1225 *
1226 * @category filtering
1227 * @since 2.0.0
1228 */
1229export const pick = <S extends ReadonlyArray<Key<any, any>>>(
1230 ...services: S
1231) =>
1232<Services>(self: Context<Services>): Context<Services & Service.Identifier<S[number]>> => {
1233 const keep = new Set(services.map((key) => key.key))
1234 return withFlat(self, (map) =>
1235 map.forEach((_, key) => {
1236 if (!keep.has(key)) map.delete(key)
1237 }))
1238}
1239
1240/**
1241 * Returns a new `Context` with the specified service keys removed.
1242 *
1243 * **When to use**
1244 *
1245 * Use when you want to remove a denylist of services from a `Context`.
1246 *
1247 * **Example** (Omitting services from a context)
1248 *
1249 * ```ts import.meta.vitest
1250 * import { Context, Option, pipe } from "effect"
1251 *
1252 * const Port = Context.Service<{ PORT: number }>("Port")
1253 * const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
1254 *
1255 * const someContext = pipe(
1256 * Context.make(Port, { PORT: 8080 }),
1257 * Context.add(Timeout, { TIMEOUT: 5000 })
1258 * )
1259 *
1260 * const context = pipe(someContext, Context.omit(Timeout))
1261 *
1262 * Context.getOption(context, Port) // => Option.some({ PORT: 8080 })
1263 * Context.getOption(context, Timeout) // => Option.none()
1264 * ```
1265 *
1266 * @see {@link pick} for keeping selected services
1267 *
1268 * @category filtering
1269 * @since 2.0.0
1270 */
1271export const omit = <S extends ReadonlyArray<Key<any, any>>>(
1272 ...keys: S
1273) =>
1274<Services>(self: Context<Services>): Context<Exclude<Services, Service.Identifier<S[number]>>> =>
1275 withFlat(self, (map) => {
1276 for (let i = 0; i < keys.length; i++) {
1277 map.delete(keys[i].key)
1278 }
1279 })
1280
1281/**
1282 * Creates a context key with a default value.
1283 *
1284 * **When to use**
1285 *
1286 * Use when you need to define a context key with a lazily computed default
1287 * value.
1288 *
1289 * **Details**
1290 *
1291 * `Context.Reference` allows you to create a key that can hold a value. You
1292 * can provide a default value for the service, which will automatically be used
1293 * when the context is accessed, or override it with a custom implementation
1294 * when needed. The default value is computed lazily and cached on the
1295 * reference.
1296 *
1297 * **Example** (Creating references with default values)
1298 *
1299 * ```ts import.meta.vitest
1300 * import { Context } from "effect"
1301 *
1302 * // Create a reference with a default value
1303 * const messages: Array<string> = []
1304 * const LoggerRef = Context.Reference("Logger", {
1305 * defaultValue: () => ({ log: (msg: string) => messages.push(`Default: ${msg}`) })
1306 * })
1307 *
1308 * // The reference provides the default value when accessed from an empty context
1309 * const context = Context.empty()
1310 * const logger = Context.get(context, LoggerRef)
1311 *
1312 * // You can also override the default value
1313 * const customContext = Context.make(LoggerRef, {
1314 * log: (msg: string) => messages.push(`Custom: ${msg}`)
1315 * })
1316 * const customLogger = Context.get(customContext, LoggerRef)
1317 * logger.log("default")
1318 * customLogger.log("message")
1319 * messages // => ["Default: default", "Custom: message"]
1320 * ```
1321 *
1322 * @see {@link Service} for required services without default values
1323 *
1324 * @category services
1325 * @since 3.11.0
1326 */
1327export const Reference: <Service>(
1328 key: string,
1329 options: {
1330 readonly defaultValue: () => Service
1331 /** @internal */
1332 readonly fiberCached?: boolean | undefined
1333 }
1334) => Reference<Service> = Service as any