exisjs/di
The exisjs/di module provides the Inversion of Control (IoC) dependency injection container for ExisJS. It supports functional context-driven injection (inject()), class constructor and property decorators (@Inject(), @Optional()), circular dependency resolution (forwardRef()), and multiple lifecycle scopes.
Module Exports
import { Container, inject, forwardRef, isForwardRef, Inject, Optional, INJECT_METADATA, PROPERTY_INJECT_METADATA, OPTIONAL_METADATA, SCOPE_METADATA,} from 'exisjs/di'
import type { ProviderToken, ProviderDefinition, CustomProvider, ValueProvider, FactoryProvider, ClassProvider, ExistingProvider, ClassConstructor, ForwardReference,} from 'exisjs/di'1. Provider Definitions & Types
Dependencies are registered with the container using tokens (classes, string identifiers, symbols, or forward references):
export type ProviderToken<T = any> = | string | symbol | (new (...args: any[]) => T) | ForwardReference<T>Provider Variants
| Provider Type | Interface | Description | Example |
|---|---|---|---|
| Value Provider | ValueProvider<T> | Binds a static value, client instance, or configuration object. | app.provide('CONFIG', { useValue: { env: 'prod' } }) |
| Class Provider | ClassProvider<T> | Binds a class constructor instantiated on first resolution. | app.provide('UserService', { useClass: UserService }) |
| Factory Provider | FactoryProvider<T> | Executes a factory function with resolved dependency tokens. | app.provide({ provide: 'DB', useFactory: (cfg) => connect(cfg), inject: ['CONFIG'] }) |
| Existing / Alias Provider | ExistingProvider<T> | Creates an alias pointing to another registered provider token. | app.provide({ provide: 'DB_ALIAS', useExisting: 'DB' }) |
| Direct Class | ClassConstructor<T> | Direct constructor binding without an options wrapper. | app.provide(AuthService, AuthService) |
2. Provider Scopes
ExisJS supports three provider lifecycle scopes:
| Scope | Lifetime | Description |
|---|---|---|
'singleton' (Default) | Application Lifetime | Instantiated once and cached in memory across all requests. |
'request' | Request Lifetime | Instantiated once per incoming HTTP request; isolated in request-scoped cache. |
'transient' | Resolution Lifetime | Re-instantiated every single time the dependency is requested. |
app.provide('TraceLogger', { useClass: RequestScopedLogger, scope: 'request',})3. Functional Injection (inject())
Resolves dependencies from within active route handlers, middleware functions, or helper routines using AsyncLocalStorage.
function inject<T>(token: ProviderToken<T>): TExample
import { controller, route } from 'exisjs/router'import { inject } from 'exisjs/di'import { DatabaseService } from '../services/database'
export default controller({ list: route.get('/', { async handle() { // Resolved from active request execution context const db = inject(DatabaseService) const apiKey = inject<string>('API_KEY')
return db.query('SELECT * FROM users') }, }),})4. Class-Based OOP Injection
@Inject(token)
Parameter and property decorator specifying an explicit injection token:
import { Controller, Get } from 'exisjs/decorators'import { Inject } from 'exisjs/di'import { UserService } from './service'
@Controller('/users')export default class UserController { @Inject('CONFIG') private config!: Record<string, any>
constructor( @Inject(UserService) private userService: UserService, @Inject('DATABASE_URL') private dbUrl: string ) {}}@Optional()
Marks a dependency as optional, resolving to undefined without throwing if not present in the container:
import { Inject, Optional } from 'exisjs/di'
export class NotificationService { constructor( @Inject('SLACK_WEBHOOK') @Optional() private slackUrl?: string ) {}}5. Circular Dependency Resolution (forwardRef)
Resolves mutually dependent services by deferring token resolution until instantiation time:
function forwardRef<T>(fn: () => ProviderToken<T>): ForwardReference<T>Example
import { Injectable } from 'exisjs/decorators'import { Inject, forwardRef } from 'exisjs/di'import { PaymentService } from './payments'
@Injectable()export class OrderService { constructor( @Inject(forwardRef(() => PaymentService)) private paymentService: PaymentService ) {}}6. Container Class API
The core IoC container coordinating provider registration and resolution.
export class Container { provide<T>(token: ProviderToken<T>, provider: ProviderDefinition<T>): void resolve<T>(token: ProviderToken<T>, requestCache?: Map<any, any>): T instantiateClass<T>(TargetClass: ClassConstructor<T>, requestCache?: Map<any, any>): T clearCache(): void}| Method | Signature | Description |
|---|---|---|
provide(token, provider) | (token, provider) => void | Registers a provider definition under a token. |
resolve(token, cache?) | (token, cache?) => T | Resolves an instance matching the token. |
instantiateClass(Target, cache?) | (Target, cache?) => T | Instantiates a class, resolving constructor parameters recursively. |
clearCache() | () => void | Clears all cached singleton instances and in-flight locks. |
7. Lifecycle Hooks
Providers registered in the container can implement standard lifecycle interfaces:
| Hook Interface | Method | Triggered When |
|---|---|---|
OnInit | onInit(): Promise<void> | void | Application server starts and container boots. |
OnDestroy | onDestroy(): Promise<void> | void | Server receives termination signal (SIGTERM, SIGINT) or app.close(). |
OnApplicationBootstrap | onApplicationBootstrap(): Promise<void> | void | All modules have initialized before listening for connections. |