EdgeAIG's picture
download
raw
24.8 kB
import * as Effectable from "./Effectable.js";
import * as Equal from "./Equal.js";
import { dual } from "./Function.js";
import * as Hash from "./Hash.js";
import { exitSucceed, PipeInspectableProto, withFiber } from "./internal/core.js";
import * as Option from "./Option.js";
import { hasProperty } from "./Predicate.js";
/**
* Runtime type identifier attached to `Context` service keys and used by
* `isKey` to recognize them.
*
* @category type IDs
* @since 4.0.0
*/
export const ServiceTypeId = "~effect/Context/Service";
/**
* Creates a `Context` service key.
*
* **When to use**
*
* Use when you need to define a context service key for a dependency that must
* be provided by the surrounding context.
*
* **Details**
*
* Call `Context.Service("Key")` for a function-style key, or use the two-stage
* form `Context.Service<Self, Shape>()("Key")` for class-style service
* declarations. The returned key can be yielded as an Effect and passed to
* `Context.make`, `Context.add`, and the Context getter functions.
*
* **Gotchas**
*
* The string key is the runtime identity of the service. Reusing the same key
* string for unrelated services makes them occupy the same slot in a
* `Context`.
*
* **Example** (Creating service keys)
*
* ```ts
* import { Context } from "effect"
*
* // Create a simple service
* const Database = Context.Service<{
* query: (sql: string) => string
* }>("Database")
*
* // Create a service class
* class Config extends Context.Service<Config, {
* port: number
* }>()("Config") {}
*
* // Use the services to create contexts
* const db = Context.make(Database, {
* query: (sql) => `Result: ${sql}`
* })
* const config = Context.make(Config, { port: 8080 })
* ```
*
* @see {@link Reference} for service keys with default values
*
* @category constructors
* @since 4.0.0
*/
export const Service = function () {
const prevLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 2;
const err = new Error();
Error.stackTraceLimit = prevLimit;
function KeyClass() {}
const self = KeyClass;
Object.setPrototypeOf(self, ServiceProto);
// @effect-diagnostics-next-line floatingEffect:off
Object.defineProperty(self, "stack", {
get() {
return err.stack;
}
});
if (arguments.length > 0) {
self.key = arguments[0];
if (arguments[1]?.defaultValue) {
self[ReferenceTypeId] = ReferenceTypeId;
self.defaultValue = arguments[1].defaultValue;
}
return self;
}
return function (key, options) {
self.key = key;
if (options?.make) {
;
self.make = options.make;
}
return self;
};
};
const ServiceProto = {
[ServiceTypeId]: ServiceTypeId,
... /*#__PURE__*/Effectable.Prototype({
label: "Service",
evaluate(fiber) {
return exitSucceed(get(fiber.context, this));
}
}),
toJSON() {
return {
_id: "Service",
key: this.key,
stack: this.stack
};
},
of(self) {
return self;
},
context(self) {
return make(this, self);
},
use(f) {
return withFiber(fiber => f(get(fiber.context, this)));
},
useSync(f) {
return withFiber(fiber => exitSucceed(f(get(fiber.context, this))));
}
};
const ReferenceTypeId = "~effect/Context/Reference";
const TypeId = "~effect/Context";
/**
* Creates a `Context` from an existing service map.
*
* **When to use**
*
* Use when constructing a low-level `Context` from a trusted map whose lifecycle
* you control.
*
* **Gotchas**
*
* This is unsafe because later mutation of the provided map can affect the
* created `Context`. Prefer `empty`, `make`, `add`, or `merge` for normal
* Context construction.
*
* **Example** (Creating a context from a map)
*
* ```ts
* import { Context } from "effect"
*
* // Create a context from a Map (unsafe)
* const map = new Map([
* ["Logger", { log: (msg: string) => console.log(msg) }]
* ])
*
* const context = Context.makeUnsafe(map)
* ```
*
* @category constructors
* @since 4.0.0
*/
export const makeUnsafe = mapUnsafe => {
const self = Object.create(Proto);
self.mapUnsafe = mapUnsafe;
self.mutable = false;
return self;
};
const Proto = {
...PipeInspectableProto,
[TypeId]: {
_Services: _ => _
},
toJSON() {
return {
_id: "Context",
services: Array.from(this.mapUnsafe).map(([key, value]) => ({
key,
value
}))
};
},
[Equal.symbol](that) {
if (!isContext(that) || this.mapUnsafe.size !== that.mapUnsafe.size) return false;
for (const k of this.mapUnsafe.keys()) {
if (!that.mapUnsafe.has(k) || !Equal.equals(this.mapUnsafe.get(k), that.mapUnsafe.get(k))) {
return false;
}
}
return true;
},
[Hash.symbol]() {
return Hash.number(this.mapUnsafe.size);
}
};
/**
* Checks whether the provided argument is a `Context`.
*
* **When to use**
*
* Use to narrow an unknown value before passing it to APIs that require a
* `Context`.
*
* **Details**
*
* This checks the runtime `Context` marker and does not inspect which services
* the context contains.
*
* **Gotchas**
*
* This guard only proves that the value is a `Context`; it does not prove that
* any specific service is present.
*
* **Example** (Checking for contexts)
*
* ```ts
* import { Context } from "effect"
* import * as assert from "node:assert"
*
* assert.strictEqual(Context.isContext(Context.empty()), true)
* ```
*
* @see {@link isKey} for checking service keys
* @see {@link isReference} for checking references with defaults
*
* @category guards
* @since 2.0.0
*/
export const isContext = u => hasProperty(u, TypeId);
/**
* Checks whether the provided argument is a `Key`.
*
* **Example** (Checking for keys)
*
* ```ts
* import { Context } from "effect"
* import * as assert from "node:assert"
*
* assert.strictEqual(Context.isKey(Context.Service("Service")), true)
* ```
*
* @category guards
* @since 4.0.0
*/
export const isKey = u => hasProperty(u, ServiceTypeId);
/**
* Checks whether the provided argument is a `Reference`.
*
* **Example** (Checking for references)
*
* ```ts
* import { Context } from "effect"
* import * as assert from "node:assert"
*
* const LoggerRef = Context.Reference("Logger", {
* defaultValue: () => ({ log: (msg: string) => console.log(msg) })
* })
*
* assert.strictEqual(Context.isReference(LoggerRef), true)
* assert.strictEqual(Context.isReference(Context.Service("Key")), false)
* ```
*
* @category guards
* @since 3.11.0
*/
export const isReference = u => hasProperty(u, ReferenceTypeId);
/**
* Returns an empty `Context`.
*
* **Example** (Creating an empty context)
*
* ```ts
* import { Context } from "effect"
* import * as assert from "node:assert"
*
* assert.strictEqual(Context.isContext(Context.empty()), true)
* ```
*
* @category constructors
* @since 2.0.0
*/
export const empty = () => emptyContext;
const emptyContext = /*#__PURE__*/makeUnsafe(/*#__PURE__*/new Map());
/**
* Creates a new `Context` with a single service associated to the key.
*
* **Example** (Creating a context with one service)
*
* ```ts
* import { Context } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
*
* const context = Context.make(Port, { PORT: 8080 })
*
* assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 })
* ```
*
* @category constructors
* @since 2.0.0
*/
export const make = (key, service) => makeUnsafe(new Map([[key.key, service]]));
/**
* Adds a service to a given `Context`.
*
* **When to use**
*
* Use when you need to store a known service value in a `Context`.
*
* **Details**
*
* If the context already contains the same service key, the new service
* replaces the previous one.
*
* **Example** (Adding a service to a context)
*
* ```ts
* import { Context, pipe } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
* const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
*
* const someContext = Context.make(Port, { PORT: 8080 })
*
* const context = pipe(
* someContext,
* Context.add(Timeout, { TIMEOUT: 5000 })
* )
*
* assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 })
* assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 })
* ```
*
* @see {@link addOrOmit} for adding or removing a service from an `Option`
*
* @category adders
* @since 2.0.0
*/
export const add = /*#__PURE__*/dual(3, (self, key, service) => withMapUnsafe(self, map => {
map.set(key.key, service);
}));
/**
* Adds or removes a service depending on an `Option`.
*
* **When to use**
*
* Use when you need to add or omit a `Context` service based on an `Option`.
*
* **Details**
*
* When `service` is `Option.some`, the value is stored for the key. When it is
* `Option.none`, the key is removed from the returned `Context`.
*
* **Example** (Adding optional services)
*
* ```ts
* import { Context, Option } from "effect"
*
* const Port = Context.Service<{ PORT: number }>("Port")
*
* const withPort = Context.empty().pipe(
* Context.addOrOmit(Port, Option.some({ PORT: 8080 }))
* )
*
* const withoutPort = withPort.pipe(
* Context.addOrOmit(Port, Option.none())
* )
* ```
*
* @see {@link add} for always storing a service value
*
* @category adders
* @since 4.0.0
*/
export const addOrOmit = /*#__PURE__*/dual(3, (self, key, service) => withMapUnsafe(self, map => {
if (service._tag === "None") {
map.delete(key.key);
} else {
map.set(key.key, service.value);
}
}));
/**
* Gets the service for a key, or evaluates the fallback when a non-reference
* key is absent.
*
* **When to use**
*
* Use when you need a fallback for a missing `Context.Service` key while still
* resolving `Context.Reference` defaults.
*
* **Details**
*
* If the key is a `Context.Reference` and no override is stored in the
* context, its cached default value is returned instead of the fallback.
*
* **Gotchas**
*
* The fallback is not evaluated for missing `Context.Reference` keys because
* references resolve to their default value.
*
* **Example** (Falling back for missing services)
*
* ```ts
* import { Context } from "effect"
*
* const Logger = Context.Service<{ log: (msg: string) => void }>("Logger")
* const Database = Context.Service<{ query: (sql: string) => string }>(
* "Database"
* )
*
* const context = Context.make(Logger, {
* log: (msg: string) => console.log(msg)
* })
*
* const logger = Context.getOrElse(context, Logger, () => ({ log: () => {} }))
* const database = Context.getOrElse(
* context,
* Database,
* () => ({ query: () => "fallback" })
* )
*
* console.log(logger === Context.get(context, Logger)) // true
* console.log(database.query("SELECT 1")) // "fallback"
* ```
*
* @see {@link getOption} for returning `Option.none` when a non-reference key is missing
*
* @category getters
* @since 3.7.0
*/
export const getOrElse = /*#__PURE__*/dual(3, (self, key, orElse) => {
if (self.mapUnsafe.has(key.key)) {
return self.mapUnsafe.get(key.key);
}
return isReference(key) ? getDefaultValue(key) : orElse();
});
/**
* Returns the service currently stored for a key, or `undefined` when the key
* is absent.
*
* **When to use**
*
* Use when you need to read the service stored for a key without resolving
* `Context.Reference` defaults.
*
* **Gotchas**
*
* This is a raw lookup and does not resolve default values for
* `Context.Reference` keys.
*
* @see {@link getOption} for a reference-aware optional lookup
*
* @category getters
* @since 4.0.0
*/
export const getOrUndefined = /*#__PURE__*/dual(2, (self, key) => self.mapUnsafe.get(key.key));
/**
* Gets the service for a key, throwing if an absent non-reference key cannot be
* resolved.
*
* **When to use**
*
* Use when you need to read a service from a context whose type does not prove
* the service is present.
*
* **Details**
*
* If the key is a `Context.Reference` and no override is stored in the
* context, its cached default value is returned. For absent non-reference keys,
* this function throws a runtime error.
*
* **Example** (Getting services unsafely)
*
* ```ts
* import { Context } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
* const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
*
* const context = Context.make(Port, { PORT: 8080 })
*
* assert.deepStrictEqual(Context.getUnsafe(context, Port), { PORT: 8080 })
* assert.throws(() => Context.getUnsafe(context, Timeout))
* ```
*
* @see {@link get} for type-checked service access
* @see {@link getOption} for optional service access
*
* @category unsafe
* @since 4.0.0
*/
export const getUnsafe = /*#__PURE__*/dual(2, (self, service) => {
if (!self.mapUnsafe.has(service.key)) {
if (ReferenceTypeId in service) return getDefaultValue(service);
throw serviceNotFoundError(service);
}
return self.mapUnsafe.get(service.key);
});
/**
* Gets a service from the context that corresponds to the given key.
*
* **When to use**
*
* Use when you need type-checked access to a service already included in the
* context type.
*
* **Example** (Getting a service from a context)
*
* ```ts
* import { Context, pipe } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
* const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
*
* const context = pipe(
* Context.make(Port, { PORT: 8080 }),
* Context.add(Timeout, { TIMEOUT: 5000 })
* )
*
* assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 })
* ```
*
* @see {@link getOption} for optional service access
* @see {@link getOrElse} for fallback values
*
* @category getters
* @since 2.0.0
*/
export const get = getUnsafe;
/**
* Gets the value for a `Context.Reference`, returning its cached default when
* the context does not contain an override.
*
* **When to use**
*
* Use when you need a `Context.Reference` value resolved from either a stored
* override or the reference's default value.
*
* **Details**
*
* Stored overrides take precedence. If no override is present, the reference's
* default value is computed lazily and cached on the reference itself.
*
* **Gotchas**
*
* Mutable default values can be shared across contexts unless an override is
* provided, because the default is cached on the `Context.Reference`.
*
* **Example** (Getting reference defaults unsafely)
*
* ```ts
* import { Context } from "effect"
*
* const LoggerRef = Context.Reference("Logger", {
* defaultValue: () => ({ log: (msg: string) => console.log(msg) })
* })
*
* const context = Context.empty()
* const logger = Context.getReferenceUnsafe(context, LoggerRef)
*
* console.log(typeof logger.log) // "function"
* ```
*
* @see {@link getUnsafe} for unsafe access with any service key
* @see {@link get} for type-checked reference-aware access
* @see {@link getOption} for optional access to non-reference keys
*
* @category unsafe
* @since 4.0.0
*/
export const getReferenceUnsafe = (self, service) => {
if (!self.mapUnsafe.has(service.key)) {
return getDefaultValue(service);
}
return self.mapUnsafe.get(service.key);
};
const defaultValueCacheKey = "~effect/Context/defaultValue";
const getDefaultValue = ref => {
if (defaultValueCacheKey in ref) {
return ref[defaultValueCacheKey];
}
return ref[defaultValueCacheKey] = ref.defaultValue();
};
const serviceNotFoundError = service => {
const error = new Error(`Service not found${service.key ? `: ${String(service.key)}` : ""}`);
if (service.stack) {
const lines = service.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;
};
/**
* Gets the service for a key safely wrapped in an `Option`.
*
* **When to use**
*
* Use when you need to read a `Context` service as an `Option` so absence is
* represented as data.
*
* **Details**
*
* Returns `Option.some` when the service is stored in the context. If the key
* is a `Context.Reference` and no override is stored, returns `Option.some` of
* the cached default value. Missing non-reference keys return `Option.none`.
*
* **Example** (Getting optional services)
*
* ```ts
* import { Context, Option } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
* const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
*
* const context = Context.make(Port, { PORT: 8080 })
*
* assert.deepStrictEqual(
* Context.getOption(context, Port),
* Option.some({ PORT: 8080 })
* )
* assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none())
* ```
*
* @see {@link getOrElse} for returning a fallback value directly
*
* @category getters
* @since 2.0.0
*/
export const getOption = /*#__PURE__*/dual(2, (self, service) => {
if (self.mapUnsafe.has(service.key)) {
return Option.some(self.mapUnsafe.get(service.key));
}
return isReference(service) ? Option.some(getDefaultValue(service)) : Option.none();
});
/**
* Merges two `Context`s into one.
*
* **When to use**
*
* Use when you need to combine two contexts.
*
* **Details**
*
* When both contexts contain the same service key, the service from `that`
* overrides the service from `self`.
*
* **Example** (Merging two contexts)
*
* ```ts
* import { Context } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
* const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
*
* const firstContext = Context.make(Port, { PORT: 8080 })
* const secondContext = Context.make(Timeout, { TIMEOUT: 5000 })
*
* const context = Context.merge(firstContext, secondContext)
*
* assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 })
* assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 })
* ```
*
* @see {@link mergeAll} for merging more than two contexts at once
*
* @category combining
* @since 2.0.0
*/
export const merge = /*#__PURE__*/dual(2, (self, that) => {
if (self.mapUnsafe.size === 0) return that;
if (that.mapUnsafe.size === 0) return self;
return withMapUnsafe(self, map => {
that.mapUnsafe.forEach((value, key) => map.set(key, value));
});
});
/**
* Merges any number of `Context`s into one.
*
* **When to use**
*
* Use when you need to combine a variadic list of contexts.
*
* **Details**
*
* When multiple contexts contain the same service key, the service from the
* last context with that key is kept.
*
* **Example** (Merging multiple contexts)
*
* ```ts
* import { Context } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
* const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
* const Host = Context.Service<{ 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 context = Context.mergeAll(
* firstContext,
* secondContext,
* thirdContext
* )
*
* assert.deepStrictEqual(Context.get(context, Port), { PORT: 8080 })
* assert.deepStrictEqual(Context.get(context, Timeout), { TIMEOUT: 5000 })
* assert.deepStrictEqual(Context.get(context, Host), { HOST: "localhost" })
* ```
*
* @see {@link merge} for merging two contexts
*
* @category combining
* @since 3.12.0
*/
export const mergeAll = (...ctxs) => {
const map = new Map();
for (let i = 0; i < ctxs.length; i++) {
ctxs[i].mapUnsafe.forEach((value, key) => {
map.set(key, value);
});
}
return makeUnsafe(map);
};
/**
* Returns a new `Context` that contains only the specified services.
*
* **When to use**
*
* Use when you want to keep an allowlist of services in a `Context`.
*
* **Example** (Picking services from a context)
*
* ```ts
* import { Context, Option, pipe } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
* const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
*
* const someContext = pipe(
* Context.make(Port, { PORT: 8080 }),
* Context.add(Timeout, { TIMEOUT: 5000 })
* )
*
* const context = pipe(someContext, Context.pick(Port))
*
* assert.deepStrictEqual(
* Context.getOption(context, Port),
* Option.some({ PORT: 8080 })
* )
* assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none())
* ```
*
* @see {@link omit} for removing selected services
*
* @category filtering
* @since 2.0.0
*/
export const pick = (...services) => self => withMapUnsafe(self, map => {
const keySet = new Set(services.map(key => key.key));
map.forEach((_, key) => {
if (keySet.has(key)) return;
map.delete(key);
});
});
/**
* Returns a new `Context` with the specified service keys removed.
*
* **When to use**
*
* Use when you want to remove a denylist of services from a `Context`.
*
* **Example** (Omitting services from a context)
*
* ```ts
* import { Context, Option, pipe } from "effect"
* import * as assert from "node:assert"
*
* const Port = Context.Service<{ PORT: number }>("Port")
* const Timeout = Context.Service<{ TIMEOUT: number }>("Timeout")
*
* const someContext = pipe(
* Context.make(Port, { PORT: 8080 }),
* Context.add(Timeout, { TIMEOUT: 5000 })
* )
*
* const context = pipe(someContext, Context.omit(Timeout))
*
* assert.deepStrictEqual(
* Context.getOption(context, Port),
* Option.some({ PORT: 8080 })
* )
* assert.deepStrictEqual(Context.getOption(context, Timeout), Option.none())
* ```
*
* @see {@link pick} for keeping selected services
*
* @category filtering
* @since 2.0.0
*/
export const omit = (...keys) => self => withMapUnsafe(self, map => {
for (let i = 0; i < keys.length; i++) {
map.delete(keys[i].key);
}
});
/**
* Performs a series of mutations on a `Context`. Prevents unnecessary copying
* of the underlying map when multiple mutations are needed.
*
* **When to use**
*
* Use to apply several `Context` transformations in one callback while copying
* the underlying service map only once.
*
* @see {@link add} for adding or replacing a service
* @see {@link addOrOmit} for adding or removing a service from an `Option`
* @see {@link merge} for combining two contexts
* @see {@link pick} for keeping selected services
* @see {@link omit} for removing selected services
*
* @category mutations
* @since 4.0.0
*/
export const mutate = /*#__PURE__*/dual(2, (self, f) => {
const next = makeUnsafe(new Map(self.mapUnsafe));
next.mutable = true;
const result = f(next);
result.mutable = false;
return result;
});
const withMapUnsafe = (self, f) => {
if (self.mutable) {
f(self.mapUnsafe);
return self;
}
const map = new Map(self.mapUnsafe);
f(map);
return makeUnsafe(map);
};
/**
* Creates a context key with a default value.
*
* **When to use**
*
* Use when you need to define a context key with a lazily computed default
* value.
*
* **Details**
*
* `Context.Reference` allows you to create a key 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. The default value is computed lazily and cached on the
* reference.
*
* **Example** (Creating references with default values)
*
* ```ts
* import { Context } from "effect"
*
* // Create a reference with a default value
* const LoggerRef = Context.Reference("Logger", {
* defaultValue: () => ({ log: (msg: string) => console.log(msg) })
* })
*
* // The reference provides the default value when accessed from an empty context
* const context = Context.empty()
* const logger = Context.get(context, LoggerRef)
*
* // You can also override the default value
* const customContext = Context.make(LoggerRef, {
* log: (msg: string) => `Custom: ${msg}`
* })
* const customLogger = Context.get(customContext, LoggerRef)
* ```
*
* @see {@link Service} for required services without default values
*
* @category references
* @since 3.11.0
*/
export const Reference = Service;
//# sourceMappingURL=Context.js.map

Xet Storage Details

Size:
24.8 kB
·
Xet hash:
f8be30223e417c35ada94e999927bcf76d329f913cd893fc6c87f7e956cc3137

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.