Values and configuration
addValue(token, value) registers something that already exists: parsed configuration, a constant, a client built by
another library, a test double. The container hands it out as is. The .NET equivalent is AddSingleton(instance).
Configuration with a typed token
Section titled “Configuration with a typed token”Interfaces don’t exist at runtime, so give configuration a token with createToken. Resolving the
token is typed without a generic argument:
import { class ServiceCollection
Collects service registrations, then builds a
ServiceProvider
.
Modeled on .NET's IServiceCollection.
ServiceCollection, function createToken<T>(name: string): InjectionToken<T>
Creates a typed token.
createToken } from '@ylcnfrht/ioc';
interface interface AppConfig
AppConfig { AppConfig.port: number
port: number; AppConfig.dbUrl: string
dbUrl: string; AppConfig.features: { signup: boolean;}
features: { signup: boolean
signup: boolean };}const const AppConfig: InjectionToken<AppConfig>
AppConfig = createToken<AppConfig>(name: string): InjectionToken<AppConfig>
Creates a typed token.
createToken<interface AppConfig
AppConfig>('AppConfig');
class class Database
Database { constructor(private readonly Database.config: AppConfig
config: interface AppConfig
AppConfig) {} Database.connect(): string
connect() { return this.Database.config: AppConfig
config.AppConfig.dbUrl: string
dbUrl; }}
const const services: ServiceCollection
services = new new ServiceCollection(): ServiceCollection
Collects service registrations, then builds a
ServiceProvider
.
Modeled on .NET's IServiceCollection.
ServiceCollection();const services: ServiceCollection
services.ServiceCollection.addValue<AppConfig>(token: Token<AppConfig>, value: NoInfer<AppConfig>): ServiceCollection
Registers an existing value (config, constants, an instance you built). Any value works, null and undefined included. Never disposed by the container.
addValue(const AppConfig: InjectionToken<AppConfig>
AppConfig, { AppConfig.port: number
port: var Number: NumberConstructor(value?: any) => number
An object that represents a number of any kind. All JavaScript numbers are 64-bit floating-point numbers.
Number(const process: { env: Record<string, string | undefined>;}
process.env: Record<string, string | undefined>
env.string | undefined
PORT ?? 3000), AppConfig.dbUrl: string
dbUrl: const process: { env: Record<string, string | undefined>;}
process.env: Record<string, string | undefined>
env.string | undefined
DATABASE_URL ?? 'postgres://localhost/app', AppConfig.features: { signup: boolean;}
features: { signup: boolean
signup: true },});const services: ServiceCollection
services.ServiceCollection.addSingleton<Database, [config: AppConfig]>(token: Newable<Database, [config: AppConfig]>, dependencies: readonly [config: Token<AppConfig>]): ServiceCollection (+4 overloads)
Registers a singleton: one instance for the root provider and all its scopes.
addSingleton(Class) / addSingleton(Class, [deps]): the class is its own token.
addSingleton(token, Implementation, [deps]): deps are checked against the constructor's parameters.
addSingleton(token, (provider) => instance): a factory, may be async.
addSingleton(class Database
Database, [const AppConfig: InjectionToken<AppConfig>
AppConfig]);
const const provider: ServiceProvider
provider = const services: ServiceCollection
services.ServiceCollection.buildServiceProvider(options?: BuildOptions): ServiceProvider
Builds the provider. Registrations are copied, so changing this collection afterwards
does not affect providers already built.
buildServiceProvider();const config = await const provider: ServiceProvider
provider.ServiceProvider.getRequiredService<AppConfig>(token: Token<AppConfig>): Promise<AppConfig>
Resolves token, throwing ServiceNotFoundError if it is not registered.
getRequiredService(const AppConfig: InjectionToken<AppConfig>
AppConfig);const config: AppConfig
addValue type-checks the value against the token: services.addValue(AppConfig, { port: '3000' }) doesn’t compile.
Small values: one token each
Section titled “Small values: one token each”Inject a single setting with its own token rather than the whole config object, when a class needs only one value:
const const ApiUrl: InjectionToken<string>
ApiUrl = createToken<string>(name: string): InjectionToken<string>
Creates a typed token.
createToken<string>('ApiUrl');const const RetryCount: InjectionToken<number>
RetryCount = createToken<number>(name: string): InjectionToken<number>
Creates a typed token.
createToken<number>('RetryCount');
class class ApiClient
ApiClient { constructor(readonly ApiClient.baseUrl: string
baseUrl: string, readonly ApiClient.retries: number
retries: number) {}}
const const services: ServiceCollection
services = new new ServiceCollection(): ServiceCollection
Collects service registrations, then builds a
ServiceProvider
.
Modeled on .NET's IServiceCollection.
ServiceCollection();const services: ServiceCollection
services.ServiceCollection.addValue<string>(token: Token<string>, value: string): ServiceCollection
Registers an existing value (config, constants, an instance you built). Any value works, null and undefined included. Never disposed by the container.
addValue(const ApiUrl: InjectionToken<string>
ApiUrl, 'https://api.example.com');const services: ServiceCollection
services.ServiceCollection.addValue<number>(token: Token<number>, value: number): ServiceCollection
Registers an existing value (config, constants, an instance you built). Any value works, null and undefined included. Never disposed by the container.
addValue(const RetryCount: InjectionToken<number>
RetryCount, 3);const services: ServiceCollection
services.ServiceCollection.addSingleton<ApiClient, [baseUrl: string, retries: number]>(token: Newable<ApiClient, [baseUrl: string, retries: number]>, dependencies: readonly [baseUrl: Token<string>, retries: Token<number>]): ServiceCollection (+4 overloads)
Registers a singleton: one instance for the root provider and all its scopes.
addSingleton(Class) / addSingleton(Class, [deps]): the class is its own token.
addSingleton(token, Implementation, [deps]): deps are checked against the constructor's parameters.
addSingleton(token, (provider) => instance): a factory, may be async.
addSingleton(class ApiClient
ApiClient, [const ApiUrl: InjectionToken<string>
ApiUrl, const RetryCount: InjectionToken<number>
RetryCount]);null and undefined are values too
Section titled “null and undefined are values too”Any value works, including null and undefined. They are registered values, not missing services:
const const Tracer: InjectionToken<{ span(name: string): void;} | null>
Tracer = createToken<{ span(name: string): void;} | null>(name: string): InjectionToken<{ span(name: string): void;} | null>
Creates a typed token.
createToken<{ function span(name: string): void
span(name: string
name: string): void } | null>('Tracer');
const const services: ServiceCollection
services = new new ServiceCollection(): ServiceCollection
Collects service registrations, then builds a
ServiceProvider
.
Modeled on .NET's IServiceCollection.
ServiceCollection();const services: ServiceCollection
services.ServiceCollection.addValue<{ span(name: string): void;} | null>(token: Token<{ span(name: string): void;} | null>, value: NoInfer<{ span(name: string): void;} | null>): ServiceCollection
Registers an existing value (config, constants, an instance you built). Any value works, null and undefined included. Never disposed by the container.
addValue(const Tracer: InjectionToken<{ span(name: string): void;} | null>
Tracer, null); // tracing disabled in this environment
const const provider: ServiceProvider
provider = const services: ServiceCollection
services.ServiceCollection.buildServiceProvider(options?: BuildOptions): ServiceProvider
Builds the provider. Registrations are copied, so changing this collection afterwards
does not affect providers already built.
buildServiceProvider();const provider: ServiceProvider
provider.ServiceProvider.isService<{ span(name: string): void;} | null>(token: Token<{ span(name: string): void;} | null>, key?: ServiceKey): boolean
True if token (or token with key) is registered. False once disposed.
isService(const Tracer: InjectionToken<{ span(name: string): void;} | null>
Tracer); // trueawait const provider: ServiceProvider
provider.ServiceProvider.getRequiredService<{ span(name: string): void;} | null>(token: Token<{ span(name: string): void;} | null>): Promise<{ span(name: string): void;} | null>
Resolves token, throwing ServiceNotFoundError if it is not registered.
getRequiredService(const Tracer: InjectionToken<{ span(name: string): void;} | null>
Tracer); // null, no errorgetService returns undefined both for “not registered” and for a registered undefined; use isService to tell them
apart.
Values are never disposed
Section titled “Values are never disposed”The container didn’t create a value, so it doesn’t own it. Disposing the provider does not call onDestroy,
[Symbol.asyncDispose] or [Symbol.dispose] on registered values. Close them yourself, or register a factory instead of a
value if the container should own the instance:
// Owned by the container: closed on provider.dispose()const services: ServiceCollection
services.ServiceCollection.addSingleton<Pool>(token: Token<Pool>, factory: ServiceFactory<NoInfer<Pool>>): ServiceCollection (+4 overloads)
Registers a singleton: one instance for the root provider and all its scopes.
addSingleton(Class) / addSingleton(Class, [deps]): the class is its own token.
addSingleton(token, Implementation, [deps]): deps are checked against the constructor's parameters.
addSingleton(token, (provider) => instance): a factory, may be async.
addSingleton(class Pool
Pool, () => { const const pool: Pool
pool = new constructor Pool(url: string): Pool
Pool('postgres://localhost/app'); return var Object: ObjectConstructor
Provides functionality common to all JavaScript objects.
Object.ObjectConstructor.assign<Pool, { onDestroy: () => Promise<void>;}>(target: Pool, source: { onDestroy: () => Promise<void>;}): Pool & { onDestroy: () => Promise<void>;} (+3 overloads)
Copy the values of all of the enumerable own properties from one or more source objects to a
target object. Returns the target object.
assign(const pool: Pool
pool, { onDestroy: () => Promise<void>
onDestroy: () => const pool: Pool
pool.Pool.end(): Promise<void>
end() });});Values and lifetimes
Section titled “Values and lifetimes”A value behaves like a singleton: every scope gets the same object, and
diagnostics show it as [SINGLETON] with no dependencies. Mutating it changes it for everyone.