Skip to content
Buy me a coffee

Dependencies

A class gets its dependencies through its constructor. At registration you list a token for each constructor parameter, in the same order. When the class is resolved, the container resolves each token and passes the results as arguments.

class
class OrderService
OrderService
{
constructor(
private readonly
OrderService.db: Database
db
:
class Database
Database
, // ← Database
private readonly
OrderService.logger: Logger
logger
:
interface Logger
Logger
, // ← Logger
) {}
}
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
().
ServiceCollection.addScoped<OrderService, [db: Database, logger: Logger]>(token: Newable<OrderService, [db: Database, logger: Logger]>, dependencies: readonly [db: Token<Database>, logger: Token<Logger>]): ServiceCollection (+4 overloads)

Registers a scoped service: one instance per scope. Same forms as

addSingleton

.

addScoped
(
class OrderService
OrderService
, [
class Database
Database
,
const Logger: InjectionToken<Logger>
Logger
]);

There are no decorators and no metadata: the array is the whole contract. It is also the only place where a service mentions the container, and the class itself stays a plain class you can construct in a test.

The dependency array is typed from the constructor: each position expects a token for the parameter at that position. A typed token (createToken<T>()) whose type doesn’t fit the parameter is rejected, and so is an array that is too short:

const services: ServiceCollection
services
.
ServiceCollection.addScoped<OrderService>(token: Token<OrderService>, factory: ServiceFactory<NoInfer<OrderService>>): ServiceCollection (+4 overloads)

Registers a scoped service: one instance per scope. Same forms as

addSingleton

.

addScoped
(
class OrderService
OrderService
, [
const Logger: InjectionToken<Logger>
Logger
,
class Database
Database
]); // Logger token where a Database goes
Error ts(2769) ― No overload matches this call. Overload 3 of 5, '(token: Token<OrderService>, implementation: Newable<NoInfer<OrderService>, []>): ServiceCollection', gave the following error. Argument of type '(InjectionToken<Logger> | typeof Database)[]' is not assignable to parameter of type 'Newable<NoInfer<OrderService>, []>'. Type '(InjectionToken<Logger> | typeof Database)[]' provides no match for the signature 'new (): NoInfer<OrderService>'.
const services: ServiceCollection
services
.
ServiceCollection.addScoped<OrderService>(token: Token<OrderService>, factory: ServiceFactory<NoInfer<OrderService>>): ServiceCollection (+4 overloads)

Registers a scoped service: one instance per scope. Same forms as

addSingleton

.

addScoped
(
class OrderService
OrderService
, [
class Database
Database
]); // one token short
Error ts(2769) ― No overload matches this call. Overload 1 of 5, '(token: Token<OrderService>, factory: ServiceFactory<NoInfer<OrderService>>): ServiceCollection', gave the following error. Argument of type '(typeof Database)[]' is not assignable to parameter of type 'ServiceFactory<NoInfer<OrderService>>'. Type '(typeof Database)[]' provides no match for the signature '(provider: ServiceProvider, key?: ServiceKey | undefined): NoInfer<OrderService> | Promise<NoInfer<OrderService>>'. Overload 2 of 5, '(token: Newable<OrderService, [db: Database, logger: Logger]>, dependencies: readonly [db: Token<Database>, logger: Token<Logger>]): ServiceCollection', gave the following error. Argument of type '[typeof Database]' is not assignable to parameter of type 'readonly [db: Token<Database>, logger: Token<Logger>]'. Source has 1 element(s) but target requires 2. Overload 3 of 5, '(token: Token<OrderService>, implementation: Newable<NoInfer<OrderService>, []>): ServiceCollection', gave the following error. Argument of type '(typeof Database)[]' is not assignable to parameter of type 'Newable<NoInfer<OrderService>, []>'. Type '(typeof Database)[]' provides no match for the signature 'new (): NoInfer<OrderService>'.

The check works for every form that takes a class and a dependency array: add*(Class, deps), add*(token, Impl, deps), tryAdd*, the keyed methods and replace. It also checks that the implementation fits the token: addSingleton(Logger, Database) fails because a Database is not a Logger.

Classes used as dependency tokens are compared with the constructor’s parameters like typed tokens are, so swapped class tokens are a compile error:

class
class Clock
Clock
{
Clock.now(): number
now
() {
return
var Date: DateConstructor

Enables basic storage and retrieval of dates and times.

Date
.
DateConstructor.now(): number

Returns the number of milliseconds elapsed since midnight, January 1, 1970 Universal Coordinated Time (UTC).

now
();
}
}
class
class Mailer
Mailer
{
Mailer.send(to: string): void
send
(
to: string
to
: string) {}
}
class
class Reports
Reports
{
constructor(readonly
Reports.clock: Clock
clock
:
class Clock
Clock
, readonly
Reports.mailer: Mailer
mailer
:
class Mailer
Mailer
) {}
}
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
().
ServiceCollection.addSingleton<Clock>(token: Newable<Clock, []>): 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 Clock
Clock
).
ServiceCollection.addSingleton<Mailer>(token: Newable<Mailer, []>): 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 Mailer
Mailer
).
ServiceCollection.addScoped<Reports>(token: Token<Reports>, factory: ServiceFactory<NoInfer<Reports>>): ServiceCollection (+4 overloads)

Registers a scoped service: one instance per scope. Same forms as

addSingleton

.

addScoped
(
class Reports
Reports
, [
class Mailer
Mailer
,
class Clock
Clock
]);
Error ts(2769) ― No overload matches this call. Overload 3 of 5, '(token: Token<Reports>, implementation: Newable<NoInfer<Reports>, []>): ServiceCollection', gave the following error. Argument of type '(typeof Clock | typeof Mailer)[]' is not assignable to parameter of type 'Newable<NoInfer<Reports>, []>'. Type '(typeof Clock | typeof Mailer)[]' provides no match for the signature 'new (): NoInfer<Reports>'.

TypeScript also compares types structurally, so two typed tokens whose types have the same shape are interchangeable.

The parameter types are inferred from the implementation. If you pass the service type explicitly, the remaining type parameter falls back to any[] and the array is no longer compared with the constructor:

// Compiles, and fails at runtime with the wrong arguments.
const services: ServiceCollection
services
.
ServiceCollection.addScoped<OrderService, any[]>(token: Newable<OrderService, any[]>, dependencies: readonly Token<any>[]): ServiceCollection (+4 overloads)

Registers a scoped service: one instance per scope. Same forms as

addSingleton

.

addScoped
<
class OrderService
OrderService
>(
class OrderService
OrderService
, [
const Logger: InjectionToken<Logger>
Logger
,
class Database
Database
]);

Let TypeScript infer. If a token needs a type, put it on the token (createToken<Logger>('Logger')), not on the call. Code migrated from @nodelibraries/ioc, which needed addSingleton<ILogger>(...), should drop those type arguments; see Migrating.

At runtime the container compares the number of tokens with the constructor’s declared parameter count (Function.length). Too few tokens throw a RegistrationError from the add* call itself, at startup, instead of creating a half-initialised object later:

try {
// @ts-expect-error -- OrderService needs [Database]
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
().
ServiceCollection.addScoped<OrderService>(token: Token<OrderService>, factory: ServiceFactory<NoInfer<OrderService>>): ServiceCollection (+4 overloads)

Registers a scoped service: one instance per scope. Same forms as

addSingleton

.

addScoped
(
class OrderService
OrderService
);
} catch (
var error: unknown
error
) {
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@since ― v0.1.100

log
(
var error: unknown
error
instanceof
class RegistrationError

A registration is invalid (e.g. a constructor needs more dependencies than were given).

RegistrationError
); // true
// 'OrderService' has 1 constructor parameter(s) but 0 dependency token(s) were given for 'OrderService'. …
}

This is the safety net for JavaScript code and for explicit <T> calls, where the compiler doesn’t help. Function.length stops at the first parameter with a default value and doesn’t count rest parameters, so those parameters may be left out of the array.

Whether a dependency is registered is only known once the collection is complete. By default it is checked when the service is resolved, and a missing one throws ServiceNotFoundError with the path that needed it (No service registered for 'Logger' (required by OrderService).). Pass validateOnBuild: true to buildServiceProvider to check every registration up front instead; see Validation.

The container tells implementations and factories apart by syntax. A function written with class is constructed with new and receives its dependencies. Any other function, an arrow or a function declaration, is a factory and is called as factory(provider, key).

class
class Mailer
Mailer
{}
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
()
.
ServiceCollection.addSingleton<unknown>(token: Token<unknown>, implementation: Newable<unknown, []>): 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
('mailer',
class Mailer
Mailer
) // class: constructed with new
.
ServiceCollection.addSingleton<unknown>(token: Token<unknown>, factory: ServiceFactory<unknown>): 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
('mailer2', () => new
constructor Mailer(): Mailer
Mailer
()); // arrow: called as a factory

Factories take no dependency array: they resolve what they need from the provider they receive. Their dependencies are therefore invisible to the compile-time check, to validateOnBuild and to the diagnostics. Prefer a class with a dependency array where you can.