veffect
Version:
powerful TypeScript validation library built on the robust foundation of Effect combining exceptional type safety, high performance, and developer experience. Taking inspiration from Effect's functional principles, VEffect delivers a balanced approach tha
354 lines • 11.4 kB
TypeScript
/**
* 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 2.0.0
*/
import type { Equal } from "./Equal.js";
import type { Inspectable } from "./Inspectable.js";
import type { Option } from "./Option.js";
import type { Pipeable } from "./Pipeable.js";
import type * as Types from "./Types.js";
import type * as Unify from "./Unify.js";
declare const TagTypeId: unique symbol;
/**
* @since 2.0.0
* @category symbol
*/
export type TagTypeId = typeof TagTypeId;
/**
* @since 2.0.0
* @category models
*/
export interface Tag<in out Id, in out Value> extends Pipeable, Inspectable {
readonly _tag: "Tag";
readonly _op: "Tag";
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 2.0.0
* @category models
*/
export interface TagClassShape<Id, Shape> {
readonly [TagTypeId]: TagTypeId;
readonly Type: Shape;
readonly Id: Id;
}
/**
* @since 2.0.0
* @category models
*/
export interface TagClass<Self, Id, Type> extends Tag<Self, Type> {
new (_: never): TagClassShape<Id, Type>;
}
/**
* @category models
* @since 2.0.0
*/
export interface TagUnify<A extends {
[Unify.typeSymbol]?: any;
}> {
Tag?: () => A[Unify.typeSymbol] extends Tag<infer I0, infer S0> | infer _ ? Tag<I0, S0> : never;
}
/**
* @category models
* @since 2.0.0
*/
export interface TagUnifyIgnore {
}
/**
* @since 2.0.0
*/
export declare namespace Tag {
/**
* @since 2.0.0
*/
type Service<T extends Tag<any, any> | TagClassShape<any, any>> = T extends Tag<any, infer A> ? A : T extends TagClassShape<any, infer A> ? A : never;
/**
* @since 2.0.0
*/
type Identifier<T extends Tag<any, any> | TagClassShape<any, any>> = T extends Tag<infer A, any> ? A : T extends TagClassShape<infer A, any> ? A : never;
}
/**
* Creates a new `Tag` instance with an optional key parameter.
*
* @param key - A key that will be used to compare tags.
*
* @example
* import * as Context from "effect/Context"
*
* assert.strictEqual(Context.GenericTag("PORT").key === Context.GenericTag("PORT").key, true)
*
* @since 2.0.0
* @category constructors
*/
export declare const GenericTag: <Identifier, Service = Identifier>(key: string) => Tag<Identifier, Service>;
declare const TypeId: unique symbol;
/**
* @since 2.0.0
* @category symbol
*/
export type TypeId = typeof TypeId;
/**
* @since 2.0.0
* @category models
*/
export type ValidTagsById<R> = R extends infer S ? Tag<S, any> : never;
/**
* @since 2.0.0
* @category models
*/
export interface Context<in Services> extends Equal, Pipeable, Inspectable {
readonly [TypeId]: {
readonly _Services: Types.Contravariant<Services>;
};
readonly unsafeMap: Map<string, any>;
}
/**
* @since 2.0.0
* @category constructors
*/
export declare const unsafeMake: <Services>(unsafeMap: Map<string, any>) => Context<Services>;
/**
* 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/Context"
*
* assert.strictEqual(Context.isContext(Context.empty()), true)
*
* @since 2.0.0
* @category guards
*/
export declare const isContext: (input: unknown) => input is Context<never>;
/**
* 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/Context"
*
* assert.strictEqual(Context.isTag(Context.GenericTag("Tag")), true)
*
* @since 2.0.0
* @category guards
*/
export declare const isTag: (input: unknown) => input is Tag<any, any>;
/**
* Returns an empty `Context`.
*
* @example
* import * as Context from "effect/Context"
*
* assert.strictEqual(Context.isContext(Context.empty()), true)
*
* @since 2.0.0
* @category constructors
*/
export declare const empty: () => Context<never>;
/**
* Creates a new `Context` with a single service associated to the tag.
*
* @example
* import * as Context from "effect/Context"
*
* const Port = Context.GenericTag<{ PORT: number }>("Port")
*
* const Services = Context.make(Port, { PORT: 8080 })
*
* assert.deepStrictEqual(Context.get(Services, Port), { PORT: 8080 })
*
* @since 2.0.0
* @category constructors
*/
export declare const make: <T extends Tag<any, any>>(tag: T, service: Tag.Service<T>) => Context<Tag.Identifier<T>>;
/**
* Adds a service to a given `Context`.
*
* @example
* import * as Context from "effect/Context"
* import { pipe } from "effect/Function"
*
* 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 })
*
* @since 2.0.0
*/
export declare const add: {
<T extends Tag<any, any>>(tag: T, service: Tag.Service<T>): <Services>(self: Context<Services>) => Context<Services | Tag.Identifier<T>>;
<Services, T extends Tag<any, any>>(self: Context<Services>, tag: T, service: Tag.Service<T>): Context<Services | Tag.Identifier<T>>;
};
/**
* 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/Context"
* import { pipe } from "effect/Function"
*
* 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 })
*
* @since 2.0.0
* @category getters
*/
export declare const get: {
<Services, T extends ValidTagsById<Services>>(tag: T): (self: Context<Services>) => Tag.Service<T>;
<Services, T extends ValidTagsById<Services>>(self: Context<Services>, tag: T): Tag.Service<T>;
};
/**
* 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/Context"
*
* 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))
*
* @since 2.0.0
* @category unsafe
*/
export 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;
};
/**
* 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/Context"
* import * as O from "effect/Option"
*
* 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), O.some({ PORT: 8080 }))
* assert.deepStrictEqual(Context.getOption(Services, Timeout), O.none())
*
* @since 2.0.0
* @category getters
*/
export 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>;
};
/**
* 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/Context"
*
* 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 })
*
* @since 2.0.0
*/
export 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>;
};
/**
* 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/Context"
* import { pipe } from "effect/Function"
* import * as O from "effect/Option"
*
* 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), O.some({ PORT: 8080 }))
* assert.deepStrictEqual(Context.getOption(Services, Timeout), O.none())
*
* @since 2.0.0
*/
export declare const pick: <Services, S extends Array<ValidTagsById<Services>>>(...tags: S) => (self: Context<Services>) => Context<{
[k in keyof S]: Tag.Identifier<S[k]>;
}[number]>;
/**
* @since 2.0.0
*/
export declare const omit: <Services, S extends Array<ValidTagsById<Services>>>(...tags: S) => (self: Context<Services>) => Context<Exclude<Services, {
[k in keyof S]: Tag.Identifier<S[k]>;
}[keyof S]>>;
/**
* @since 2.0.0
* @category constructors
*/
export declare const Tag: <const Id extends string>(id: Id) => <Self, Shape>() => TagClass<Self, Id, Shape>;
export {};
//# sourceMappingURL=Context.d.ts.map