Type-safe service management and dependency injection system using Context and Tag for organizing and providing services throughout your application with complete compile-time safety.
The Context system provides a type-safe way to manage services and dependencies.
/**
* A Context is a collection of services
*/
interface Context<in Services> {}
/**
* A Tag identifies a specific service type
*/
interface Tag<in in out Self, in Type> {
readonly _tag: "Tag";
readonly identifier: string;
}
declare namespace Context {
/**
* Creates an empty context
*/
function empty(): Context<never>;
/**
* Creates a context with services
*/
function make<Services>(): Context<Services>;
/**
* Adds a service to the context
*/
function add<T>(tag: Tag<T, T>, service: T): <Services>(self: Context<Services>) => Context<Services | T>;
/**
* Gets a service from the context
*/
function get<Services, T>(tag: Tag<T, T>): (self: Context<Services>) => T;
/**
* Checks if a service exists in the context
*/
function has<T>(tag: Tag<T, T>): <Services>(self: Context<Services>) => boolean;
/**
* Removes a service from the context
*/
function omit<T>(tag: Tag<T, T>): <Services>(self: Context<Services>) => Context<Exclude<Services, T>>;
/**
* Merges two contexts
*/
function merge<Services1, Services2>(that: Context<Services2>): (self: Context<Services1>) => Context<Services1 | Services2>;
/**
* Picks specific services from a context
*/
function pick<Services, S extends Services>(...tags: Tag<S, S>[]): (self: Context<Services>) => Context<S>;
}Functions for creating and managing service tags and implementations.
/**
* Creates a generic service tag
*/
function GenericTag<Self, Type = Self>(identifier: string): Tag<Self, Type>;
/**
* Creates a service tag with validation
*/
function Tag<Self, Type = Self>(identifier: string): Tag<Self, Type>;Usage Examples:
import { Context, Effect } from "effect";
// Define services
interface Logger {
log(message: string): Effect<void>;
}
interface Database {
query(sql: string): Effect<unknown[]>;
}
// Create service tags
const Logger = Context.GenericTag<Logger>("Logger");
const Database = Context.GenericTag<Database>("Database");
// Create service implementations
const logger: Logger = {
log: (message) => Effect.sync(() => console.log(message))
};
const database: Database = {
query: (sql) => Effect.sync(() => [{ id: 1, name: "test" }])
};
// Build context
const context = pipe(
Context.empty(),
Context.add(Logger, logger),
Context.add(Database, database)
);
// Use services in effects
const program = Effect.gen(function* () {
const logger = yield* Logger;
const database = yield* Database;
yield* logger.log("Starting query");
const results = yield* database.query("SELECT * FROM users");
yield* logger.log(`Found ${results.length} users`);
return results;
});
// Run with context
const result = await Effect.runPromise(
pipe(program, Effect.provideContext(context))
);Functions for working with services in Effect computations.
declare namespace Effect {
/**
* Access a service from the context
*/
function service<T>(tag: Tag<T, T>): Effect<T, never, T>;
/**
* Provide a service to an Effect
*/
function provideService<T>(tag: Tag<T, T>, service: T): <A, E, R>(self: Effect<A, E, R>) => Effect<A, E, Exclude<R, T>>;
/**
* Provide multiple services at once
*/
function provideContext<R>(context: Context<R>): <A, E>(self: Effect<A, E, R>) => Effect<A, E>;
/**
* Access the entire context
*/
function context<R>(): Effect<Context<R>, never, R>;
/**
* Provide a service using an Effect
*/
function provideServiceEffect<T, E, R>(
tag: Tag<T, T>,
effect: Effect<T, E, R>
): <A, E1, R1>(self: Effect<A, E1, R1 | T>) => Effect<A, E | E1, R | Exclude<R1, T>>;
}Usage Examples:
import { Context, Effect, pipe } from "effect";
// Service-based architecture
interface ApiClient {
get(url: string): Effect<unknown, Error>;
post(url: string, data: unknown): Effect<unknown, Error>;
}
interface UserService {
getUser(id: string): Effect<User, Error>;
createUser(data: CreateUserData): Effect<User, Error>;
}
const ApiClient = Context.GenericTag<ApiClient>("ApiClient");
const UserService = Context.GenericTag<UserService>("UserService");
// Implementation that depends on other services
const userService: UserService = {
getUser: (id) => Effect.gen(function* () {
const client = yield* ApiClient;
const user = yield* client.get(`/users/${id}`);
return user as User;
}),
createUser: (data) => Effect.gen(function* () {
const client = yield* ApiClient;
const user = yield* client.post("/users", data);
return user as User;
})
};
// Usage in application
const app = Effect.gen(function* () {
const userSvc = yield* UserService;
const user = yield* userSvc.getUser("123");
yield* Effect.log(`Retrieved user: ${user.name}`);
return user;
});// Service identifier type
type ServiceIdentifier = string;
// Context variance
interface Context<in Services> {
readonly [ContextTypeId]: {
readonly _Services: Contravariant<Services>;
};
}
// Tag variance
interface Tag<in in out Self, in Type> {
readonly [TagTypeId]: {
readonly _Self: Invariant<Self>;
readonly _Type: Contravariant<Type>;
};
}
// Variance markers
interface Contravariant<T> {
readonly _T: (_: T) => void;
}
interface Invariant<T> {
readonly _T: {
readonly _A: (_: never) => T;
readonly _B: (_: T) => void;
};
}