import { Module, type DynamicModule } from "@nestjs/common"; import { withTenant } from "../db/client.js"; import { LIVE_CHAT_RUNTIME, LiveChatRuntime, type LiveChatRuntimeOptions } from "./runtime.js"; import { ChatRealtimeBridge } from "./bridge.js"; import { LiveChatGuard } from "./live-chat.guard.js"; import { DeviceService } from "./devices.js"; import { LiveChatController } from "./live-chat.controller.js"; import { LiveChatHubService } from "./live-chat.service.js"; import { ChatRingBuffer } from "./ring-buffer.js"; import { RedisChatGovernors, type ChatGovernors } from "./redis-governors.js"; import { SendGovernors } from "./governors.js"; import type { ChatTarget } from "./types.js"; /** Central review imports this module with credential-bound transports, * capability targets and ingestion drivers via LiveChatModule.register(). */ function defaultTargets(): (workspaceId: string, channelId: string) => Promise { return async (workspaceId, channelId) => { const result = await withTenant(workspaceId, client => client.query<{ connection_id: string; provider: string; live_remote_id: string | null }>( `SELECT vc.id AS connection_id, vc.provider, vcs.remote_ref AS live_remote_id FROM virtual_channel_sources vcs JOIN connections vc ON vc.id = vcs.connection_id AND vc.workspace_id = vcs.workspace_id WHERE vcs.workspace_id = $1 AND vcs.virtual_channel_id = $2`, [workspaceId, channelId])); return result.rows.map(row => ({ connectionId: row.connection_id, provider: row.provider, liveRemoteId: row.live_remote_id ?? row.connection_id, read: false, reply: false, moderate: false, state: "degraded" as const, reason: "chat adapter not configured", })); }; } @Module({ controllers: [LiveChatController], providers: [ { provide: ChatRingBuffer, useValue: new ChatRingBuffer(500) }, { provide: SendGovernors, useFactory: () => new RedisChatGovernors({ buckets: { twitch: { capacity: 100, refillPerSecond: 100 / 30 } }, quotas: { youtube: { budget: 10000, costPerCall: 50 } } }) }, { provide: ChatRealtimeBridge, useFactory: () => new ChatRealtimeBridge() }, { provide: LiveChatHubService, useFactory: (buffer: ChatRingBuffer, governors: ChatGovernors, bridge: ChatRealtimeBridge, runtime?: LiveChatRuntimeOptions) => new LiveChatHubService({ buffer, governors, publish: bridge.publish, targets: defaultTargets(), ...runtime }), inject: [ChatRingBuffer, SendGovernors, ChatRealtimeBridge, { token: LIVE_CHAT_RUNTIME, optional: true }], }, DeviceService, LiveChatGuard, LiveChatRuntime, ], exports: [LiveChatHubService, DeviceService, ChatRealtimeBridge], }) export class LiveChatModule { static register(options: LiveChatRuntimeOptions): DynamicModule { return { module: LiveChatModule, providers: [{ provide: LIVE_CHAT_RUNTIME, useValue: options }] }; } }