Skip to content
Buy me a coffee

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).

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.

@example

const services = new ServiceCollection();
services.addSingleton(Logger, ConsoleLogger);
services.addScoped(UserService, [Logger]);
const provider = services.buildServiceProvider({ validateScopes: true });

ServiceCollection
,
function createToken<T>(name: string): InjectionToken<T>

Creates a typed token.

@example

interface Logger { log(msg: string): void }
const Logger = createToken<Logger>('Logger');
services.addSingleton(Logger, ConsoleLogger);
const logger = await provider.getRequiredService(Logger); // typed as Logger

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.

@example

interface Logger { log(msg: string): void }
const Logger = createToken<Logger>('Logger');
services.addSingleton(Logger, ConsoleLogger);
const logger = await provider.getRequiredService(Logger); // typed as Logger

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.

@example

const services = new ServiceCollection();
services.addSingleton(Logger, ConsoleLogger);
services.addScoped(UserService, [Logger]);
const provider = services.buildServiceProvider({ validateScopes: true });

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.

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.

@example

interface Logger { log(msg: string): void }
const Logger = createToken<Logger>('Logger');
services.addSingleton(Logger, ConsoleLogger);
const logger = await provider.getRequiredService(Logger); // typed as Logger

createToken
<string>('ApiUrl');
const
const RetryCount: InjectionToken<number>
RetryCount
=
createToken<number>(name: string): InjectionToken<number>

Creates a typed token.

@example

interface Logger { log(msg: string): void }
const Logger = createToken<Logger>('Logger');
services.addSingleton(Logger, ConsoleLogger);
const logger = await provider.getRequiredService(Logger); // typed as Logger

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.

@example

const services = new ServiceCollection();
services.addSingleton(Logger, ConsoleLogger);
services.addScoped(UserService, [Logger]);
const provider = services.buildServiceProvider({ validateScopes: true });

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
]);

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.

@example

interface Logger { log(msg: string): void }
const Logger = createToken<Logger>('Logger');
services.addSingleton(Logger, ConsoleLogger);
const logger = await provider.getRequiredService(Logger); // typed as Logger

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.

@example

const services = new ServiceCollection();
services.addSingleton(Logger, ConsoleLogger);
services.addScoped(UserService, [Logger]);
const provider = services.buildServiceProvider({ validateScopes: true });

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
); // true
await
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 error

getService returns undefined both for “not registered” and for a registered undefined; use isService to tell them apart.

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.

@param ― target The target object to copy to.

@param ― source The source object from which to copy properties.

assign
(
const pool: Pool
pool
, {
onDestroy: () => Promise<void>
onDestroy
: () =>
const pool: Pool
pool
.
Pool.end(): Promise<void>
end
() });
});

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.