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 instanciaconst 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
@scopedson ú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
Chatcomo 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.