Skip to content

Factorías

Usa registerClass cuando la construcción sea exactamente new Ctor(...deps). Usa registerFactory cuando necesites configuración, una API externa, vincular una interfaz u otra lógica explícita.

ts
const container = new Container()
  .registerValue('config', {
    dsn: 'postgres://localhost/app',
    poolSize: 10
  })
  .registerFactory('pool', (c) => {
    const { dsn, poolSize } = c.get('config')
    return new Pool({ connectionString: dsn, max: poolSize })
  })
  .registerClass('users', UserRepository, ['pool'])

El tipo devuelto se convierte en el tipo del servicio registrado.

Factorías con acceso al contenedor

El callback básico recibe un contenedor filtrado por tiempo de vida. Una factoría singleton solo puede resolver dependencias seguras para singleton; TypeScript rechaza las claves scoped y transient.

ts
const root = new Container()
  .registerValue('prefix', 'app')
  .registerFactory('logger', (c) => new Logger(c.get('prefix')))

Usa esta forma para una resolución condicional o en varios pasos. Todas las llamadas a .get() deben ser síncronas. Si el límite de inicialización debe propagarse por el grafo, usa registerAsyncFactory.

Dependencias declaradas de una factoría

El overload con deps hace visibles los requisitos de la factoría y limita el resolver del callback a esas claves:

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

Las dependencias declaradas propagan los requisitos de entradas de scope y tiempos de vida a módulos y registros posteriores. A diferencia de registerAsyncFactory, el callback recibe un resolver, no valores posicionales.

Tiempos de vida de las factorías

Las factorías usan los mismos tiempos de vida que las clases:

ts
const root = new Container()
  .registerFactory('cache', () => new Cache(), 'singleton')
  .registerFactory('requestState', () => new RequestState(), 'scoped')
  .registerFactory('operation', () => new Operation(), 'transient')

Los resultados singleton y scoped se almacenan y pertenecen al contenedor. InferDI no almacena ni libera los resultados transient.

El cuarto argumento puede ser un lazyKey que crea un companion y conserva el tiempo de vida del objetivo:

ts
const root = new Container()
  .registerFactory('cache', () => new Cache(), 'singleton', 'cacheLazy')

root.get('cacheLazy').get()

Al crear un companion hay que indicar el tiempo de vida, incluido 'singleton'. Consulta Inyección perezosa para ver las demás reglas.

Vincular interfaces

Las interfaces no tienen constructor en runtime. Indica el tipo de servicio de forma explícita cuando los consumidores deban depender de una abstracción:

ts
interface Mailer {
  send(message: string): void
}

class SendGridMailer implements Mailer {
  send(message: string) {}
}

const container = new Container()
  .registerFactory<'mailer', Mailer>(
    'mailer',
    () => new SendGridMailer()
  )

Los consumidores de mailer ven ahora Mailer, no SendGridMailer.

Elegir el contrato de Promise

Una Promise devuelta por registerFactory es el propio valor del servicio: .get() devuelve Promise<T> y las factorías dependientes reciben esa misma Promise. Usa esta forma solo cuando la Promise pertenezca al grafo síncrono.

Si el servicio es T y la Promise representa su inicialización, usa registerAsyncFactory. El estado asíncrono se propaga y el servicio se resuelve con .getAsync(). Dependencias asíncronas explica ambos contratos, la caché, los companions lazy, los errores y la liberación de recursos.