Complete API documentation for Illuma's core classes, functions, and decorators.
- NodeContainer
- NodeToken
- MultiNodeToken
- nodeInject
- injectDefer
- Injector
- Decorators
- Async Injection Functions
- Plugin API
- Type Definitions
The main dependency injection container.
new NodeContainer(options?: {
measurePerformance?: boolean;
diagnostics?: boolean;
instant?: boolean;
parent?: iDIContainer;
})| Parameter | Type | Default | Description |
|---|---|---|---|
options.measurePerformance |
boolean |
false |
Enable performance monitoring |
options.diagnostics |
boolean |
false |
Enable diagnostics reporting |
options.instant |
boolean |
true |
Whether to instantiate consumers immediately on bootstrap (true) or lazily (false) |
options.parent |
iDIContainer |
undefined |
Optional parent container for hierarchical injection |
Register a provider or array of providers in the container.
// Single provider
container.provide(UserService);
// Provider object
container.provide({
provide: CONFIG,
value: { apiUrl: 'https://api.example.com' }
});
// Array of providers
container.provide([
UserService,
DatabaseService,
{ provide: CONFIG, value: config }
]);Build the dependency tree and prepare the container for use. Must be called before get().
container.provide([UserService, DatabaseService]);
container.bootstrap();Retrieve an instance from the container. Container must be bootstrapped first.
const userService = container.get(UserService);
const config = container.get(CONFIG);Create a new instance with dependencies injected, without registering it in the container. Accepts either an injectable class or a factory function.
// With an injectable class
@NodeInjectable()
class RequestHandler {
private readonly logger = nodeInject(Logger);
}
const handler = container.produce(RequestHandler);
// handler is not registered in container
// Each call creates a new instance
// With a factory function
const config = container.produce(() => {
const env = nodeInject(Environment);
return { apiUrl: env.apiUrl, timeout: 5000 };
});Register a middleware function to run during instance creation for this container.
container.registerMiddleware((params, next) => {
console.log('Instantiating', params.token.name);
return next(params);
});A token for identifying non-class dependencies.
new NodeToken<T>(name: string, options?: { factory?: () => T })| Parameter | Type | Description |
|---|---|---|
name |
string |
Unique identifier for the token |
options.factory |
() => T |
Optional factory for default value |
const API_URL = new NodeToken<string>('API_URL');
container.provide(API_URL.withValue('https://api.example.com'));const CONFIG = new NodeToken<Config>('CONFIG');
container.provide(CONFIG.withFactory(() => loadConfig()));const LOGGER = new NodeToken<Logger>('LOGGER');
container.provide(LOGGER.withClass(ConsoleLogger));const DB = new NodeToken<Database>('DB');
container.provide(DB.withAlias(PRIMARY_DB));A token that can have multiple providers, returning an array.
new MultiNodeToken<T>(name: string, options?: { factory?: () => T })const PLUGINS = new MultiNodeToken<Plugin>('PLUGINS');
container.provide([
PLUGINS.withClass(LoggingPlugin),
PLUGINS.withClass(MetricsPlugin)
]);
const plugins = container.get(PLUGINS); // Plugin[]Same as NodeToken: withValue(), withFactory(), withClass(), withAlias().
Inject a dependency into a class field or factory function.
function nodeInject<T>(
token: Token<T>,
options?: { optional?: boolean }
): T| Parameter | Type | Description |
|---|---|---|
token |
Token<T> |
The token or class to inject |
options.optional |
boolean |
If true, returns null when not found |
@NodeInjectable()
class UserService {
private readonly logger = nodeInject(Logger);
private readonly cache = nodeInject(CacheService, { optional: true });
public getUser(id: string) {
this.logger.log(`Fetching user ${id}`);
return this.cache?.get(id) ?? this.fetchFromDb(id);
}
}Lazily inject a dependency. Useful for handling circular dependencies or deferring resolution in a cost of transparency while bootstrapping.
If the only injection point for the dependency is via injectDefer, it may appear unused in diagnostics.
function injectDefer<T>(
token: Token<T>,
options?: { optional?: boolean }
): () => T| Parameter | Type | Description |
|---|---|---|
token |
Token<T> |
The token or class to inject |
options.optional |
boolean |
If true, returns function returning T | null |
@NodeInjectable()
class ServiceA {
// Returns a function that resolves the dependency when called
private readonly injectB = injectDefer(ServiceB);
private get b(): ServiceB {
return this.injectB();
}
public doSomething() {
// Call the getter to access the instance
this.b.method();
}
}
// Note: injectDefer returns a function, so you must call it to get the instance or array of instances.Token for accessing the DI container from within services.
Retrieve a registered instance from the container.
@NodeInjectable()
class PluginManager {
private readonly injector = nodeInject(Injector);
public executePlugin(token: Token<Plugin>) {
const plugin = this.injector.get(token);
plugin.execute();
}
}Create a new instance with dependencies injected. Accepts either an injectable class or a factory function.
@NodeInjectable()
class FactoryService {
private readonly injector = nodeInject(Injector);
// With an injectable class
public createHandler() {
return this.injector.produce(RequestHandler);
}
// With a factory function
public createCustomConfig(data) {
return this.injector.produce(() => {
const env = nodeInject(Environment);
return { apiUrl: env.apiUrl, ...data };
});
}
}Mark a class as injectable.
@NodeInjectable()
class UserService {
private readonly db = nodeInject(DatabaseService);
}Requires: experimentalDecorators: true in tsconfig.json
Alternative to @NodeInjectable() without decorators.
import { makeInjectable } from '@illuma/core';
class _UserService {
public getUser() { return { id: 1 }; }
}
export type UserService = _UserService;
export const UserService = makeInjectable(_UserService);Registers a class as injectable with a specific token using the internal WeakMap registry.
This is primarily used internally by both @NodeInjectable and makeInjectable but is exposed for plugins to implement custom decorators.
function registerClassAsInjectable<T>(ctor: Ctor<T>, token: NodeToken<T>): void| Parameter | Type | Description |
|---|---|---|
ctor |
Ctor<T> |
The class constructor to register |
token |
NodeToken<T> |
The token to associate with it |
Example: Creating a custom decorator
function CustomService(name: string) {
return (ctor: any) => {
const token = new NodeToken(name, { factory: () => new ctor() });
registerClassAsInjectable(ctor, token);
return ctor;
};
}Lazily inject a single dependency.
function injectAsync<T>(
fn: () => Token<T> | Promise<Token<T>>,
options?: {
withCache?: boolean;
overrides?: Provider[];
injector?: iInjector;
}
): () => Promise<T | T[]>| Option | Type | Default | Description |
|---|---|---|---|
withCache |
boolean |
true |
Cache the resolved instance |
overrides |
Provider[] |
[] |
Additional providers for the sub-container |
injector |
iInjector |
undefined |
Explicit injector to use instead of context |
private readonly getAnalytics = injectAsync(
() => import('./analytics').then(m => m.AnalyticsService)
);
public async track(event: string) {
const analytics = await this.getAnalytics();
analytics.track(event);
}Create an isolated sub-container with an array of providers.
function injectGroupAsync(
fn: () => Provider<unknown>[] | Promise<Provider<unknown>[]>,
options?: {
withCache?: boolean;
overrides?: Provider[];
injector?: iInjector;
}
): () => Promise<iInjector>private readonly getPluginContainer = injectGroupAsync(
() => import('./plugins').then(m => m.providePlugins())
);
public async executePlugins() {
const injector = await this.getPluginContainer();
const plugins = injector.get(PLUGINS);
}Create a sub-container with a specific entrypoint token and providers.
function injectEntryAsync<T>(
fn: () => iEntrypointConfig<Token<T>> | Promise<iEntrypointConfig<Token<T>>>,
options?: {
withCache?: boolean;
overrides?: Provider[];
injector?: iInjector;
}
): () => Promise<T | T[]>// in user.service.ts
const USERS_CONFIG = new NodeToken<{ table: string }>('USERS_CONFIG');
@NodeInjectable()
class UserService {
private readonly db = nodeInject(DatabaseService); // Declared in parent container
private readonly config = nodeInject(USERS_CONFIG);
public getUsers() {
return this.db.query(`SELECT * FROM ${this.config.table}`);
}
}
export const userModule: iEntrypointConfig<UserService> = {
entrypoint: UserService,
providers: [
UserService,
{ provide: USERS_CONFIG, value: { table: 'users' } }
],
};// in app.service.ts
@NodeInjectable()
class AppService {
private readonly getUserService = injectEntryAsync(() =>
import('./user.service').then(m => m.userModule)
);
public async listUsers() {
const userService = await this.getUserService();
// userService is resolved with DatabaseService injected from parent container
// and USERS_CONFIG provided in the sub-container
return userService.getUsers();
}
}Static methods available on the Illuma class for hooking into the DI system.
Register a custom scanner to detect injection points. Illuma's default scanner detects nodeInject calls by executing the factory in a proxy context. You can add scanners to support other forms of injection detection.
import { Illuma, type iContextScanner } from '@illuma/core/plugins';
const myScanner: iContextScanner = {
scan(factory) {
// Custom logic to analyze the factory function
// Return a Set of injection nodes found
return new Set();
}
};
Illuma.extendContextScanner(myScanner);Enable the built-in diagnostics system, which includes a default reporter and performance tracking middleware. This function must be called before bootstrapping any container that should have diagnostics enabled.
import { enableIllumaDiagnostics } from '@illuma/core/plugins';
// Enable diagnostics before creating containers
enableIllumaDiagnostics();
// Now containers will report diagnostics after bootstrap
const container = new NodeContainer();
container.provide([...]);
container.bootstrap();
// → Diagnostics output will be loggedNote: The
diagnostics: trueoption inNodeContainerconstructor is deprecated. UseenableIllumaDiagnostics()instead.
Register a custom diagnostics module. These modules receive a report after a container is bootstrapped, providing insights into the dependency graph.
You must call enableIllumaDiagnostics() before bootstrapping to enable the diagnostics system.
import { Illuma, enableIllumaDiagnostics, type iDiagnosticsModule } from '@illuma/core/plugins';
const reporter: iDiagnosticsModule = {
onReport(report) {
console.log(`Total nodes: ${report.totalNodes}`);
console.log(`Unused nodes: ${report.unusedNodes.length}`);
console.log(`Bootstrap time: ${report.bootstrapDuration}ms`);
}
};
// Enable diagnostics and register custom reporter
enableIllumaDiagnostics();
Illuma.extendDiagnostics(reporter);Register a middleware function that will run for all containers and providers. This allows you to intercept the instantiation of every provider in your application, which is useful for cross-cutting concerns like logging, performance profiling, mocking, or instance transformation.
Each middleware receives an iInstantiationParams object and a next function. You must call next(params) to proceed with the instantiation (or return a custom value to bypass it).
type iMiddleware<T = unknown> = (
params: iInstantiationParams<T>,
next: (params: iInstantiationParams<T>) => T,
) => T;
interface iInstantiationParams<T> {
readonly token: NodeBase<T>; // The token being resolved
readonly factory: () => T; // The factory function that creates the instance
readonly deps: Set<Token<unknown>>; // Dependencies detected for this node
}import { Illuma } from '@illuma/core/plugins';
Illuma.registerGlobalMiddleware((params, next) => {
const start = performance.now();
// Proceed with instantiation
const instance = next(params);
const end = performance.now();
console.log(`[${params.token.name}] instantiated in ${(end - start).toFixed(2)}ms`);
return instance;
});Union type for dependency identifiers.
type Token<T> = NodeToken<T> | MultiNodeToken<T> | Ctor<T>;Constructor type.
type Ctor<T> = new (...args: any[]) => T;Any provider type.
type Provider<T = unknown> =
| NodeBase<T>
| iNodeProvider<T>
| Ctor<T>
| Provider[];interface iNodeValueProvider<T> {
provide: Token<T>;
value: T;
}
interface iNodeFactoryProvider<T> {
provide: Token<T>;
factory: () => T;
}
interface iNodeClassProvider<T> {
provide: Token<T>;
useClass: Ctor<T>;
}
interface iNodeAliasProvider<T> {
provide: Token<T>;
alias: Token<T>;
}Interface for container/injector access.
interface iInjector {
get<T>(token: Token<T>): T;
produce<T>(fn: Ctor<T> | (() => T)): T;
}Middleware function type.
type iMiddleware<T = unknown> = (
params: iInstantiationParams<T>,
next: (params: iInstantiationParams<T>) => T,
) => T;Parameters passed to middleware.
interface iInstantiationParams<T = unknown> {
readonly token: NodeBase<T>;
readonly factory: () => T;
readonly deps: Set<Token<unknown>>;
}- Getting Started - Setup and basic concepts
- Providers Guide - Provider types in detail
- Tokens Guide - Using NodeToken and MultiNodeToken
- Async Injection Guide - Advanced async patterns
- Testing Guide - Testing with Illuma
- Error Reference - Troubleshooting