Skip to content

Inicio rápido

Empieza con dos clases normales y una cadena de composición explícita. Los scopes de petición llegan después, cuando el grafo básico ya está claro.

Instalación

bash
pnpm add @inferdi/inferdi
bash
npm install @inferdi/inferdi
bash
yarn add @inferdi/inferdi

Construir el grafo

ts
import { Container } from '@inferdi/inferdi'

export class Logger {
  info(message: string) {
    console.info(message)
  }
}

export class UserService {
  constructor(private readonly logger: Logger) {}

  find(id: string) {
    this.logger.info(`user=${id}`)
    return { id }
  }
}

export const root = new Container()
  .registerClass('logger', Logger, [])
  .registerClass('users', UserService, ['logger'])

export const users = root.get('users')

UserService no importa InferDI. El código de composición elige Logger, da nombre a ambos registros y fija el orden del constructor. El tipo devuelto para root ya contiene los dos servicios.

Si cambias el constructor y olvidas actualizar el grafo, el error aparece donde ensamblas la aplicación:

ts
import { 
Container
} from '@inferdi/inferdi'
class
Logger
{
info
(
message
: string) {}
} class
UserService
{
constructor(readonly
logger
:
Logger
, readonly
region
: string) {}
} new
Container
()
.
registerClass
('logger',
Logger
, [])
.
registerClass
('users',
UserService
, ['logger'])

Así funciona el estado de tipo del grafo: cada registro refina el tipo del contenedor y las operaciones posteriores deben encajar con lo ya declarado.

Resolver servicios

root.get('users') devuelve UserService de forma síncrona. El tiempo de vida predeterminado es singleton, así que las llamadas posteriores reciben la instancia en caché.

Los datos de petición necesitan un límite más corto. Decláralos como entrada de scope y entrégalos al abrir un scope hijo:

ts
import { Container } from '@inferdi/inferdi'

type RequestContext = {
  requestId: string
}

class RequestLog {
  constructor(readonly request: RequestContext) {}

  dispose() {
    console.info(`closed ${this.request.requestId}`)
  }
}

const root = new Container()
  .declareScopeInputs<{ request: RequestContext }>()
  .registerClass('requestLog', RequestLog, ['request'], 'scoped')

export async function handle(request: RequestContext) {
  const scope = root.createScope({ request })

  try {
    return scope.get('requestLog')
  } finally {
    await scope.dispose()
  }
}

El bloque finally cierra el hijo aunque falle el trabajo de la petición. scope.dispose() libera el RequestLog propiedad del scope; el valor request sigue siendo propiedad de la aplicación. Si tu toolchain de TypeScript admite Explicit Resource Management, puedes expresar el mismo cleanup con await using.

Elegir tiempos de vida

Tiempo de vidaPolítica de instanciaDueño de la cachéDueño del cleanup
singletonuna por dueño del registroroot o dueño del registroese contenedor
scopeduna por scope que resuelvescope hijoese scope
transientuna por resoluciónningunollamador

Los valores entregados mediante registerValue, .override() o entradas de scope también pertenecen a la aplicación. Un singleton no puede depender directamente de un servicio con scope o transitorio; InferDI rechaza la relación en los tipos y la comprueba otra vez en el contrato de runtime predeterminado.

El siguiente paso

Por qué InferDI explica las decisiones de diseño y Seguridad de tipos cubre todas las comprobaciones del grafo. Scopes y liberación detalla la propiedad; Adaptadores conecta los scopes con el ciclo de vida de la aplicación.