ExisJS

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 TypeInterfaceDescriptionExample
Value ProviderValueProvider<T>Binds a static value, client instance, or configuration object.app.provide('CONFIG', { useValue: { env: 'prod' } })
Class ProviderClassProvider<T>Binds a class constructor instantiated on first resolution.app.provide('UserService', { useClass: UserService })
Factory ProviderFactoryProvider<T>Executes a factory function with resolved dependency tokens.app.provide({ provide: 'DB', useFactory: (cfg) => connect(cfg), inject: ['CONFIG'] })
Existing / Alias ProviderExistingProvider<T>Creates an alias pointing to another registered provider token.app.provide({ provide: 'DB_ALIAS', useExisting: 'DB' })
Direct ClassClassConstructor<T>Direct constructor binding without an options wrapper.app.provide(AuthService, AuthService)

2. Provider Scopes

ExisJS supports three provider lifecycle scopes:

ScopeLifetimeDescription
'singleton' (Default)Application LifetimeInstantiated once and cached in memory across all requests.
'request'Request LifetimeInstantiated once per incoming HTTP request; isolated in request-scoped cache.
'transient'Resolution LifetimeRe-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>): T

Example

src/http/users/route.ts
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

src/services/orders.ts
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}
MethodSignatureDescription
provide(token, provider)(token, provider) => voidRegisters a provider definition under a token.
resolve(token, cache?)(token, cache?) => TResolves an instance matching the token.
instantiateClass(Target, cache?)(Target, cache?) => TInstantiates a class, resolving constructor parameters recursively.
clearCache()() => voidClears all cached singleton instances and in-flight locks.

7. Lifecycle Hooks

Providers registered in the container can implement standard lifecycle interfaces:

Hook InterfaceMethodTriggered When
OnInitonInit(): Promise<void> | voidApplication server starts and container boots.
OnDestroyonDestroy(): Promise<void> | voidServer receives termination signal (SIGTERM, SIGINT) or app.close().
OnApplicationBootstraponApplicationBootstrap(): Promise<void> | voidAll modules have initialized before listening for connections.