Skip to content

Inyección de Dependencias

Wabot utiliza inyección de dependencias (DI) para resolver automáticamente las clases que necesita tu aplicación. Esto significa que no tienes que instanciar manualmente tus servicios, repositorios o módulos — el framework se encarga de crearlos y conectarlos.

Conceptos clave

El sistema DI de Wabot se basa en tsyringe y ofrece tres ciclos de vida para las dependencias:

  • Singleton (@singleton()): una única instancia compartida en toda la aplicación.
  • Injectable (@injectable()): se crea una nueva instancia cada vez que se resuelve.
  • Scoped (@scoped(Lifecycle.ContainerScoped)): una instancia por contenedor hijo (por request HTTP, por conexión socket, etc.).

Decoradores

@singleton()

Crea una única instancia que se reutiliza en toda la aplicación. Ideal para servicios de configuración, repositorios y servicios que mantienen estado global.

import { singleton } from '@wabot-dev/framework'
@singleton()
export class ProductCatalog {
private products: Map<string, Product> = new Map()
addProduct(product: Product) {
this.products.set(product.id, product)
}
findById(id: string): Product | undefined {
return this.products.get(id)
}
}

@injectable()

Crea una nueva instancia cada vez que se solicita. Útil para servicios sin estado o que deben ser independientes entre sí.

import { injectable } from '@wabot-dev/framework'
@injectable()
export class PriceCalculator {
calculate(unitPrice: number, quantity: number, discount: number): number {
const subtotal = unitPrice * quantity
return subtotal - (subtotal * discount / 100)
}
}

@scoped(Lifecycle.ContainerScoped)

Crea una instancia por contenedor hijo. El framework crea contenedores hijos automáticamente para cada request HTTP y cada conexión socket, lo que permite tener servicios con estado aislado por request.

import { scoped, Lifecycle } from '@wabot-dev/framework'
@scoped(Lifecycle.ContainerScoped)
export class RequestContext {
private userId?: string
setUserId(id: string) {
this.userId = id
}
getUserId(): string {
if (!this.userId) throw new Error('Usuario no autenticado')
return this.userId
}
}

@inject('token')

Permite inyectar dependencias mediante un token string en lugar de un tipo. Útil para inyectar valores de configuración o interfaces.

import { injectable, inject } from '@wabot-dev/framework'
@injectable()
export class NotificationService {
constructor(
@inject('SMTP_HOST') private smtpHost: string,
@inject('SMTP_PORT') private smtpPort: number,
) {}
async send(to: string, message: string) {
// Enviar notificación usando smtpHost y smtpPort
}
}

Para registrar un token en el contenedor:

import { container } from '@wabot-dev/framework'
container.register('SMTP_HOST', { useValue: 'smtp.example.com' })
container.register('SMTP_PORT', { useValue: 587 })

Resolución manual

En la mayoría de los casos, las dependencias se resuelven automáticamente a través de los constructores. Pero cuando necesitas resolver manualmente:

import { container } from '@wabot-dev/framework'
// Resolver una instancia
const catalog = container.resolve(ProductCatalog)
// Crear un contenedor hijo (aislado)
const childContainer = container.createChildContainer()
childContainer.register('USER_ID', { useValue: '123' })
const service = childContainer.resolve(UserService)

Cómo usa Wabot los contenedores hijos

El framework crea contenedores hijos automáticamente en estos contextos:

  • Cada request HTTP en controladores REST: el controlador se resuelve en un contenedor hijo, por lo que servicios @scoped son únicos por request.
  • Cada conexión socket: el controlador socket se resuelve en un contenedor hijo por conexión.
  • Cada chat: los mindsets y sus módulos se resuelven en un contenedor hijo por chat, lo que permite inyectar Chat como dependencia con el contexto correcto.

Esto significa que puedes inyectar servicios como Chat, Auth o RequestContext y obtener automáticamente el contexto correcto sin pasarlo manualmente.

Ejemplo completo

import { singleton, injectable, scoped, Lifecycle, container } from '@wabot-dev/framework'
import { Pool } from 'pg'
// Repositorio singleton — una instancia para toda la app
@singleton()
export class OfferRepository {
constructor(private pool: Pool) {}
async findByChatId(chatId: string) {
// Consulta a la base de datos
}
}
// Servicio de cálculo — nueva instancia cada vez
@injectable()
export class PricingService {
calculate(basePrice: number, discount: number): number {
return basePrice * (1 - discount / 100)
}
}
// Servicio scoped — una instancia por request/chat
@scoped(Lifecycle.ContainerScoped)
export class SalesSession {
private prospectName?: string
setProspect(name: string) {
this.prospectName = name
}
getProspect(): string {
return this.prospectName ?? 'Desconocido'
}
}
// Módulo que usa todas las dependencias — se resuelven automáticamente
@injectable()
export class SalesModule {
constructor(
private offers: OfferRepository, // singleton
private pricing: PricingService, // nueva instancia
private session: SalesSession, // scoped al chat actual
) {}
async createQuote(productId: string, quantity: number) {
const price = this.pricing.calculate(1000, 10)
const prospect = this.session.getProspect()
return { prospect, price, quantity }
}
}

Siguiente paso

Configura la personalidad y comportamiento de tu bot: Mentalidad de tu Bot.