Context.ts overview
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 v2.0.0
Exports Grouped by Category
constructors
GenericTag
Creates a new Tag
instance with an optional key parameter.
Example
import * as assert from "node:assert"
import { Context } from "effect"
assert.strictEqual(Context.GenericTag("PORT").key === Context.GenericTag("PORT").key, true)
Signature
declare const GenericTag: <Identifier, Service = Identifier>(key: string) => Tag<Identifier, Service>
Since v2.0.0
Reference
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)
import * as assert from "node:assert"
import { Context, Effect } from "effect"
class SpecialNumber extends Context.Reference<SpecialNumber>()("SpecialNumber", { defaultValue: () => 2048 }) {}
// ┌─── Effect<void, never, never>
// â–¼
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)
import { Context, Effect } from "effect"
class SpecialNumber extends Context.Reference<SpecialNumber>()("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
Signature
declare const Reference: <Self>() => <const Id extends string, Service>(
id: Id,
options: { readonly defaultValue: () => Service }
) => ReferenceClass<Self, Id, Service>
Since v3.11.0
Tag
Example
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 })
}
Signature
declare const Tag: <const Id extends string>(id: Id) => <Self, Shape>() => TagClass<Self, Id, Shape>
Since v2.0.0
empty
Returns an empty Context
.
Example
import * as assert from "node:assert"
import { Context } from "effect"
assert.strictEqual(Context.isContext(Context.empty()), true)
Signature
declare const empty: () => Context<never>
Since v2.0.0
make
Creates a new Context
with a single service associated to the tag.
Example
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 })
Signature
declare const make: <I, S>(tag: Tag<I, S>, service: Types.NoInfer<S>) => Context<I>
Since v2.0.0
unsafeMake
Signature
declare const unsafeMake: <Services>(unsafeMap: Map<string, any>) => Context<Services>
Since v2.0.0
getters
get
Get a service from the context that corresponds to the given tag.
Example
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 })
Signature
declare const get: {
<I, S>(tag: Reference<I, S>): <Services>(self: Context<Services>) => S
<Services, I extends Services, S>(tag: Tag<I, S>): (self: Context<Services>) => S
<Services, I, S>(self: Context<Services>, tag: Reference<I, S>): S
<Services, I extends Services, S>(self: Context<Services>, tag: Tag<I, S>): S
}
Since v2.0.0
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
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())
Signature
declare const getOption: {
<S, I>(tag: Tag<I, S>): <Services>(self: Context<Services>) => Option<S>
<Services, S, I>(self: Context<Services>, tag: Tag<I, S>): Option<S>
}
Since v2.0.0
getOrElse
Get a service from the context that corresponds to the given tag, or use the fallback value.
Signature
declare const getOrElse: {
<S, I, B>(tag: Tag<I, S>, orElse: LazyArg<B>): <Services>(self: Context<Services>) => S | B
<Services, S, I, B>(self: Context<Services>, tag: Tag<I, S>, orElse: LazyArg<B>): S | B
}
Since v3.7.0
guards
isContext
Checks if the provided argument is a Context
.
Example
import * as assert from "node:assert"
import { Context } from "effect"
assert.strictEqual(Context.isContext(Context.empty()), true)
Signature
declare const isContext: (input: unknown) => input is Context<never>
Since v2.0.0
isReference
Checks if the provided argument is a Reference
.
Signature
declare const isReference: (u: unknown) => u is Reference<any, any>
Since v3.11.0
isTag
Checks if the provided argument is a Tag
.
Example
import * as assert from "node:assert"
import { Context } from "effect"
assert.strictEqual(Context.isTag(Context.GenericTag("Tag")), true)
Signature
declare const isTag: (input: unknown) => input is Tag<any, any>
Since v2.0.0
models
Context (interface)
Signature
export interface Context<in Services> extends Equal, Pipeable, Inspectable {
readonly [TypeId]: {
readonly _Services: Types.Contravariant<Services>
}
readonly unsafeMap: Map<string, any>
}
Since v2.0.0
Reference (interface)
Signature
export interface Reference<in out Id, in out Value> extends Pipeable, Inspectable {
readonly [ReferenceTypeId]: ReferenceTypeId
readonly defaultValue: () => Value
readonly _op: "Tag"
readonly Service: Value
readonly Identifier: Id
readonly [TagTypeId]: {
readonly _Service: Types.Invariant<Value>
readonly _Identifier: Types.Invariant<Id>
}
of(self: Value): Value
context(self: Value): Context<Id>
readonly stack?: string | undefined
readonly key: string
[Unify.typeSymbol]?: unknown
[Unify.unifySymbol]?: TagUnify<this>
[Unify.ignoreSymbol]?: TagUnifyIgnore
}
Since v3.11.0
ReferenceClass (interface)
Signature
export interface ReferenceClass<Self, Id extends string, Type> extends Reference<Self, Type> {
new (_: never): TagClassShape<Id, Type>
readonly key: Id
}
Since v3.11.0
Tag (interface)
Signature
export interface Tag<in out Id, in out Value> extends Pipeable, Inspectable {
readonly _op: "Tag"
readonly Service: Value
readonly Identifier: Id
readonly [TagTypeId]: {
readonly _Service: Types.Invariant<Value>
readonly _Identifier: Types.Invariant<Id>
}
of(self: Value): Value
context(self: Value): Context<Id>
readonly stack?: string | undefined
readonly key: string
[Unify.typeSymbol]?: unknown
[Unify.unifySymbol]?: TagUnify<this>
[Unify.ignoreSymbol]?: TagUnifyIgnore
}
Since v3.5.9
TagClass (interface)
Signature
export interface TagClass<Self, Id extends string, Type> extends Tag<Self, Type> {
new (_: never): TagClassShape<Id, Type>
readonly key: Id
}
Since v2.0.0
TagClassShape (interface)
Signature
export interface TagClassShape<Id, Shape> {
readonly [TagTypeId]: TagTypeId
readonly Type: Shape
readonly Id: Id
}
Since v2.0.0
TagUnify (interface)
Signature
export interface TagUnify<A extends { [Unify.typeSymbol]?: any }> {
Tag?: () => Extract<A[Unify.typeSymbol], Tag<any, any>>
}
Since v2.0.0
TagUnifyIgnore (interface)
Signature
export interface TagUnifyIgnore {}
Since v2.0.0
ValidTagsById (type alias)
Signature
type ValidTagsById<R> = R extends infer S ? Tag<S, any> : never
Since v2.0.0
symbol
ReferenceTypeId (type alias)
Signature
type ReferenceTypeId = typeof ReferenceTypeId
Since v3.11.0
TagTypeId (type alias)
Signature
type TagTypeId = typeof TagTypeId
Since v2.0.0
TypeId (type alias)
Signature
type TypeId = typeof TypeId
Since v2.0.0
unsafe
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 getOption
.
Example
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))
Signature
declare const unsafeGet: {
<S, I>(tag: Tag<I, S>): <Services>(self: Context<Services>) => S
<Services, S, I>(self: Context<Services>, tag: Tag<I, S>): S
}
Since v2.0.0
utils
Tag (namespace)
Since v2.0.0
Service (type alias)
Signature
type Service<T> = T extends Tag<any, any> ? T["Service"] : T extends TagClassShape<any, infer A> ? A : never
Since v2.0.0
Identifier (type alias)
Signature
type Identifier<T> = T extends Tag<any, any> ? T["Identifier"] : T extends TagClassShape<any, any> ? T : never
Since v2.0.0
add
Adds a service to a given Context
.
Example
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 })
Signature
declare const add: {
<I, S>(tag: Tag<I, S>, service: Types.NoInfer<S>): <Services>(self: Context<Services>) => Context<Services | I>
<Services, I, S>(self: Context<Services>, tag: Tag<I, S>, service: Types.NoInfer<S>): Context<Services | I>
}
Since v2.0.0
merge
Merges two Context
s, returning a new Context
containing the services of both.
Example
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 })
Signature
declare const merge: {
<R1>(that: Context<R1>): <Services>(self: Context<Services>) => Context<R1 | Services>
<Services, R1>(self: Context<Services>, that: Context<R1>): Context<Services | R1>
}
Since v2.0.0
mergeAll
Merges any number of Context
s, returning a new Context
containing the services of all.
Example
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" })
Signature
declare const mergeAll: <T extends Array<unknown>>(...ctxs: { [K in keyof T]: Context<T[K]> }) => Context<T[number]>
Since v3.12.0
omit
Signature
declare const omit: <Tags extends ReadonlyArray<Tag<any, any>>>(
...tags: Tags
) => <Services>(self: Context<Services>) => Context<Exclude<Services, Tag.Identifier<Tags[number]>>>
Since v2.0.0
pick
Returns a new Context
that contains only the specified services.
Example
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())
Signature
declare const pick: <Tags extends ReadonlyArray<Tag<any, any>>>(
...tags: Tags
) => <Services>(self: Context<Services>) => Context<Services & Tag.Identifier<Tags[number]>>
Since v2.0.0