@adatechnology/meta-whatsapp-module 0.2.0-rc.8 → 0.2.0-rc.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/createMetaWhatsAppModule.ts","../src/repositories/SessionRepository.ts","../src/schema/schema.ts","../src/repositories/MessageRepository.ts","../src/repositories/FlowGraphRepository.ts","../src/repositories/SettingsRepository.ts","../src/use-cases/LogMessage.use-case.ts","../src/use-cases/SendMessage.use-case.ts","../src/use-cases/TakeoverConversation.use-case.ts","../src/use-cases/ReleaseConversation.use-case.ts","../src/use-cases/ListConversations.use-case.ts","../src/use-cases/ListMessages.use-case.ts","../src/use-cases/ExportConversation.use-case.ts","../src/use-cases/FlowGraph.use-cases.ts","../src/flows/FlowInterpreter.ts","../src/channel/WhatsAppChannelAdapter.ts","../src/channel/ReceiveWebhook.use-case.ts","../src/channel/webhookSecurity.ts","../src/channel/IngestInboundMedia.use-case.ts","../src/runMigrations.ts","../src/realtime/SseHub.ts"],"sourcesContent":["export { createMetaWhatsAppModule } from './createMetaWhatsAppModule'\nexport type {\n MetaWhatsAppModule,\n CreateMetaWhatsAppModuleParams,\n MetaWhatsAppModuleConfig,\n MetaWhatsAppModuleFeatures,\n MetaWhatsAppModuleProviders,\n} from './createMetaWhatsAppModule'\n\nexport { metaWhatsAppSchema, sessions, messages, flowGraphs, settings } from './schema/schema'\nexport type {\n SessionRow,\n NewSessionRow,\n MessageRow,\n NewMessageRow,\n FlowGraphRow,\n NewFlowGraphRow,\n SettingsRow,\n NewSettingsRow,\n} from './schema/schema'\n\nexport { WhatsAppChannelAdapter } from './channel/WhatsAppChannelAdapter'\nexport { ReceiveWebhookUseCase } from './channel/ReceiveWebhook.use-case'\nexport type { ReceiveWebhookParams, ReceiveWebhookResult } from './channel/ReceiveWebhook.use-case'\nexport {\n verifyWebhookChallenge,\n verifyWebhookSignature,\n claimWebhookDelivery,\n WEBHOOK_NONCE_TTL_SECONDS,\n} from './channel/webhookSecurity'\nexport type { NonceStoreInterface } from './channel/webhookSecurity'\n\nexport { IngestInboundMediaUseCase, extractMediaDescriptor } from './channel/IngestInboundMedia.use-case'\nexport type { IngestInboundMediaParams, IngestInboundMediaResult } from './channel/IngestInboundMedia.use-case'\n\nexport { SettingsRepository } from './repositories/SettingsRepository'\nexport { SendMessageUseCase } from './use-cases/SendMessage.use-case'\nexport type { SendTextParams, SendMediaParams, SendTemplateParams } from './use-cases/SendMessage.use-case'\n\nexport {\n runMetaWhatsAppMigrations,\n metaWhatsAppMigrationsFolder,\n META_WHATSAPP_MIGRATIONS_TABLE,\n} from './runMigrations'\nexport type { RunMetaWhatsAppMigrationsParams } from './runMigrations'\nexport type { MetaWhatsAppDatabase, DrizzleMigrateFunction } from './database.types'\n\nexport { SessionRepository } from './repositories/SessionRepository'\nexport type { ListConversationsFilters } from './repositories/SessionRepository'\nexport { MessageRepository } from './repositories/MessageRepository'\nexport type {\n InsertMessageParams,\n ListMessagesParams as RepositoryListMessagesParams,\n} from './repositories/MessageRepository'\n\nexport { SseHub, issueSseTicket, redeemSseTicket } from './realtime/SseHub'\nexport type { SseListener, TicketStoreInterface, RealtimeRelay } from './realtime/SseHub'\n\nexport { FlowGraphRepository, OptimisticLockError, InvalidFlowGraphError } from './repositories/FlowGraphRepository'\nexport { FlowInterpreter } from './flows/FlowInterpreter'\nexport type { FlowStepInput, FlowStepResult, FlowRunResult } from './flows/FlowInterpreter'\n\nexport { TakeoverConversationUseCase } from './use-cases/TakeoverConversation.use-case'\nexport type { TakeoverConversationParams } from './use-cases/TakeoverConversation.use-case'\nexport { ReleaseConversationUseCase } from './use-cases/ReleaseConversation.use-case'\nexport type { ReleaseConversationParams } from './use-cases/ReleaseConversation.use-case'\nexport { LogMessageUseCase } from './use-cases/LogMessage.use-case'\nexport type { LogMessageParams } from './use-cases/LogMessage.use-case'\nexport { ListConversationsUseCase } from './use-cases/ListConversations.use-case'\nexport type { ListConversationsParams } from './use-cases/ListConversations.use-case'\nexport { ListMessagesUseCase } from './use-cases/ListMessages.use-case'\nexport type { ListMessagesParams } from './use-cases/ListMessages.use-case'\nexport { ExportConversationUseCase } from './use-cases/ExportConversation.use-case'\nexport type { ExportConversationParams, ExportConversationResult } from './use-cases/ExportConversation.use-case'\n\nexport {\n GetFlowGraphUseCase,\n ListFlowGraphsUseCase,\n CreateFlowGraphUseCase,\n SaveFlowGraphUseCase,\n DeleteFlowGraphUseCase,\n GetLiveFlowPositionsUseCase,\n} from './use-cases/FlowGraph.use-cases'\nexport type { CreateFlowGraphParams, SaveFlowGraphParams } from './use-cases/FlowGraph.use-cases'\n","import { WhatsAppMessageProvider } from '@adatechnology/meta-whatsapp-provider'\nimport type { MetaWhatsAppDatabase } from './database.types'\nimport type {\n CatalogPort,\n ChannelAdapterInterface,\n FlowActionHandler,\n FlowActionKind,\n MetaWhatsAppHooks,\n ObjectStorageInterface,\n RealtimeNotifierInterface,\n SessionState,\n SubjectResolverInterface,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport { SessionRepository } from './repositories/SessionRepository'\nimport { MessageRepository } from './repositories/MessageRepository'\nimport { FlowGraphRepository } from './repositories/FlowGraphRepository'\nimport { SettingsRepository } from './repositories/SettingsRepository'\nimport { LogMessageUseCase } from './use-cases/LogMessage.use-case'\nimport { SendMessageUseCase } from './use-cases/SendMessage.use-case'\nimport { TakeoverConversationUseCase } from './use-cases/TakeoverConversation.use-case'\nimport { ReleaseConversationUseCase } from './use-cases/ReleaseConversation.use-case'\nimport { ListConversationsUseCase } from './use-cases/ListConversations.use-case'\nimport { ListMessagesUseCase } from './use-cases/ListMessages.use-case'\nimport { ExportConversationUseCase } from './use-cases/ExportConversation.use-case'\nimport {\n GetFlowGraphUseCase,\n ListFlowGraphsUseCase,\n CreateFlowGraphUseCase,\n SaveFlowGraphUseCase,\n DeleteFlowGraphUseCase,\n GetLiveFlowPositionsUseCase,\n} from './use-cases/FlowGraph.use-cases'\nimport { FlowInterpreter } from './flows/FlowInterpreter'\nimport { WhatsAppChannelAdapter } from './channel/WhatsAppChannelAdapter'\nimport { ReceiveWebhookUseCase } from './channel/ReceiveWebhook.use-case'\nimport { IngestInboundMediaUseCase } from './channel/IngestInboundMedia.use-case'\nimport { verifyWebhookChallenge, type NonceStoreInterface } from './channel/webhookSecurity'\n\nexport interface MetaWhatsAppModuleConfig {\n phoneNumberId: string\n accessToken: string\n webhookVerifyToken: string\n appSecret: string\n wabaId?: string\n apiVersion?: string\n // Aponta para um mock local em dev/teste; undefined usa a Graph API real.\n baseUrl?: string\n}\n\nexport interface MetaWhatsAppModuleFeatures {\n // T4.4 — desliga o motor de grafo. Um produto cuja conversa é dirigida por código próprio\n // (o QuickCart tem o seu ConversationEngine) não deve nem carregar o interpretador, e o\n // módulo não pode presumir que todo consumidor quer fluxo visual.\n flowEngine?: boolean\n}\n\nexport interface MetaWhatsAppModuleProviders {\n objectStorage?: ObjectStorageInterface\n realtime?: RealtimeNotifierInterface\n subjectResolver?: SubjectResolverInterface\n // Opcional por desenho — sem catálogo injetado o módulo sobe e funciona, só os recursos de\n // produto ficam desligados (ver .specs/features/meta-catalog-trio/spec.md §4).\n catalog?: CatalogPort\n}\n\nexport interface CreateMetaWhatsAppModuleParams {\n db: MetaWhatsAppDatabase\n config: MetaWhatsAppModuleConfig\n // Cache compartilhado entre instâncias para o anti-replay do webhook.\n nonceStore: NonceStoreInterface\n // Estado inicial de uma sessão nova — o módulo não conhece a máquina de estados do produto.\n startState?: SessionState\n features?: MetaWhatsAppModuleFeatures\n providers?: MetaWhatsAppModuleProviders\n hooks?: MetaWhatsAppHooks\n}\n\n// T5.6 — costura conversa × canal. Tudo que é ambiente (db, credenciais, storage, cache,\n// realtime) entra por parâmetro: o módulo não lê process.env nem abre conexão própria, que é o\n// que permite ao host controlar pool, ciclo de vida e configuração por empresa.\nexport function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams) {\n const { db, config, nonceStore, providers = {}, hooks } = params\n const startState = params.startState ?? 'start'\n const flowEngineEnabled = params.features?.flowEngine ?? true\n\n const messageProvider = new WhatsAppMessageProvider({\n accessToken: config.accessToken,\n phoneNumberId: config.phoneNumberId,\n wabaId: config.wabaId,\n apiVersion: config.apiVersion,\n baseUrl: config.baseUrl,\n })\n const channel: ChannelAdapterInterface = new WhatsAppChannelAdapter(messageProvider)\n\n const sessionRepository = new SessionRepository(db)\n const messageRepository = new MessageRepository(db)\n const settingsRepository = new SettingsRepository(db)\n const flowGraphRepository = new FlowGraphRepository(db)\n\n const logMessage = new LogMessageUseCase(sessionRepository, messageRepository, providers.realtime)\n const sendMessage = new SendMessageUseCase(channel, sessionRepository, logMessage, providers.objectStorage)\n\n const receiveWebhook = new ReceiveWebhookUseCase({\n appSecret: config.appSecret,\n nonceStore,\n sessionRepository,\n messageRepository,\n logMessage,\n startState,\n hooks,\n realtime: providers.realtime,\n })\n\n // Só existe se a flag estiver ligada — não adianta o host \"não usar\" um interpretador que\n // ainda assim ficou instanciado e exposto na API pública do módulo.\n const flowInterpreter = flowEngineEnabled ? new FlowInterpreter() : undefined\n\n // Só existe com storage injetado — sem ele não há para onde copiar o binário, e devolver um\n // use-case que sempre falha seria pior do que a ausência ser visível no tipo.\n const ingestInboundMedia = providers.objectStorage\n ? new IngestInboundMediaUseCase(db, channel, providers.objectStorage)\n : undefined\n\n return {\n channel,\n // undefined quando providers.objectStorage não foi injetado.\n ingestInboundMedia,\n conversations: {\n log: logMessage,\n send: sendMessage,\n takeover: new TakeoverConversationUseCase(sessionRepository, providers.realtime),\n release: new ReleaseConversationUseCase(sessionRepository, providers.realtime),\n list: new ListConversationsUseCase(sessionRepository),\n listMessages: new ListMessagesUseCase(sessionRepository, messageRepository),\n export: new ExportConversationUseCase(sessionRepository),\n repository: sessionRepository,\n },\n settings: settingsRepository,\n webhook: {\n receive: receiveWebhook,\n // GET de verificação da Meta — o host liga na sua rota e devolve o retorno como texto puro.\n verifyChallenge: (query: { mode: string | null; token: string | null; challenge: string | null }) =>\n verifyWebhookChallenge({ ...query, expectedToken: config.webhookVerifyToken }),\n },\n // undefined quando features.flowEngine === false.\n flows: flowInterpreter\n ? {\n interpreter: flowInterpreter,\n registerFlowAction: (kind: FlowActionKind, handler: FlowActionHandler) =>\n flowInterpreter.registerFlowAction(kind, handler),\n get: new GetFlowGraphUseCase(flowGraphRepository),\n list: new ListFlowGraphsUseCase(flowGraphRepository),\n create: new CreateFlowGraphUseCase(flowGraphRepository),\n save: new SaveFlowGraphUseCase(flowGraphRepository),\n delete: new DeleteFlowGraphUseCase(flowGraphRepository),\n livePositions: new GetLiveFlowPositionsUseCase(flowGraphRepository),\n repository: flowGraphRepository,\n }\n : undefined,\n catalog: providers.catalog,\n }\n}\n\nexport type MetaWhatsAppModule = ReturnType<typeof createMetaWhatsAppModule>\n","import { and, desc, eq, sql } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport type {\n ConversationSummary,\n MessageDirection,\n SessionMode,\n SessionState,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport { messages, sessions, type SessionRow } from '../schema/schema'\n\nexport interface ListConversationsFilters {\n page?: number\n limit?: number\n waitingHuman?: boolean\n search?: string\n}\n\nconst DEFAULT_LIMIT = 20\n\n// Expressões correlacionadas da lista de conversas, extraídas para poderem ser renderizadas e\n// verificadas sem banco (ver SessionRepository.test.ts).\n//\n// A correlação usa `${sessions}.company_id` e NÃO `${sessions.companyId}`: dentro da lista de\n// seleção o drizzle renderiza a referência de coluna sem qualificar a tabela, e um\n// `\"company_id\"` solto dentro do subselect resolve para a coluna de `m`. O resultado seria\n// `m.company_id = m.company_id` (sempre verdadeiro) e `m.session_id = m.id` (nunca) — zero\n// linhas, NULL, e nenhum erro.\nexport const conversationSummaryProjection = {\n lastContent: sql<string | null>`(\n select m.content from ${messages} m\n where m.company_id = ${sessions}.company_id and m.session_id = ${sessions}.id\n order by m.created_at desc limit 1\n )`,\n lastDirection: sql<string | null>`(\n select m.direction from ${messages} m\n where m.company_id = ${sessions}.company_id and m.session_id = ${sessions}.id\n order by m.created_at desc limit 1\n )`,\n // Entradas do cliente depois da última leitura do atendente. Sessão nunca lida conta\n // tudo — é o comportamento esperado de uma conversa que ninguém abriu ainda.\n unread: sql<number>`(\n select count(*)::int from ${messages} m\n where m.company_id = ${sessions}.company_id\n and m.session_id = ${sessions}.id\n and m.direction = 'inbound'\n and (${sessions}.last_agent_read_at is null or m.created_at > ${sessions}.last_agent_read_at)\n )`,\n} as const\n\n// T3.2 — todo método recebe/filtra por companyId explicitamente (nunca lê de um campo do\n// payload do cliente); ver database.md \"Consistência e multiempresa\".\nexport class SessionRepository {\n constructor(private readonly db: MetaWhatsAppDatabase) {}\n\n async getContext(companyId: string, whatsappNumber: string): Promise<SessionRow | undefined> {\n const [row] = await this.db\n .select()\n .from(sessions)\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n .limit(1)\n return row\n }\n\n async getOrCreate(companyId: string, whatsappNumber: string, startState: SessionState): Promise<SessionRow> {\n const existing = await this.getContext(companyId, whatsappNumber)\n if (existing) return existing\n\n const [created] = await this.db\n .insert(sessions)\n .values({ companyId, whatsappNumber, currentState: startState })\n .onConflictDoUpdate({\n target: [sessions.companyId, sessions.whatsappNumber],\n set: { lastActivity: sql`now()` },\n })\n .returning()\n return created!\n }\n\n async setState(\n companyId: string,\n whatsappNumber: string,\n state: SessionState,\n context?: Record<string, unknown>,\n ): Promise<void> {\n await this.db\n .update(sessions)\n .set({\n currentState: state,\n ...(context !== undefined ? { context } : {}),\n lastActivity: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n // Posição no grafo de fluxo — chamado pelo host a cada transição do FlowInterpreter.\n // Passar null em ambos desliga o rastreio (ex.: conversa saiu do motor de fluxo).\n async setFlowPosition(\n companyId: string,\n whatsappNumber: string,\n flowKey: string | null,\n currentNodeId: string | null,\n ): Promise<void> {\n await this.db\n .update(sessions)\n .set({ flowKey, currentNodeId, lastActivity: sql`now()`, updatedAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n // Marca a chegada de uma mensagem DO CLIENTE. É este carimbo — e não lastActivity, que também\n // se move em ações do atendente/bot — que define a janela de 24h do WhatsApp: fora dela, só\n // template aprovado reabre a conversa (ver WindowExpiredError nos contracts).\n async touchInbound(companyId: string, whatsappNumber: string): Promise<void> {\n await this.db\n .update(sessions)\n .set({ lastInboundAt: sql`now()`, lastActivity: sql`now()`, updatedAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n // Horas desde a última mensagem do cliente — undefined se ele nunca escreveu. O canal (Fase 5)\n // usa isto para decidir entre mensagem livre e template antes de chamar a Graph API.\n async hoursSinceLastInbound(companyId: string, whatsappNumber: string): Promise<number | undefined> {\n const session = await this.getContext(companyId, whatsappNumber)\n if (!session?.lastInboundAt) return undefined\n return (Date.now() - session.lastInboundAt.getTime()) / (1000 * 60 * 60)\n }\n\n async setMode(\n companyId: string,\n whatsappNumber: string,\n mode: SessionMode,\n assignedUserId?: string | null,\n ): Promise<void> {\n await this.db\n .update(sessions)\n .set({ mode, assignedUserId: assignedUserId ?? null, updatedAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n // T3.3 — takeover: atendente assume a conversa, tirando-a do modo bot.\n async takeover(companyId: string, whatsappNumber: string, agentUserId: string): Promise<void> {\n await this.setMode(companyId, whatsappNumber, 'human', agentUserId)\n }\n\n // T3.3 — release: devolve a conversa ao bot.\n async release(companyId: string, whatsappNumber: string): Promise<void> {\n await this.setMode(companyId, whatsappNumber, 'bot', null)\n }\n\n async requestHuman(companyId: string, whatsappNumber: string): Promise<void> {\n await this.db\n .update(sessions)\n .set({ humanRequestedAt: sql`now()`, updatedAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n async markRead(companyId: string, whatsappNumber: string): Promise<void> {\n await this.db\n .update(sessions)\n .set({ lastAgentReadAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n async markAllRead(companyId: string, userId: string): Promise<number> {\n const result = await this.db\n .update(sessions)\n .set({ lastAgentReadAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.assignedUserId, userId)))\n return Number((result as unknown as { rowCount?: number }).rowCount ?? 0)\n }\n\n async listByContextFilters(companyId: string, filters: ListConversationsFilters): Promise<ConversationSummary[]> {\n const limit = filters.limit ?? DEFAULT_LIMIT\n const offset = ((filters.page ?? 1) - 1) * limit\n\n const conditions = [eq(sessions.companyId, companyId)]\n if (filters.waitingHuman) conditions.push(sql`${sessions.humanRequestedAt} is not null`)\n if (filters.search) conditions.push(sql`${sessions.whatsappNumber} ilike ${'%' + filters.search + '%'}`)\n\n const rows = await this.db\n .select({\n id: sessions.id,\n whatsappNumber: sessions.whatsappNumber,\n mode: sessions.mode,\n assignedUserId: sessions.assignedUserId,\n currentState: sessions.currentState,\n lastActivity: sessions.lastActivity,\n lastInboundAt: sessions.lastInboundAt,\n humanRequestedAt: sessions.humanRequestedAt,\n // Prévia e contagem saem de subquery correlacionada em vez de N+1 na volta: uma inbox\n // lista dezenas de conversas por página, e uma query por linha é o gargalo clássico\n // dessa tela. Ambos os campos são dados do próprio módulo — deixá-los para o host\n // obrigaria todo consumidor a reescrever o mesmo join contra tabelas que não são dele.\n ...conversationSummaryProjection,\n })\n .from(sessions)\n .where(and(...conditions))\n .orderBy(desc(sessions.lastActivity))\n .limit(limit)\n .offset(offset)\n\n return rows.map((row) => ({\n id: row.id,\n whatsappNumber: row.whatsappNumber,\n ...(row.lastContent !== null ? { lastContent: row.lastContent } : {}),\n ...(row.lastDirection !== null ? { lastDirection: row.lastDirection as MessageDirection } : {}),\n lastAt: row.lastActivity.toISOString(),\n lastInboundAt: row.lastInboundAt?.toISOString() ?? null,\n mode: row.mode as SessionMode,\n assignedUserId: row.assignedUserId,\n waitingHuman: row.humanRequestedAt !== null,\n unread: Number(row.unread),\n currentState: row.currentState,\n }))\n }\n\n // T3.3 — export: transcript completo de uma conversa, mensagens em ordem cronológica.\n async exportConversation(companyId: string, whatsappNumber: string) {\n const session = await this.getContext(companyId, whatsappNumber)\n if (!session) return null\n\n const rows = await this.db\n .select()\n .from(messages)\n .where(and(eq(messages.companyId, companyId), eq(messages.sessionId, session.id)))\n .orderBy(messages.createdAt)\n\n return { session, messages: rows }\n }\n}\n","import { sql } from 'drizzle-orm'\nimport {\n pgSchema,\n uuid,\n varchar,\n jsonb,\n timestamp,\n text,\n integer,\n boolean,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core'\n\n// Schema Postgres dedicado (T3.1/T3.5) — o módulo nunca escreve no schema `public` do host,\n// só ocupa este namespace próprio. O banco continua sendo um por produto; isto é apenas um\n// namespace dentro dele (ver rules/packages/pluggable-module.md §3).\nexport const metaWhatsAppSchema = pgSchema('meta_whatsapp')\n\nexport const sessions = metaWhatsAppSchema.table(\n 'sessions',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n // Multiempresa: toda entidade carrega companyId, vindo do contexto autenticado do host —\n // nunca de um campo livre do cliente (ver database.md \"Consistência e multiempresa\").\n companyId: uuid('company_id').notNull(),\n whatsappNumber: varchar('whatsapp_number', { length: 20 }).notNull(),\n // varchar (não enum): a máquina de estados de cada produto evolui de forma independente do módulo.\n currentState: varchar('current_state', { length: 64 }).notNull().default('start'),\n // Posição no grafo, quando o motor de fluxo está em uso — colunas separadas em vez de\n // convencionar \"flowKey:nodeId\" dentro de currentState: além de ser um contrato implícito que\n // nada garantia, a concatenação estourava o varchar(64) (flow key já é varchar(64) sozinha).\n flowKey: varchar('flow_key', { length: 64 }),\n currentNodeId: varchar('current_node_id', { length: 64 }),\n context: jsonb('context').$type<Record<string, unknown>>().notNull().default({}),\n mode: varchar('mode', { length: 12 }).notNull().default('bot'), // bot | human\n assignedUserId: uuid('assigned_user_id'),\n humanRequestedAt: timestamp('human_requested_at', { withTimezone: true }),\n lastInboundAt: timestamp('last_inbound_at', { withTimezone: true }),\n lastAgentReadAt: timestamp('last_agent_read_at', { withTimezone: true }),\n lastActivity: timestamp('last_activity', { withTimezone: true }).notNull().defaultNow(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n // Único por empresa (não globalmente) — dois hosts diferentes podem atender o mesmo número.\n uniqueIndex('idx_sessions_company_number').on(table.companyId, table.whatsappNumber),\n index('idx_sessions_company_mode').on(table.companyId, table.mode),\n // Alimenta getLiveFlowPositions (agregação por nó do grafo).\n index('idx_sessions_company_flow_node').on(table.companyId, table.flowKey, table.currentNodeId),\n ],\n)\n\nexport const messages = metaWhatsAppSchema.table(\n 'messages',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n companyId: uuid('company_id').notNull(),\n sessionId: uuid('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n whatsappNumber: varchar('whatsapp_number', { length: 20 }).notNull(),\n direction: varchar('direction', { length: 12 }).notNull(), // inbound | outbound\n sender: varchar('sender', { length: 12 }).notNull(), // customer | bot | agent\n agentUserId: uuid('agent_user_id'),\n // 32, não 16: os tipos da própria Meta são curtos ('text', 'interactive'), mas o host rotula\n type:\n // a saída com o subtipo que enviou ('interactive_buttons' já tem 19). Apertar aqui só\n // transfere para o consumidor a escolha entre truncar o rótulo e estourar o insert.\n varchar('type', { length: 32 }).notNull().default('text'),\n content: text('content'),\n // T5.4 — nunca base64 aqui; mídia vive no ObjectStorageInterface do host, referenciada por\n // uploadId dentro deste jsonb.\n payload: jsonb('payload').$type<Record<string, unknown>>(),\n waMessageId: varchar('wa_message_id', { length: 128 }),\n status: varchar('status', { length: 16 }),\n readAt: timestamp('read_at', { withTimezone: true }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index('idx_messages_session_created').on(table.sessionId, table.createdAt),\n index('idx_messages_company_number_created').on(table.companyId, table.whatsappNumber, table.createdAt),\n // Parcial e ÚNICO: é o que de fato garante idempotência de entrega. Um SELECT-antes-de-INSERT\n // não basta — a Meta reenvia a mesma entrega e várias instâncias do host processam em\n // paralelo, então as duas passariam pela checagem e inseririam duplicado. Parcial porque\n // mensagens outbound ainda sem waMessageId (envio em curso) são legitimamente NULL, e NULLs\n // não podem competir entre si por unicidade.\n uniqueIndex('idx_messages_company_wa_message_id')\n .on(table.companyId, table.waMessageId)\n .where(sql`${table.waMessageId} is not null`),\n ],\n)\n\n// T4.1 — um grafo por fluxo de conversa. `nodes` guarda o Record<string, FlowNodeData> inteiro\n// como jsonb (mesmo shape do editor visual em conversations-ui/flows) — não normalizado em\n// linhas, porque o grafo é sempre lido/escrito como uma unidade pelo editor e pelo interpretador.\nexport const flowGraphs = metaWhatsAppSchema.table(\n 'flow_graphs',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n companyId: uuid('company_id').notNull(),\n key: varchar('key', { length: 64 }).notNull(),\n label: varchar('label', { length: 120 }).notNull(),\n startNodeId: varchar('start_node_id', { length: 64 }).notNull(),\n nodes: jsonb('nodes').$type<Record<string, unknown>>().notNull().default({}),\n // Otimista: o editor manda a versão que carregou; salvar com versão desatualizada é\n // rejeitado pelo host (evita duas abas sobrescreverem uma a outra silenciosamente).\n version: integer('version').notNull().default(1),\n showInMenu: boolean('show_in_menu').notNull().default(false),\n menuOptionLabel: varchar('menu_option_label', { length: 64 }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [uniqueIndex('idx_flow_graphs_company_key').on(table.companyId, table.key)],\n)\n\n// T5.5 — configuração de WhatsApp POR EMPRESA, em tabela do módulo. Deliberadamente separada do\n// app_config genérico do host: são chaves que só o módulo entende, e mantê-las aqui é o que\n// permite instalar/remover a capacidade sem migrar a tabela de configuração do produto.\nexport const settings = metaWhatsAppSchema.table('settings', {\n companyId: uuid('company_id').primaryKey(),\n templateName: varchar('template_name', { length: 128 }),\n templateLanguage: varchar('template_language', { length: 16 }).notNull().default('pt_BR'),\n // Mapa posicional {{1}}, {{2}}... → token resolvido no envio (ex.: '{clientName}').\n templateVariables: jsonb('template_variables').$type<string[]>().notNull().default([]),\n welcomeMessage: text('welcome_message'),\n farewellMessage: text('farewell_message'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n})\n\nexport type SettingsRow = typeof settings.$inferSelect\nexport type NewSettingsRow = typeof settings.$inferInsert\n\nexport type SessionRow = typeof sessions.$inferSelect\nexport type NewSessionRow = typeof sessions.$inferInsert\nexport type MessageRow = typeof messages.$inferSelect\nexport type NewMessageRow = typeof messages.$inferInsert\nexport type FlowGraphRow = typeof flowGraphs.$inferSelect\nexport type NewFlowGraphRow = typeof flowGraphs.$inferInsert\n","import { and, eq, lt } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport type { MessageDirection, MessageSender, MessageStatus } from '@adatechnology/meta-whatsapp-contracts'\nimport { messages, type MessageRow, type NewMessageRow } from '../schema/schema'\n\nexport interface InsertMessageParams {\n companyId: string\n sessionId: string\n whatsappNumber: string\n direction: MessageDirection\n sender: MessageSender\n agentUserId?: string | null\n type: string\n content?: string | null\n payload?: Record<string, unknown> | null\n waMessageId?: string | null\n status?: MessageStatus | null\n}\n\nexport interface ListMessagesParams {\n companyId: string\n sessionId: string\n limit?: number\n before?: string\n}\n\nconst DEFAULT_LIMIT = 50\n\nexport class MessageRepository {\n constructor(private readonly db: MetaWhatsAppDatabase) {}\n\n // Idempotente por (companyId, waMessageId) — o mesmo webhook chega mais de uma vez (a Meta\n // reenvia em instabilidade) e várias instâncias do host processam em paralelo. A garantia é o\n // índice único parcial no banco + onConflictDoNothing, NÃO um SELECT prévio: duas entregas\n // concorrentes passariam as duas pela checagem e inseririam duplicado.\n // Devolve undefined quando a mensagem já existia (nada foi inserido).\n async insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined> {\n const values: NewMessageRow = {\n companyId: params.companyId,\n sessionId: params.sessionId,\n whatsappNumber: params.whatsappNumber,\n direction: params.direction,\n sender: params.sender,\n agentUserId: params.agentUserId ?? null,\n type: params.type,\n content: params.content ?? null,\n payload: params.payload ?? null,\n waMessageId: params.waMessageId ?? null,\n status: params.status ?? null,\n }\n\n const [created] = await this.db.insert(messages).values(values).onConflictDoNothing().returning()\n return created\n }\n\n async updateMessageStatus(\n companyId: string,\n waMessageId: string,\n status: MessageStatus,\n ): Promise<MessageRow | undefined> {\n const [updated] = await this.db\n .update(messages)\n .set({ status, ...(status === 'read' ? { readAt: new Date() } : {}) })\n .where(and(eq(messages.companyId, companyId), eq(messages.waMessageId, waMessageId)))\n .returning()\n return updated\n }\n\n async listByConversation(params: ListMessagesParams): Promise<MessageRow[]> {\n const conditions = [eq(messages.companyId, params.companyId), eq(messages.sessionId, params.sessionId)]\n if (params.before) conditions.push(lt(messages.createdAt, new Date(params.before)))\n\n return this.db\n .select()\n .from(messages)\n .where(and(...conditions))\n .orderBy(messages.createdAt)\n .limit(params.limit ?? DEFAULT_LIMIT)\n }\n}\n","import { and, eq, isNotNull, sql } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport { flowGraphNodesSchema } from '@adatechnology/meta-whatsapp-contracts'\nimport type {\n FlowGraphData,\n FlowGraphSummary,\n FlowNodeData,\n LiveFlowPosition,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport { sessions, flowGraphs, type FlowGraphRow } from '../schema/schema'\n\nexport class InvalidFlowGraphError extends Error {\n constructor(\n key: string,\n public readonly validationMessage: string,\n ) {\n super(`Grafo do fluxo \"${key}\" está malformado no banco: ${validationMessage}`)\n this.name = 'InvalidFlowGraphError'\n }\n}\n\n// O jsonb `nodes` entrou pelo editor (dado de origem cliente) e volta do banco como `unknown`.\n// Validar aqui, na fronteira, faz um grafo corrompido falhar com a causa explícita em vez de\n// virar comportamento estranho lá dentro do interpretador.\nfunction toContractGraph(row: FlowGraphRow): FlowGraphData {\n const parsed = flowGraphNodesSchema.safeParse(row.nodes)\n if (!parsed.success) throw new InvalidFlowGraphError(row.key, parsed.error.message)\n\n return {\n key: row.key,\n label: row.label,\n startNodeId: row.startNodeId,\n version: row.version,\n nodes: parsed.data as Record<string, FlowNodeData>,\n }\n}\n\nexport class OptimisticLockError extends Error {\n constructor(key: string) {\n super(`Fluxo \"${key}\" foi alterado por outra sessão — recarregue antes de salvar.`)\n this.name = 'OptimisticLockError'\n }\n}\n\n// T4.2 — CRUD de grafo de fluxo, mais a consulta de posições ao vivo (quantas sessões estão\n// em cada nó agora) que alimenta o liveCount no editor visual (FlowNodeCardData).\nexport class FlowGraphRepository {\n constructor(private readonly db: MetaWhatsAppDatabase) {}\n\n async get(companyId: string, key: string): Promise<FlowGraphData | undefined> {\n const [row] = await this.db\n .select()\n .from(flowGraphs)\n .where(and(eq(flowGraphs.companyId, companyId), eq(flowGraphs.key, key)))\n .limit(1)\n return row ? toContractGraph(row) : undefined\n }\n\n async list(companyId: string): Promise<FlowGraphSummary[]> {\n const rows = await this.db.select().from(flowGraphs).where(eq(flowGraphs.companyId, companyId))\n return rows.map((row) => ({\n key: row.key,\n label: row.label,\n nodeCount: Object.keys(row.nodes as Record<string, unknown>).length,\n showInMenu: row.showInMenu,\n menuOptionLabel: row.menuOptionLabel ?? undefined,\n updatedAt: row.updatedAt.toISOString(),\n }))\n }\n\n // Valida antes de gravar: aceitar um grafo malformado e só reclamar na leitura empurraria a\n // falha para o meio de uma conversa real, em vez de barrar quem salvou.\n private assertValidNodes(key: string, nodes: unknown): void {\n const parsed = flowGraphNodesSchema.safeParse(nodes)\n if (!parsed.success) throw new InvalidFlowGraphError(key, parsed.error.message)\n }\n\n async create(\n companyId: string,\n graph: Omit<FlowGraphData, 'version'> & { showInMenu?: boolean; menuOptionLabel?: string },\n ): Promise<FlowGraphData> {\n this.assertValidNodes(graph.key, graph.nodes)\n\n const [created] = await this.db\n .insert(flowGraphs)\n .values({\n companyId,\n key: graph.key,\n label: graph.label,\n startNodeId: graph.startNodeId,\n nodes: graph.nodes,\n showInMenu: graph.showInMenu ?? false,\n menuOptionLabel: graph.menuOptionLabel,\n })\n .returning()\n return toContractGraph(created!)\n }\n\n // Lock otimista: a escrita só aplica se `expectedVersion` ainda bater com o que está salvo —\n // senão, alguém mais salvou entretanto e o editor precisa recarregar (ver comentário no schema).\n async save(companyId: string, graph: FlowGraphData, expectedVersion: number): Promise<FlowGraphData> {\n this.assertValidNodes(graph.key, graph.nodes)\n\n const rows = await this.db\n .update(flowGraphs)\n .set({\n label: graph.label,\n startNodeId: graph.startNodeId,\n nodes: graph.nodes,\n version: expectedVersion + 1,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(flowGraphs.companyId, companyId),\n eq(flowGraphs.key, graph.key),\n eq(flowGraphs.version, expectedVersion),\n ),\n )\n .returning()\n\n if (rows.length === 0) throw new OptimisticLockError(graph.key)\n return toContractGraph(rows[0]!)\n }\n\n async delete(companyId: string, key: string): Promise<void> {\n await this.db.delete(flowGraphs).where(and(eq(flowGraphs.companyId, companyId), eq(flowGraphs.key, key)))\n }\n\n // T4.2 — GetLiveFlowPositions: agrega sessões ativas por (flowKey, currentNodeId), lendo as\n // colunas dedicadas gravadas por SessionRepository.setFlowPosition. Agrega no banco (GROUP BY,\n // coberto por idx_sessions_company_flow_node) em vez de trazer toda sessão da empresa para\n // contar em memória.\n async getLiveFlowPositions(companyId: string): Promise<LiveFlowPosition[]> {\n const rows = await this.db\n .select({\n flowKey: sessions.flowKey,\n nodeId: sessions.currentNodeId,\n count: sql<number>`count(*)::int`,\n })\n .from(sessions)\n .where(\n and(\n eq(sessions.companyId, companyId),\n eq(sessions.mode, 'bot'),\n isNotNull(sessions.flowKey),\n isNotNull(sessions.currentNodeId),\n ),\n )\n .groupBy(sessions.flowKey, sessions.currentNodeId)\n\n return rows.map((row) => ({ flowKey: row.flowKey!, nodeId: row.nodeId!, count: row.count }))\n }\n}\n","import { eq } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport type { WhatsAppSettings } from '@adatechnology/meta-whatsapp-contracts'\nimport { settings, type SettingsRow } from '../schema/schema'\n\nfunction toContractSettings(row: SettingsRow): WhatsAppSettings {\n return {\n templateName: row.templateName ?? '',\n templateLanguage: row.templateLanguage,\n templateVariables: row.templateVariables,\n welcomeMessage: row.welcomeMessage ?? '',\n farewellMessage: row.farewellMessage ?? '',\n }\n}\n\nconst EMPTY_SETTINGS: WhatsAppSettings = {\n templateName: '',\n templateLanguage: 'pt_BR',\n templateVariables: [],\n welcomeMessage: '',\n farewellMessage: '',\n}\n\n// T5.5 — configuração de WhatsApp por empresa.\nexport class SettingsRepository {\n constructor(private readonly db: MetaWhatsAppDatabase) {}\n\n // Empresa sem linha de configuração é o estado normal logo após instalar o módulo — devolve\n // os defaults em vez de undefined, para o chamador não precisar tratar \"ainda não configurado\"\n // como caso especial em todo lugar.\n async get(companyId: string): Promise<WhatsAppSettings> {\n const [row] = await this.db.select().from(settings).where(eq(settings.companyId, companyId)).limit(1)\n return row ? toContractSettings(row) : EMPTY_SETTINGS\n }\n\n async save(companyId: string, update: Partial<WhatsAppSettings>): Promise<WhatsAppSettings> {\n const current = await this.get(companyId)\n const merged = { ...current, ...update }\n\n const [row] = await this.db\n .insert(settings)\n .values({\n companyId,\n templateName: merged.templateName || null,\n templateLanguage: merged.templateLanguage,\n templateVariables: merged.templateVariables,\n welcomeMessage: merged.welcomeMessage || null,\n farewellMessage: merged.farewellMessage || null,\n })\n .onConflictDoUpdate({\n target: settings.companyId,\n set: {\n templateName: merged.templateName || null,\n templateLanguage: merged.templateLanguage,\n templateVariables: merged.templateVariables,\n welcomeMessage: merged.welcomeMessage || null,\n farewellMessage: merged.farewellMessage || null,\n updatedAt: new Date(),\n },\n })\n .returning()\n\n return toContractSettings(row!)\n }\n\n // Resolve {{1}}, {{2}}... a partir do mapa configurado + o contexto da conversa. Ex.: se\n // templateVariables = ['{clientName}', '{city}'], devolve [nome, cidade] na ordem posicional\n // que a Graph API espera nos bodyParameters.\n async resolveTemplateVariables(companyId: string, context: Record<string, unknown>): Promise<string[]> {\n const { templateVariables } = await this.get(companyId)\n return templateVariables.map((token) => {\n const key = token.replace(/^\\{|\\}$/g, '')\n const value = context[key]\n return value === undefined || value === null ? '' : String(value)\n })\n }\n}\n","import type { RealtimeNotifierInterface, SessionState } from '@adatechnology/meta-whatsapp-contracts'\nimport type { InsertMessageParams, MessageRepository } from '../repositories/MessageRepository'\nimport type { SessionRepository } from '../repositories/SessionRepository'\nimport type { MessageRow } from '../schema/schema'\n\nexport type LogMessageParams = Omit<InsertMessageParams, 'sessionId'> & {\n startState: SessionState\n}\n\n// T3.3 — ponto único de escrita no transcript: garante a sessão (getOrCreate), insere a\n// mensagem (idempotente por waMessageId) e notifica em tempo real. Usado tanto pelo canal\n// (T5.2, ao enviar) quanto pelo webhook (T5.1, ao receber).\nexport class LogMessageUseCase {\n constructor(\n private readonly sessionRepository: SessionRepository,\n private readonly messageRepository: MessageRepository,\n private readonly realtime?: RealtimeNotifierInterface,\n ) {}\n\n async execute(params: LogMessageParams): Promise<MessageRow | undefined> {\n const session = await this.sessionRepository.getOrCreate(params.companyId, params.whatsappNumber, params.startState)\n\n const saved = await this.messageRepository.insertMessage({ ...params, sessionId: session.id })\n // Entrega duplicada (mesmo waMessageId) — não emite evento nem re-carimba a janela.\n if (!saved) return undefined\n\n if (params.direction === 'inbound') {\n await this.sessionRepository.touchInbound(params.companyId, params.whatsappNumber)\n }\n\n this.realtime?.emit(`conv:${params.whatsappNumber}`, 'message', {\n direction: params.direction,\n sender: params.sender,\n })\n this.realtime?.emit('global', 'data-changed', {})\n\n return saved\n }\n}\n","import {\n WindowExpiredError,\n type ChannelAdapterInterface,\n type ObjectStorageInterface,\n type SessionState,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport type { SessionRepository } from '../repositories/SessionRepository'\nimport type { LogMessageUseCase } from './LogMessage.use-case'\nimport type { MessageRow } from '../schema/schema'\n\nconst WHATSAPP_WINDOW_HOURS = 24\n\nexport type SendTextParams = {\n companyId: string\n whatsappNumber: string\n body: string\n sender: 'bot' | 'agent'\n agentUserId?: string\n startState: SessionState\n}\n\nexport type SendMediaParams = {\n companyId: string\n whatsappNumber: string\n buffer: Buffer\n mimeType: string\n filename: string\n caption?: string\n sender: 'bot' | 'agent'\n agentUserId?: string\n startState: SessionState\n}\n\nexport type SendTemplateParams = {\n companyId: string\n whatsappNumber: string\n templateName: string\n languageCode?: string\n bodyParameters?: string[]\n sender: 'bot' | 'agent'\n agentUserId?: string\n startState: SessionState\n}\n\n// T5.2 — envio pelo canal + registro no transcript, num único ponto.\n//\n// A checagem de janela acontece ANTES da chamada à Graph API: a Meta também rejeitaria\n// (o adapter traduz o erro dela para WindowExpiredError), mas gastar uma ida à rede para\n// descobrir algo que o nosso próprio lastInboundAt já sabe é desperdício — e o erro local\n// carrega há quantas horas o cliente sumiu, que a resposta da Meta não informa.\nexport class SendMessageUseCase {\n constructor(\n private readonly channel: ChannelAdapterInterface,\n private readonly sessionRepository: SessionRepository,\n private readonly logMessage: LogMessageUseCase,\n private readonly objectStorage?: ObjectStorageInterface,\n ) {}\n\n private async assertWithinWindow(companyId: string, whatsappNumber: string): Promise<void> {\n const hours = await this.sessionRepository.hoursSinceLastInbound(companyId, whatsappNumber)\n // Cliente que nunca escreveu: não há janela aberta, só template pode iniciar a conversa.\n if (hours === undefined) throw new WindowExpiredError()\n if (hours >= WHATSAPP_WINDOW_HOURS) throw new WindowExpiredError(hours)\n }\n\n async sendText(params: SendTextParams): Promise<MessageRow | undefined> {\n await this.assertWithinWindow(params.companyId, params.whatsappNumber)\n\n const { externalMessageId } = await this.channel.sendText(params.whatsappNumber, params.body)\n\n return this.logMessage.execute({\n companyId: params.companyId,\n whatsappNumber: params.whatsappNumber,\n direction: 'outbound',\n sender: params.sender,\n agentUserId: params.agentUserId ?? null,\n type: 'text',\n content: params.body,\n waMessageId: externalMessageId,\n status: 'sent',\n startState: params.startState,\n })\n }\n\n async sendMedia(params: SendMediaParams): Promise<MessageRow | undefined> {\n await this.assertWithinWindow(params.companyId, params.whatsappNumber)\n\n const { externalMessageId } = await this.channel.sendMedia({\n to: params.whatsappNumber,\n buffer: params.buffer,\n mimeType: params.mimeType,\n filename: params.filename,\n caption: params.caption,\n })\n\n // T5.4 — o binário vai para o storage do host e o transcript guarda só a referência.\n // Gravar base64 no jsonb (dívida herdada do bot) inchava a tabela de mensagens, tornava\n // qualquer SELECT da conversa caro e duplicava um dado que o storage já tem.\n const uploadId = this.objectStorage\n ? (\n await this.objectStorage.upload({\n buffer: params.buffer,\n mimeType: params.mimeType,\n key: `meta-whatsapp/${params.companyId}/${Date.now()}-${params.filename}`,\n })\n ).uploadId\n : undefined\n\n return this.logMessage.execute({\n companyId: params.companyId,\n whatsappNumber: params.whatsappNumber,\n direction: 'outbound',\n sender: params.sender,\n agentUserId: params.agentUserId ?? null,\n type: mediaTypeFor(params.mimeType),\n content: params.caption ?? params.filename,\n payload: { filename: params.filename, mimeType: params.mimeType, ...(uploadId ? { uploadId } : {}) },\n waMessageId: externalMessageId,\n status: 'sent',\n startState: params.startState,\n })\n }\n\n // Template é o único envio que ignora a janela — é justamente o mecanismo que a Meta oferece\n // para reabri-la.\n async sendTemplate(params: SendTemplateParams): Promise<MessageRow | undefined> {\n const { externalMessageId } = await this.channel.sendTemplate({\n to: params.whatsappNumber,\n templateName: params.templateName,\n languageCode: params.languageCode ?? 'pt_BR',\n bodyParameters: params.bodyParameters,\n })\n\n return this.logMessage.execute({\n companyId: params.companyId,\n whatsappNumber: params.whatsappNumber,\n direction: 'outbound',\n sender: params.sender,\n agentUserId: params.agentUserId ?? null,\n type: 'template',\n content: params.templateName,\n payload: { templateName: params.templateName, bodyParameters: params.bodyParameters ?? [] },\n waMessageId: externalMessageId,\n status: 'sent',\n startState: params.startState,\n })\n }\n}\n\nfunction mediaTypeFor(mimeType: string): string {\n if (mimeType.startsWith('image/')) return 'image'\n if (mimeType.startsWith('audio/')) return 'audio'\n if (mimeType.startsWith('video/')) return 'video'\n return 'document'\n}\n","import type { SessionRepository } from '../repositories/SessionRepository'\nimport type { RealtimeNotifierInterface } from '@adatechnology/meta-whatsapp-contracts'\n\nexport type TakeoverConversationParams = {\n companyId: string\n whatsappNumber: string\n agentUserId: string\n}\n\n// T3.3 — atendente assume a conversa (tira do bot). Emite evento para os clientes de admin\n// atualizarem a UI (badge \"atendido por\", desabilitar botão de takeover) em tempo real.\nexport class TakeoverConversationUseCase {\n constructor(\n private readonly sessionRepository: SessionRepository,\n private readonly realtime?: RealtimeNotifierInterface,\n ) {}\n\n async execute(params: TakeoverConversationParams): Promise<void> {\n await this.sessionRepository.takeover(params.companyId, params.whatsappNumber, params.agentUserId)\n this.realtime?.emit(`conv:${params.whatsappNumber}`, 'mode-changed', {\n mode: 'human',\n assignedUserId: params.agentUserId,\n })\n this.realtime?.emit('global', 'data-changed', {})\n }\n}\n","import type { SessionRepository } from '../repositories/SessionRepository'\nimport type { RealtimeNotifierInterface } from '@adatechnology/meta-whatsapp-contracts'\n\nexport type ReleaseConversationParams = {\n companyId: string\n whatsappNumber: string\n}\n\n// T3.3 — devolve a conversa ao bot.\nexport class ReleaseConversationUseCase {\n constructor(\n private readonly sessionRepository: SessionRepository,\n private readonly realtime?: RealtimeNotifierInterface,\n ) {}\n\n async execute(params: ReleaseConversationParams): Promise<void> {\n await this.sessionRepository.release(params.companyId, params.whatsappNumber)\n this.realtime?.emit(`conv:${params.whatsappNumber}`, 'mode-changed', { mode: 'bot', assignedUserId: null })\n this.realtime?.emit('global', 'data-changed', {})\n }\n}\n","import type { ConversationSummary } from '@adatechnology/meta-whatsapp-contracts'\nimport type { ListConversationsFilters, SessionRepository } from '../repositories/SessionRepository'\n\nexport type ListConversationsParams = {\n companyId: string\n filters?: ListConversationsFilters\n}\n\n// T3.3 — listagem paginada/filtrada de conversas para a tela de inbox do host.\nexport class ListConversationsUseCase {\n constructor(private readonly sessionRepository: SessionRepository) {}\n\n async execute(params: ListConversationsParams): Promise<ConversationSummary[]> {\n return this.sessionRepository.listByContextFilters(params.companyId, params.filters ?? {})\n }\n}\n","import type { MessageRepository, ListMessagesParams as RepoListMessagesParams } from '../repositories/MessageRepository'\nimport type { SessionRepository } from '../repositories/SessionRepository'\nimport { SessionNotFoundError } from '@adatechnology/meta-whatsapp-contracts'\nimport type { MessageRow } from '../schema/schema'\n\nexport type ListMessagesParams = {\n companyId: string\n whatsappNumber: string\n limit?: number\n before?: string\n}\n\n// T3.3 — histórico paginado de uma conversa (resolve sessionId a partir do número antes de\n// delegar ao repositório de mensagens).\nexport class ListMessagesUseCase {\n constructor(\n private readonly sessionRepository: SessionRepository,\n private readonly messageRepository: MessageRepository,\n ) {}\n\n async execute(params: ListMessagesParams): Promise<MessageRow[]> {\n const session = await this.sessionRepository.getContext(params.companyId, params.whatsappNumber)\n if (!session) throw new SessionNotFoundError(params.whatsappNumber)\n\n const repoParams: RepoListMessagesParams = {\n companyId: params.companyId,\n sessionId: session.id,\n limit: params.limit,\n before: params.before,\n }\n return this.messageRepository.listByConversation(repoParams)\n }\n}\n","import type { SessionRepository } from '../repositories/SessionRepository'\nimport { SessionNotFoundError } from '@adatechnology/meta-whatsapp-contracts'\nimport type { MessageRow, SessionRow } from '../schema/schema'\n\nexport type ExportConversationParams = {\n companyId: string\n whatsappNumber: string\n}\n\nexport type ExportConversationResult = {\n session: SessionRow\n messages: MessageRow[]\n}\n\n// T3.3 — transcript completo para export (ex.: anexar num chamado de suporte, auditoria).\nexport class ExportConversationUseCase {\n constructor(private readonly sessionRepository: SessionRepository) {}\n\n async execute(params: ExportConversationParams): Promise<ExportConversationResult> {\n const result = await this.sessionRepository.exportConversation(params.companyId, params.whatsappNumber)\n if (!result) throw new SessionNotFoundError(params.whatsappNumber)\n return result\n }\n}\n","import type { FlowGraphData, FlowGraphSummary } from '@adatechnology/meta-whatsapp-contracts'\nimport type { FlowGraphRepository } from '../repositories/FlowGraphRepository'\n\n// T4.2 — CRUD de grafo, agrupado num único arquivo (cada use-case é uma classe fina sobre o\n// mesmo repositório; não há regra de negócio própria a proteger na sua separação em arquivos\n// individuais como fizemos para conversation/session).\n\nexport class GetFlowGraphUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: { companyId: string; key: string }): Promise<FlowGraphData | undefined> {\n return this.flowGraphRepository.get(params.companyId, params.key)\n }\n}\n\nexport class ListFlowGraphsUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: { companyId: string }): Promise<FlowGraphSummary[]> {\n return this.flowGraphRepository.list(params.companyId)\n }\n}\n\nexport type CreateFlowGraphParams = {\n companyId: string\n key: string\n label: string\n startNodeId: string\n nodes: FlowGraphData['nodes']\n showInMenu?: boolean\n menuOptionLabel?: string\n}\n\nexport class CreateFlowGraphUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: CreateFlowGraphParams): Promise<FlowGraphData> {\n return this.flowGraphRepository.create(params.companyId, params)\n }\n}\n\nexport type SaveFlowGraphParams = {\n companyId: string\n graph: FlowGraphData\n expectedVersion: number\n}\n\n// Salvar já é publicar — o motor de fluxo lê o grafo em tempo real (ver rules/packages/\n// pluggable-module.md e tasks.md T4.2). A validação de conteúdo (destinos quebrados, opções\n// duplicadas, etc.) é responsabilidade do editor (conversations-ui/flows validateGraph), não\n// deste use-case — aqui só garantimos a consistência otimista da escrita.\nexport class SaveFlowGraphUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: SaveFlowGraphParams): Promise<FlowGraphData> {\n return this.flowGraphRepository.save(params.companyId, params.graph, params.expectedVersion)\n }\n}\n\nexport class DeleteFlowGraphUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: { companyId: string; key: string }): Promise<void> {\n return this.flowGraphRepository.delete(params.companyId, params.key)\n }\n}\n\nexport class GetLiveFlowPositionsUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: { companyId: string }) {\n return this.flowGraphRepository.getLiveFlowPositions(params.companyId)\n }\n}\n","import type {\n ChannelAdapterInterface,\n ConversationSession,\n FlowActionHandler,\n FlowActionKind,\n FlowActionResult,\n FlowGraphData,\n FlowNodeData,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport { isCrossFlowTarget, crossFlowKey } from '@adatechnology/meta-whatsapp-contracts'\n\nexport interface FlowStepInput {\n graph: FlowGraphData\n currentNodeId: string\n userAnswer?: string\n context: Record<string, unknown>\n session: ConversationSession\n channel: ChannelAdapterInterface\n}\n\nexport type FlowStepResult =\n | { kind: 'awaiting-answer'; nodeId: string; context: Record<string, unknown> }\n | { kind: 'advanced'; nodeId: string; context: Record<string, unknown> }\n | { kind: 'cross-flow'; flowKey: string; context: Record<string, unknown> }\n | { kind: 'terminal'; context: Record<string, unknown> }\n\n// Resultado de run(): nunca 'advanced' (o laço já consumiu esses), mas pode ser\n// 'max-steps-exceeded' quando o grafo tem um ciclo de nós automáticos.\nexport type FlowRunResult = (\n | { kind: 'awaiting-answer'; nodeId: string; context: Record<string, unknown> }\n | { kind: 'cross-flow'; flowKey: string; context: Record<string, unknown> }\n | { kind: 'terminal'; context: Record<string, unknown> }\n | { kind: 'max-steps-exceeded'; context: Record<string, unknown> }\n) & {\n // Caminho percorrido, do nó inicial ao final — serve para depurar um ciclo (os nós repetidos\n // aparecem aqui) e para o host registrar a trilha da conversa.\n visited: string[]\n steps: number\n}\n\nconst DEFAULT_MAX_STEPS = 50\n\nfunction evaluateCondition(node: FlowNodeData, context: Record<string, unknown>): boolean {\n if (!node.conditionContextKey || !node.conditionOperator || !node.conditionValue) return false\n const actual = context[node.conditionContextKey]\n const expected = node.conditionValue\n\n switch (node.conditionOperator) {\n case '==':\n return String(actual) === expected\n case '!=':\n return String(actual) !== expected\n case 'contains':\n return typeof actual === 'string' && actual.includes(expected)\n case '>':\n case '>=':\n case '<':\n case '<=': {\n const actualNum = Number(actual)\n const expectedNum = Number(expected)\n if (Number.isNaN(actualNum) || Number.isNaN(expectedNum)) return false\n if (node.conditionOperator === '>') return actualNum > expectedNum\n if (node.conditionOperator === '>=') return actualNum >= expectedNum\n if (node.conditionOperator === '<') return actualNum < expectedNum\n return actualNum <= expectedNum\n }\n default:\n return false\n }\n}\n\nfunction resolveNext(node: FlowNodeData, answerId: string | undefined): string | undefined {\n if (!node.next) return undefined\n if (typeof node.next === 'string') return node.next\n if (answerId && node.next.byAnswer[answerId]) return node.next.byAnswer[answerId]\n return node.next.default\n}\n\n// T4.3 — interpreta um grafo de fluxo nó a nó. `registerFlowAction` é o único jeito de um\n// `actionKind` fazer algo: o interpretador nunca conhece 'trigger_simulation' ou qualquer outro\n// caso de negócio do host — ele só invoca o handler registrado (ver providers.ts,\n// FlowActionRegistry) e segue o `next` que o handler devolver, se houver.\nexport class FlowInterpreter {\n private readonly actionHandlers = new Map<FlowActionKind, FlowActionHandler>()\n\n registerFlowAction(kind: FlowActionKind, handler: FlowActionHandler): void {\n this.actionHandlers.set(kind, handler)\n }\n\n async step(input: FlowStepInput): Promise<FlowStepResult> {\n const node = input.graph.nodes[input.currentNodeId]\n if (!node) return { kind: 'terminal', context: input.context }\n\n if (node.type === 'condition') {\n const branch = evaluateCondition(node, input.context) ? 'true' : 'false'\n return this.moveTo(input, resolveNext(node, branch))\n }\n\n if (node.type === 'action') {\n const handler = node.actionKind ? this.actionHandlers.get(node.actionKind) : undefined\n if (!handler) return { kind: 'terminal', context: input.context }\n\n // Runtime de uma função `void` é sempre `undefined` — cast seguro para poder encadear\n // `?.` (TS não deixa acessar propriedade num tipo `void | {...}` mesmo com optional chaining).\n const result = (await handler({\n node,\n session: input.session,\n channel: input.channel,\n context: input.context,\n })) as FlowActionResult | undefined\n\n const contextAfterAction = result?.context ? { ...input.context, ...result.context } : input.context\n const nextNodeId = result?.next ?? resolveNext(node, undefined)\n return this.moveTo({ ...input, context: contextAfterAction }, nextNodeId)\n }\n\n const isChoice = node.type === 'menu' || node.questionType === 'choice'\n\n // Sem resposta do usuário ainda: este é o nó atual, aguardando input — não avança.\n if (input.userAnswer === undefined) {\n return { kind: 'awaiting-answer', nodeId: input.currentNodeId, context: input.context }\n }\n\n const updatedContext = node.contextKey ? { ...input.context, [node.contextKey]: input.userAnswer } : input.context\n const answerId = isChoice ? input.userAnswer : undefined\n const nextNodeId = resolveNext(node, answerId)\n\n return this.moveTo({ ...input, context: updatedContext }, nextNodeId)\n }\n\n private moveTo(input: FlowStepInput, nextNodeId: string | undefined): FlowStepResult {\n if (!nextNodeId) return { kind: 'terminal', context: input.context }\n if (isCrossFlowTarget(nextNodeId))\n return { kind: 'cross-flow', flowKey: crossFlowKey(nextNodeId), context: input.context }\n if (!input.graph.nodes[nextNodeId]) return { kind: 'terminal', context: input.context }\n return { kind: 'advanced', nodeId: nextNodeId, context: input.context }\n }\n\n // Avança pelo grafo até parar num ponto que exige algo de fora: uma pergunta esperando resposta\n // do cliente, o fim do fluxo, ou um salto para outro fluxo. Nós de condição e ação não pedem\n // input nenhum, então parar neles obrigaria o chamador a reimplementar este laço — e sem\n // proteção, um grafo com ciclo de condições (A→B→A) o prenderia para sempre.\n //\n // O corte por `maxSteps` é uma rede de segurança de runtime, não a defesa principal: um ciclo é\n // erro de autoria e deveria ser barrado na publicação. Como validateGraph (no editor) hoje\n // detecta nó inalcançável mas NÃO detecta ciclo, um grafo cíclico é publicável — então o\n // runtime precisa sobreviver a ele em vez de travar o processo que atende o webhook.\n async run(input: FlowStepInput, options?: { maxSteps?: number }): Promise<FlowRunResult> {\n const maxSteps = options?.maxSteps ?? DEFAULT_MAX_STEPS\n const visited: string[] = [input.currentNodeId]\n\n let current: FlowStepInput = input\n let steps = 0\n\n while (steps < maxSteps) {\n const result = await this.step(current)\n steps++\n\n if (result.kind !== 'advanced') return { ...result, visited, steps }\n\n visited.push(result.nodeId)\n // Só a primeira iteração consome a resposta do cliente; daí em diante o laço percorre\n // nós automáticos (condição/ação). Manter userAnswer faria a mesma resposta ser\n // reaplicada em cada pergunta seguinte, pulando-as sem o cliente ter respondido.\n current = { ...current, currentNodeId: result.nodeId, context: result.context, userAnswer: undefined }\n }\n\n return { kind: 'max-steps-exceeded', context: current.context, visited, steps }\n }\n}\n","import { WhatsAppWindowExpiredError as ProviderWindowExpiredError } from '@adatechnology/meta-graph-core'\nimport type { WhatsAppMessageProvider } from '@adatechnology/meta-whatsapp-provider'\nimport { WindowExpiredError, type ChannelAdapterInterface } from '@adatechnology/meta-whatsapp-contracts'\n\n// T5.2 — implementa a porta ChannelAdapterInterface sobre o meta-whatsapp-provider. A camada de\n// conversa fala só com esta porta e nunca com a Graph API, o que é o que permite trocar o canal\n// (ou usar um dublê em teste) sem tocar no motor de fluxo.\n//\n// Também traduz os erros do provider (que são da família MetaGraphError) para os erros de\n// domínio do módulo — o host trata WindowExpiredError sem precisar conhecer meta-graph-core.\nexport class WhatsAppChannelAdapter implements ChannelAdapterInterface {\n constructor(private readonly messages: WhatsAppMessageProvider) {}\n\n private async translateErrors<TResult>(operation: () => Promise<TResult>): Promise<TResult> {\n try {\n return await operation()\n } catch (error) {\n if (error instanceof ProviderWindowExpiredError) throw new WindowExpiredError()\n throw error\n }\n }\n\n async sendText(to: string, body: string): Promise<{ externalMessageId: string | null }> {\n const result = await this.translateErrors(() => this.messages.sendText(to, body))\n return { externalMessageId: result.waMessageId }\n }\n\n async sendMedia(params: {\n to: string\n buffer: Buffer\n mimeType: string\n filename: string\n caption?: string\n }): Promise<{ externalMessageId: string | null }> {\n const result = await this.translateErrors(() => this.messages.sendMedia(params))\n return { externalMessageId: result.waMessageId }\n }\n\n async sendTemplate(params: {\n to: string\n templateName: string\n languageCode: string\n bodyParameters?: string[]\n }): Promise<{ externalMessageId: string | null }> {\n const result = await this.translateErrors(() => this.messages.sendTemplate(params))\n return { externalMessageId: result.waMessageId }\n }\n\n async sendInteractiveList(params: {\n to: string\n body: string\n buttonLabel: string\n rows: { id: string; title: string }[]\n }): Promise<{ externalMessageId: string | null }> {\n const result = await this.translateErrors(() =>\n this.messages.sendInteractiveList({\n to: params.to,\n bodyText: params.body,\n buttonText: params.buttonLabel,\n sections: [{ title: params.buttonLabel, rows: params.rows }],\n }),\n )\n return { externalMessageId: result.waMessageId }\n }\n\n async fetchMediaAsBase64(mediaId: string): Promise<{ data: string; mimeType: string }> {\n return this.translateErrors(() => this.messages.fetchMediaAsBase64(mediaId))\n }\n}\n","import {\n whatsAppWebhookPayloadSchema,\n type MetaWhatsAppHooks,\n type SessionState,\n type WhatsAppMessage,\n type WhatsAppStatus,\n type MessageStatus,\n type ConversationSession,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport type { MessageRepository } from '../repositories/MessageRepository'\nimport type { SessionRepository } from '../repositories/SessionRepository'\nimport type { LogMessageUseCase } from '../use-cases/LogMessage.use-case'\nimport type { RealtimeNotifierInterface } from '@adatechnology/meta-whatsapp-contracts'\nimport type { SessionRow } from '../schema/schema'\nimport { verifyWebhookSignature, claimWebhookDelivery, type NonceStoreInterface } from './webhookSecurity'\n\nexport type ReceiveWebhookParams = {\n companyId: string\n rawBody: Buffer | string\n signatureHeader: string | null | undefined\n}\n\nexport type ReceiveWebhookResult = {\n // Sempre 200 para a Meta, mesmo em duplicata — ela desativa webhooks que respondem erro.\n duplicate: boolean\n messagesProcessed: number\n statusesProcessed: number\n}\n\nfunction toSessionContract(row: SessionRow): ConversationSession {\n return {\n id: row.id,\n companyId: row.companyId,\n whatsappNumber: row.whatsappNumber,\n currentState: row.currentState,\n flowKey: row.flowKey,\n currentNodeId: row.currentNodeId,\n context: row.context,\n mode: row.mode as ConversationSession['mode'],\n assignedUserId: row.assignedUserId,\n humanRequestedAt: row.humanRequestedAt?.toISOString() ?? null,\n lastInboundAt: row.lastInboundAt?.toISOString() ?? null,\n lastAgentReadAt: row.lastAgentReadAt?.toISOString() ?? null,\n lastActivity: row.lastActivity.toISOString(),\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n }\n}\n\n// Texto legível para o transcript. Uma mensagem interativa traz título E id; guardamos o título\n// (é o que o cliente viu), com o id como fallback quando a Meta não manda título.\nfunction extractContent(message: WhatsAppMessage): string | null {\n if (message.text?.body) return message.text.body\n const interactive = message.interactive\n if (interactive?.button_reply) return interactive.button_reply.title || interactive.button_reply.id\n if (interactive?.list_reply) return interactive.list_reply.title || interactive.list_reply.id\n if (message.order) {\n const items = message.order.product_items.reduce((sum, item) => sum + item.quantity, 0)\n return `Pedido: ${items} item(ns)`\n }\n return message.image?.caption ?? message.document?.caption ?? null\n}\n\n// A resposta que o motor de fluxo deve consumir: para interativo é o ID da opção (estável), não\n// o título (que muda quando alguém edita o texto do botão no editor).\nfunction extractAnswer(message: WhatsAppMessage): string | undefined {\n const interactive = message.interactive\n if (interactive?.button_reply) return interactive.button_reply.id\n if (interactive?.list_reply) return interactive.list_reply.id\n return message.text?.body\n}\n\nfunction extractPayload(message: WhatsAppMessage): Record<string, unknown> | null {\n const payload: Record<string, unknown> = {}\n if (message.interactive) payload['interactive'] = message.interactive\n if (message.order) payload['order'] = message.order\n if (message.image) payload['image'] = message.image\n if (message.audio) payload['audio'] = message.audio\n if (message.video) payload['video'] = message.video\n if (message.document) payload['document'] = message.document\n if (message.sticker) payload['sticker'] = message.sticker\n if (message.context?.referred_product) payload['referredProduct'] = message.context.referred_product\n return Object.keys(payload).length > 0 ? payload : null\n}\n\n// T5.1 — porta de entrada do canal. Ordem deliberada: assinatura → anti-replay → parse → efeito.\n// Verificar a assinatura antes de qualquer parse evita gastar CPU (e superfície do zod) com\n// corpo não autenticado.\nexport class ReceiveWebhookUseCase {\n constructor(\n private readonly params: {\n appSecret: string\n nonceStore: NonceStoreInterface\n sessionRepository: SessionRepository\n messageRepository: MessageRepository\n logMessage: LogMessageUseCase\n startState: SessionState\n hooks?: MetaWhatsAppHooks\n realtime?: RealtimeNotifierInterface\n },\n ) {}\n\n async execute(input: ReceiveWebhookParams): Promise<ReceiveWebhookResult> {\n verifyWebhookSignature({\n rawBody: input.rawBody,\n signatureHeader: input.signatureHeader,\n appSecret: this.params.appSecret,\n })\n\n const claimed = await claimWebhookDelivery({\n nonceStore: this.params.nonceStore,\n signatureHeader: input.signatureHeader!,\n })\n if (!claimed) return { duplicate: true, messagesProcessed: 0, statusesProcessed: 0 }\n\n const rawText = typeof input.rawBody === 'string' ? input.rawBody : input.rawBody.toString('utf8')\n const payload = whatsAppWebhookPayloadSchema.parse(JSON.parse(rawText))\n\n let messagesProcessed = 0\n let statusesProcessed = 0\n\n for (const entry of payload.entry) {\n for (const change of entry.changes) {\n for (const message of change.value.messages ?? []) {\n await this.handleMessage(input.companyId, message)\n messagesProcessed++\n }\n for (const status of change.value.statuses ?? []) {\n await this.handleStatus(input.companyId, status)\n statusesProcessed++\n }\n }\n }\n\n return { duplicate: false, messagesProcessed, statusesProcessed }\n }\n\n private async handleMessage(companyId: string, message: WhatsAppMessage): Promise<void> {\n const saved = await this.params.logMessage.execute({\n companyId,\n whatsappNumber: message.from,\n direction: 'inbound',\n sender: 'customer',\n type: message.type,\n content: extractContent(message),\n payload: extractPayload(message),\n waMessageId: message.id,\n status: 'received',\n startState: this.params.startState,\n })\n // Mensagem repetida (mesmo waMessageId já gravado): não dispara o hook de novo, senão a\n // regra de negócio do host rodaria duas vezes para a mesma mensagem do cliente.\n if (!saved) return\n\n const sessionRow = await this.params.sessionRepository.getContext(companyId, message.from)\n if (!sessionRow) return\n\n // Conversa em atendimento humano não é processada pelo bot — o atendente responde.\n if (sessionRow.mode === 'human') return\n\n const outcome = await this.params.hooks?.onMessageReceived?.(message, toSessionContract(sessionRow))\n // 'handled' = o host já respondeu e assumiu a mensagem; o módulo não segue com o fluxo.\n if (outcome?.outcome === 'handled') return\n\n // 'continue' (ou sem hook): quem dirige o motor de fluxo é o host via runFlowStep — o\n // webhook só entrega o sinal. Expor a resposta extraída evita que cada host reimplemente\n // a lógica de \"qual é a resposta do cliente\" para interativo vs texto.\n void extractAnswer(message)\n }\n\n private async handleStatus(companyId: string, status: WhatsAppStatus): Promise<void> {\n const updated = await this.params.messageRepository.updateMessageStatus(\n companyId,\n status.id,\n status.status as MessageStatus,\n )\n if (!updated) return\n\n this.params.realtime?.emit(`conv:${updated.whatsappNumber}`, 'message-status', {\n waMessageId: status.id,\n status: status.status,\n })\n\n const sessionRow = await this.params.sessionRepository.getContext(companyId, updated.whatsappNumber)\n await this.params.hooks?.onStatusUpdate?.(status, sessionRow ? toSessionContract(sessionRow) : null)\n }\n}\n\nexport { extractAnswer, extractContent }\n","import { createHmac, timingSafeEqual } from 'node:crypto'\nimport { InvalidWebhookSignatureError } from '@adatechnology/meta-whatsapp-contracts'\n\n// Porta mínima de armazenamento com TTL para o nonce anti-replay. Qualquer cache do host serve\n// (Redis, Memcached); precisa ser COMPARTILHADO entre instâncias — um cache em memória por\n// processo deixaria a mesma entrega passar uma vez em cada instância.\nexport interface NonceStoreInterface {\n // Deve ser atômico (SET NX): devolve true só se a chave ainda não existia. Um get-then-set\n // não serve — duas entregas simultâneas leriam \"ausente\" e ambas seguiriam.\n setIfAbsent(key: string, ttlSeconds: number): Promise<boolean>\n}\n\nexport const WEBHOOK_NONCE_TTL_SECONDS = 300\n\n// Comparação em tempo constante entre dois textos. `timingSafeEqual` exige buffers do mesmo\n// tamanho, e o próprio comprimento vaza informação — por isso comparamos digests de tamanho\n// fixo em vez dos valores crus.\nfunction safeEqualStrings(left: string, right: string): boolean {\n const leftDigest = createHmac('sha256', 'constant-time-compare').update(left).digest()\n const rightDigest = createHmac('sha256', 'constant-time-compare').update(right).digest()\n return timingSafeEqual(leftDigest, rightDigest)\n}\n\n// GET /webhook da Meta: ela envia hub.mode/hub.verify_token/hub.challenge e espera o challenge\n// de volta em texto puro. Comparação em tempo constante — o verify token é um segredo, e\n// comparar com === permitiria descobri-lo caractere a caractere pelo tempo de resposta.\nexport function verifyWebhookChallenge(params: {\n mode: string | null\n token: string | null\n challenge: string | null\n expectedToken: string\n}): string {\n if (params.mode !== 'subscribe' || !params.token || !params.challenge) {\n throw new InvalidWebhookSignatureError()\n }\n if (!safeEqualStrings(params.token, params.expectedToken)) {\n throw new InvalidWebhookSignatureError()\n }\n return params.challenge\n}\n\n// POST /webhook: a Meta assina o corpo CRU com o app secret e manda em X-Hub-Signature-256.\n// Precisa ser o buffer exato recebido — reserializar o JSON muda espaçamento/ordem e invalida\n// a assinatura.\nexport function verifyWebhookSignature(params: {\n rawBody: Buffer | string\n signatureHeader: string | null | undefined\n appSecret: string\n}): void {\n const { rawBody, signatureHeader, appSecret } = params\n if (!signatureHeader?.startsWith('sha256=')) throw new InvalidWebhookSignatureError()\n\n const expected = createHmac('sha256', appSecret).update(rawBody).digest('hex')\n const received = signatureHeader.slice('sha256='.length)\n\n // Comprimentos diferentes já reprovam, mas passamos pelo digest de tamanho fixo para não\n // transformar isso num canal de tempo.\n if (!safeEqualStrings(received, expected)) throw new InvalidWebhookSignatureError()\n}\n\n// Anti-replay: a mesma entrega assinada, reenviada por um atacante que a capturou, seria aceita\n// de novo — a assinatura continua válida. O nonce marca a entrega como já processada por uma\n// janela curta. A Meta legitimamente reenvia em caso de timeout, então isto também absorve o\n// retry dela sem duplicar efeito.\n//\n// Devolve false quando a entrega já foi vista (o chamador deve responder 200 e ignorar — a Meta\n// desativa webhooks que respondem erro).\nexport async function claimWebhookDelivery(params: {\n nonceStore: NonceStoreInterface\n signatureHeader: string\n ttlSeconds?: number\n}): Promise<boolean> {\n // A assinatura é derivada do corpo, então é única por entrega — não precisamos de um nonce\n // separado, e usá-la evita depender de um campo que a Meta não garante.\n const key = `meta-whatsapp:webhook:${params.signatureHeader}`\n return params.nonceStore.setIfAbsent(key, params.ttlSeconds ?? WEBHOOK_NONCE_TTL_SECONDS)\n}\n","import { eq, and } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport type { ChannelAdapterInterface, ObjectStorageInterface } from '@adatechnology/meta-whatsapp-contracts'\nimport { messages, type MessageRow } from '../schema/schema'\n\nexport type IngestInboundMediaParams = {\n companyId: string\n messageId: string\n // ID da mídia na Meta — a URL de download expira, então o binário precisa ser copiado para o\n // storage do host antes de sumir.\n sourceMediaId: string\n mimeType: string\n filename?: string\n}\n\nexport type IngestInboundMediaResult = {\n uploadId: string\n // true quando a mídia já estava no storage e nada foi baixado de novo.\n alreadyIngested: boolean\n}\n\n// T5.3 — copia mídia recebida da Meta para o storage do host.\n//\n// Não reimplementa S3: delega ao ObjectStorageInterface que o host injeta (object-storage-provider\n// ou o que ele usar). O módulo só orquestra \"baixa da Meta → entrega ao storage → referencia na\n// mensagem\".\n//\n// Idempotente por sourceMediaId gravado no payload da mensagem: o job pode ser reentregue (fila\n// com retry, webhook reprocessado) e baixar de novo custaria banda e geraria um segundo objeto\n// órfão no storage para o mesmo binário.\nexport class IngestInboundMediaUseCase {\n constructor(\n private readonly db: MetaWhatsAppDatabase,\n private readonly channel: ChannelAdapterInterface,\n private readonly objectStorage: ObjectStorageInterface,\n ) {}\n\n async execute(params: IngestInboundMediaParams): Promise<IngestInboundMediaResult> {\n const [message] = await this.db\n .select()\n .from(messages)\n .where(and(eq(messages.companyId, params.companyId), eq(messages.id, params.messageId)))\n .limit(1)\n\n if (!message) throw new Error(`Mensagem ${params.messageId} não encontrada para ingestão de mídia`)\n\n const payload = (message.payload ?? {}) as Record<string, unknown>\n\n // Já ingerida: devolve o uploadId existente sem tocar na rede.\n if (payload['uploadId'] && payload['sourceMediaId'] === params.sourceMediaId) {\n return { uploadId: String(payload['uploadId']), alreadyIngested: true }\n }\n\n const { data, mimeType } = await this.channel.fetchMediaAsBase64(params.sourceMediaId)\n const buffer = Buffer.from(data, 'base64')\n\n const { uploadId } = await this.objectStorage.upload({\n buffer,\n mimeType: mimeType || params.mimeType,\n key: `meta-whatsapp/${params.companyId}/inbound/${params.sourceMediaId}`,\n })\n\n // Guarda sourceMediaId junto do uploadId: é o que torna a próxima execução idempotente.\n const updatedPayload: Record<string, unknown> = {\n ...payload,\n uploadId,\n sourceMediaId: params.sourceMediaId,\n mimeType: mimeType || params.mimeType,\n ...(params.filename ? { filename: params.filename } : {}),\n }\n\n await this.db\n .update(messages)\n .set({ payload: updatedPayload })\n .where(and(eq(messages.companyId, params.companyId), eq(messages.id, params.messageId)))\n\n return { uploadId, alreadyIngested: false }\n }\n}\n\n// Extrai o descritor de mídia de uma mensagem já persistida — o webhook guarda o objeto cru da\n// Meta (image/audio/video/document/sticker) no payload, e a ingestão precisa saber qual é.\nexport function extractMediaDescriptor(\n message: MessageRow,\n): { sourceMediaId: string; mimeType: string; filename?: string } | undefined {\n const payload = (message.payload ?? {}) as Record<string, unknown>\n for (const key of ['image', 'audio', 'video', 'document', 'sticker'] as const) {\n const media = payload[key] as { id?: string; mime_type?: string; filename?: string } | undefined\n if (media?.id) {\n return {\n sourceMediaId: media.id,\n mimeType: media.mime_type ?? 'application/octet-stream',\n filename: media.filename,\n }\n }\n }\n return undefined\n}\n","import { join } from 'node:path'\nimport type { MetaWhatsAppDatabase } from './database.types'\n\n// Migrations append-only com journal próprio — nunca colide com as migrations do host, que roda\n// as delas por um caminho totalmente separado (ver rules/packages/pluggable-module.md §3.2).\n//\n// O journal vive em `drizzle.meta_whatsapp_migrations` (schema `drizzle`, o padrão do\n// drizzle-kit; o nome DA TABELA é que isola o módulo — o host usa `__drizzle_migrations` ali do\n// lado, sem conflito). Deliberadamente NÃO fica dentro de `meta_whatsapp`: a primeira migration\n// do módulo é justamente quem cria esse schema (CREATE SCHEMA), então apontar o journal para lá\n// gera uma corrida entre o migrator garantindo o schema da tabela de controle e a migration\n// tentando criá-lo de novo — o erro \"schema meta_whatsapp already exists\".\n//\n// Para resetar um banco de teste é preciso derrubar os dois: o schema meta_whatsapp E o journal\n// em drizzle.meta_whatsapp_migrations. Só dropar o schema faz o migrator pular tudo na próxima\n// execução, deixando o banco sem as tabelas e sem erro aparente.\nexport const META_WHATSAPP_MIGRATIONS_TABLE = 'meta_whatsapp_migrations'\n\n// Pasta das migrations embarcadas, para o host que prefere chamar o próprio migrator.\nexport function metaWhatsAppMigrationsFolder(): string {\n return join(__dirname, 'migrations')\n}\n\nexport type RunMetaWhatsAppMigrationsParams = {\n db: MetaWhatsAppDatabase\n // O `migrate` do driver do host — `drizzle-orm/node-postgres/migrator`,\n // `drizzle-orm/bun-sql/migrator`, etc. O módulo não escolhe driver pelo host.\n migrate: (db: never, config: { migrationsFolder: string; migrationsTable?: string }) => Promise<void>\n}\n\nexport async function runMetaWhatsAppMigrations(params: RunMetaWhatsAppMigrationsParams): Promise<void> {\n await params.migrate(params.db as never, {\n migrationsFolder: metaWhatsAppMigrationsFolder(),\n migrationsTable: META_WHATSAPP_MIGRATIONS_TABLE,\n })\n}\n","import { randomUUID } from 'node:crypto'\nimport type { RealtimeNotifierInterface } from '@adatechnology/meta-whatsapp-contracts'\n\nexport type SseListener = (event: string, payload: Record<string, unknown>) => void\n\n// Porta mínima de cache para o ticket de conexão SSE — qualquer TTL cache do host serve\n// (Redis, in-memory); o módulo não assume Redis especificamente.\nexport interface TicketStoreInterface {\n set(key: string, value: string, ttlSeconds: number): Promise<void>\n get(key: string): Promise<string | null>\n delete(key: string): Promise<void>\n}\n\n// Publica localmente e, se um relay for injetado (ex.: Redis pub/sub), propaga entre processos\n// — várias instâncias do host atrás de um load balancer recebem o mesmo evento (T3.4).\nexport interface RealtimeRelay {\n publish(channel: string, message: string): Promise<void>\n subscribe(channel: string, onMessage: (message: string) => void): Promise<() => void>\n}\n\nconst TICKET_TTL_SECONDS = 60\n\n// T3.4 — hub de tempo real atrás da porta RealtimeNotifierInterface (contracts). O host pode\n// trocar por WebSocket ou desligar sem o resto do módulo saber — ver providers.ts.\ntype RelayEnvelope = {\n originId: string\n event: string\n payload: Record<string, unknown>\n}\n\nexport class SseHub implements RealtimeNotifierInterface {\n private readonly localListeners = new Map<string, Set<SseListener>>()\n // Uma assinatura de relay por CANAL, compartilhada por todos os listeners locais dele — uma\n // por listener faria o relay entregar N cópias num canal com N conexões abertas.\n private readonly relaySubscriptions = new Map<string, Promise<() => void>>()\n // Identifica esta instância do processo para descartar o próprio eco (pub/sub entrega a\n // mensagem a todos os inscritos, inclusive quem publicou).\n private readonly instanceId = randomUUID()\n\n constructor(private readonly relay?: RealtimeRelay) {}\n\n emit(channel: string, event: string, payload: Record<string, unknown>): void {\n this.dispatchLocal(channel, event, payload)\n if (this.relay) {\n const envelope: RelayEnvelope = { originId: this.instanceId, event, payload }\n this.relay.publish(channel, JSON.stringify(envelope)).catch(() => undefined)\n }\n }\n\n private dispatchLocal(channel: string, event: string, payload: Record<string, unknown>): void {\n const listeners = this.localListeners.get(channel)\n if (!listeners) return\n for (const listener of listeners) listener(event, payload)\n }\n\n async subscribe(channel: string, listener: SseListener): Promise<() => void> {\n if (!this.localListeners.has(channel)) this.localListeners.set(channel, new Set())\n this.localListeners.get(channel)!.add(listener)\n\n if (this.relay && !this.relaySubscriptions.has(channel)) {\n this.relaySubscriptions.set(\n channel,\n this.relay.subscribe(channel, (message) => {\n try {\n const envelope = JSON.parse(message) as RelayEnvelope\n // Eco do que nós mesmos publicamos: dispatchLocal já entregou em emit().\n if (envelope.originId === this.instanceId) return\n this.dispatchLocal(channel, envelope.event, envelope.payload)\n } catch {\n // mensagem de relay malformada — ignora, não derruba a conexão\n }\n }),\n )\n }\n\n return () => {\n const listeners = this.localListeners.get(channel)\n listeners?.delete(listener)\n // Último listener do canal saiu: solta a assinatura do relay e a chave do Map, senão\n // canais efêmeros (conv:<numero>) se acumulam pelo uptime inteiro do processo.\n if (listeners && listeners.size === 0) {\n this.localListeners.delete(channel)\n const pendingUnsubscribe = this.relaySubscriptions.get(channel)\n this.relaySubscriptions.delete(channel)\n pendingUnsubscribe?.then((unsubscribe) => unsubscribe()).catch(() => undefined)\n }\n }\n }\n}\n\n// EventSource do browser não manda headers customizados — o ticket vai na query string da URL\n// de conexão e expira em 60s, curto o bastante para não valer a pena capturar e reusar depois.\nexport async function issueSseTicket(\n store: TicketStoreInterface,\n companyId: string,\n whatsappNumber: string,\n): Promise<string> {\n const ticket = randomUUID()\n await store.set(`sse:ticket:${ticket}`, JSON.stringify({ companyId, whatsappNumber }), TICKET_TTL_SECONDS)\n return ticket\n}\n\nexport async function redeemSseTicket(\n store: TicketStoreInterface,\n ticket: string,\n): Promise<{ companyId: string; whatsappNumber: string } | null> {\n const raw = await store.get(`sse:ticket:${ticket}`)\n if (!raw) return null\n await store.delete(`sse:ticket:${ticket}`)\n return JSON.parse(raw) as { companyId: string; whatsappNumber: string }\n}\n"],"mappings":"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","names":["import_drizzle_orm","metaWhatsAppSchema","pgSchema","sessions","table","id","uuid","primaryKey","defaultRandom","companyId","notNull","whatsappNumber","varchar","length","currentState","default","flowKey","currentNodeId","context","jsonb","$type","mode","assignedUserId","humanRequestedAt","timestamp","withTimezone","lastInboundAt","lastAgentReadAt","lastActivity","defaultNow","createdAt","updatedAt","uniqueIndex","on","index","messages","sessionId","references","onDelete","direction","sender","agentUserId","type","content","text","payload","waMessageId","status","readAt","where","sql","flowGraphs","key","label","startNodeId","nodes","version","integer","showInMenu","boolean","menuOptionLabel","settings","templateName","templateLanguage","templateVariables","welcomeMessage","farewellMessage","DEFAULT_LIMIT","conversationSummaryProjection","lastContent","sql","messages","sessions","lastDirection","unread","SessionRepository","db","getContext","companyId","whatsappNumber","row","select","from","where","and","eq","limit","getOrCreate","startState","existing","created","insert","values","currentState","onConflictDoUpdate","target","set","lastActivity","returning","setState","state","context","update","undefined","updatedAt","setFlowPosition","flowKey","currentNodeId","touchInbound","lastInboundAt","hoursSinceLastInbound","session","Date","now","getTime","setMode","mode","assignedUserId","takeover","agentUserId","release","requestHuman","humanRequestedAt","markRead","lastAgentReadAt","markAllRead","userId","result","Number","rowCount","listByContextFilters","filters","offset","page","conditions","waitingHuman","push","search","rows","id","orderBy","desc","map","lastAt","toISOString","exportConversation","sessionId","createdAt","import_drizzle_orm","DEFAULT_LIMIT","MessageRepository","db","insertMessage","params","values","companyId","sessionId","whatsappNumber","direction","sender","agentUserId","type","content","payload","waMessageId","status","created","insert","messages","onConflictDoNothing","returning","updateMessageStatus","updated","update","set","readAt","Date","where","and","eq","listByConversation","conditions","before","push","lt","createdAt","select","from","orderBy","limit","import_drizzle_orm","InvalidFlowGraphError","Error","key","validationMessage","name","toContractGraph","row","parsed","flowGraphNodesSchema","safeParse","nodes","success","error","message","label","startNodeId","version","data","OptimisticLockError","FlowGraphRepository","db","get","companyId","select","from","flowGraphs","where","and","eq","limit","undefined","list","rows","map","nodeCount","Object","keys","length","showInMenu","menuOptionLabel","updatedAt","toISOString","assertValidNodes","create","graph","created","insert","values","returning","save","expectedVersion","update","set","Date","delete","getLiveFlowPositions","flowKey","sessions","nodeId","currentNodeId","count","sql","mode","isNotNull","groupBy","import_drizzle_orm","toContractSettings","row","templateName","templateLanguage","templateVariables","welcomeMessage","farewellMessage","EMPTY_SETTINGS","SettingsRepository","db","get","companyId","select","from","settings","where","eq","limit","save","update","current","merged","insert","values","onConflictDoUpdate","target","set","updatedAt","Date","returning","resolveTemplateVariables","context","map","token","key","replace","value","undefined","String","LogMessageUseCase","sessionRepository","messageRepository","realtime","execute","params","session","getOrCreate","companyId","whatsappNumber","startState","saved","insertMessage","sessionId","id","undefined","direction","touchInbound","emit","sender","import_meta_whatsapp_contracts","WHATSAPP_WINDOW_HOURS","SendMessageUseCase","channel","sessionRepository","logMessage","objectStorage","assertWithinWindow","companyId","whatsappNumber","hours","hoursSinceLastInbound","undefined","WindowExpiredError","sendText","params","externalMessageId","body","execute","direction","sender","agentUserId","type","content","waMessageId","status","startState","sendMedia","to","buffer","mimeType","filename","caption","uploadId","upload","key","Date","now","mediaTypeFor","payload","sendTemplate","templateName","languageCode","bodyParameters","startsWith","TakeoverConversationUseCase","sessionRepository","realtime","execute","params","takeover","companyId","whatsappNumber","agentUserId","emit","mode","assignedUserId","ReleaseConversationUseCase","sessionRepository","realtime","execute","params","release","companyId","whatsappNumber","emit","mode","assignedUserId","ListConversationsUseCase","sessionRepository","execute","params","listByContextFilters","companyId","filters","import_meta_whatsapp_contracts","ListMessagesUseCase","sessionRepository","messageRepository","execute","params","session","getContext","companyId","whatsappNumber","SessionNotFoundError","repoParams","sessionId","id","limit","before","listByConversation","import_meta_whatsapp_contracts","ExportConversationUseCase","sessionRepository","execute","params","result","exportConversation","companyId","whatsappNumber","SessionNotFoundError","GetFlowGraphUseCase","flowGraphRepository","execute","params","get","companyId","key","ListFlowGraphsUseCase","list","CreateFlowGraphUseCase","create","SaveFlowGraphUseCase","save","graph","expectedVersion","DeleteFlowGraphUseCase","delete","GetLiveFlowPositionsUseCase","getLiveFlowPositions","import_meta_whatsapp_contracts","DEFAULT_MAX_STEPS","evaluateCondition","node","context","conditionContextKey","conditionOperator","conditionValue","actual","expected","String","includes","actualNum","Number","expectedNum","isNaN","resolveNext","answerId","next","undefined","byAnswer","default","FlowInterpreter","actionHandlers","Map","registerFlowAction","kind","handler","set","step","input","graph","nodes","currentNodeId","type","branch","moveTo","actionKind","get","result","session","channel","contextAfterAction","nextNodeId","isChoice","questionType","userAnswer","nodeId","updatedContext","contextKey","isCrossFlowTarget","flowKey","crossFlowKey","run","options","maxSteps","visited","current","steps","push","import_meta_whatsapp_contracts","WhatsAppChannelAdapter","messages","translateErrors","operation","error","ProviderWindowExpiredError","WindowExpiredError","sendText","to","body","result","externalMessageId","waMessageId","sendMedia","params","sendTemplate","sendInteractiveList","bodyText","buttonText","buttonLabel","sections","title","rows","fetchMediaAsBase64","mediaId","import_meta_whatsapp_contracts","import_meta_whatsapp_contracts","WEBHOOK_NONCE_TTL_SECONDS","safeEqualStrings","left","right","leftDigest","createHmac","update","digest","rightDigest","timingSafeEqual","verifyWebhookChallenge","params","mode","token","challenge","InvalidWebhookSignatureError","expectedToken","verifyWebhookSignature","rawBody","signatureHeader","appSecret","startsWith","expected","received","slice","length","claimWebhookDelivery","key","nonceStore","setIfAbsent","ttlSeconds","toSessionContract","row","id","companyId","whatsappNumber","currentState","flowKey","currentNodeId","context","mode","assignedUserId","humanRequestedAt","toISOString","lastInboundAt","lastAgentReadAt","lastActivity","createdAt","updatedAt","extractContent","message","text","body","interactive","button_reply","title","list_reply","order","items","product_items","reduce","sum","item","quantity","image","caption","document","extractAnswer","extractPayload","payload","audio","video","sticker","referred_product","Object","keys","length","ReceiveWebhookUseCase","params","execute","input","verifyWebhookSignature","rawBody","signatureHeader","appSecret","claimed","claimWebhookDelivery","nonceStore","duplicate","messagesProcessed","statusesProcessed","rawText","toString","whatsAppWebhookPayloadSchema","parse","JSON","entry","change","changes","value","messages","handleMessage","status","statuses","handleStatus","saved","logMessage","from","direction","sender","type","content","waMessageId","startState","sessionRow","sessionRepository","getContext","outcome","hooks","onMessageReceived","updated","messageRepository","updateMessageStatus","realtime","emit","onStatusUpdate","import_drizzle_orm","IngestInboundMediaUseCase","db","channel","objectStorage","execute","params","message","select","from","messages","where","and","eq","companyId","id","messageId","limit","Error","payload","sourceMediaId","uploadId","String","alreadyIngested","data","mimeType","fetchMediaAsBase64","buffer","Buffer","upload","key","updatedPayload","filename","update","set","extractMediaDescriptor","media","mime_type","undefined","createMetaWhatsAppModule","params","db","config","nonceStore","providers","hooks","startState","flowEngineEnabled","features","flowEngine","messageProvider","WhatsAppMessageProvider","accessToken","phoneNumberId","wabaId","apiVersion","baseUrl","channel","WhatsAppChannelAdapter","sessionRepository","SessionRepository","messageRepository","MessageRepository","settingsRepository","SettingsRepository","flowGraphRepository","FlowGraphRepository","logMessage","LogMessageUseCase","realtime","sendMessage","SendMessageUseCase","objectStorage","receiveWebhook","ReceiveWebhookUseCase","appSecret","flowInterpreter","FlowInterpreter","undefined","ingestInboundMedia","IngestInboundMediaUseCase","conversations","log","send","takeover","TakeoverConversationUseCase","release","ReleaseConversationUseCase","list","ListConversationsUseCase","listMessages","ListMessagesUseCase","export","ExportConversationUseCase","repository","settings","webhook","receive","verifyChallenge","query","verifyWebhookChallenge","expectedToken","webhookVerifyToken","flows","interpreter","registerFlowAction","kind","handler","get","GetFlowGraphUseCase","ListFlowGraphsUseCase","create","CreateFlowGraphUseCase","save","SaveFlowGraphUseCase","delete","DeleteFlowGraphUseCase","livePositions","GetLiveFlowPositionsUseCase","catalog","META_WHATSAPP_MIGRATIONS_TABLE","metaWhatsAppMigrationsFolder","join","__dirname","runMetaWhatsAppMigrations","params","migrate","db","migrationsFolder","migrationsTable","import_node_crypto","TICKET_TTL_SECONDS","SseHub","localListeners","Map","relaySubscriptions","instanceId","randomUUID","relay","emit","channel","event","payload","dispatchLocal","envelope","originId","publish","JSON","stringify","catch","undefined","listeners","get","listener","subscribe","has","set","Set","add","message","parse","delete","size","pendingUnsubscribe","then","unsubscribe","issueSseTicket","store","companyId","whatsappNumber","ticket","redeemSseTicket","raw"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/createMetaWhatsAppModule.ts","../src/repositories/SessionRepository.ts","../src/schema/schema.ts","../src/repositories/MessageRepository.ts","../src/repositories/FlowGraphRepository.ts","../src/repositories/SettingsRepository.ts","../src/use-cases/LogMessage.use-case.ts","../src/use-cases/SendMessage.use-case.ts","../src/use-cases/TakeoverConversation.use-case.ts","../src/use-cases/ReleaseConversation.use-case.ts","../src/use-cases/ListConversations.use-case.ts","../src/use-cases/ListMessages.use-case.ts","../src/use-cases/ExportConversation.use-case.ts","../src/use-cases/FlowGraph.use-cases.ts","../src/flows/FlowInterpreter.ts","../src/channel/WhatsAppChannelAdapter.ts","../src/channel/ReceiveWebhook.use-case.ts","../src/channel/webhookSecurity.ts","../src/channel/IngestInboundMedia.use-case.ts","../src/runMigrations.ts","../src/realtime/SseHub.ts"],"sourcesContent":["export { createMetaWhatsAppModule } from './createMetaWhatsAppModule'\nexport type {\n MetaWhatsAppModule,\n CreateMetaWhatsAppModuleParams,\n MetaWhatsAppModuleConfig,\n MetaWhatsAppModuleFeatures,\n MetaWhatsAppModuleProviders,\n} from './createMetaWhatsAppModule'\n\nexport { metaWhatsAppSchema, sessions, messages, flowGraphs, settings } from './schema/schema'\nexport type {\n SessionRow,\n NewSessionRow,\n MessageRow,\n NewMessageRow,\n FlowGraphRow,\n NewFlowGraphRow,\n SettingsRow,\n NewSettingsRow,\n} from './schema/schema'\n\nexport { WhatsAppChannelAdapter } from './channel/WhatsAppChannelAdapter'\nexport { ReceiveWebhookUseCase } from './channel/ReceiveWebhook.use-case'\nexport type { ReceiveWebhookParams, ReceiveWebhookResult } from './channel/ReceiveWebhook.use-case'\nexport {\n verifyWebhookChallenge,\n verifyWebhookSignature,\n claimWebhookDelivery,\n WEBHOOK_NONCE_TTL_SECONDS,\n} from './channel/webhookSecurity'\nexport type { NonceStoreInterface } from './channel/webhookSecurity'\n\nexport { IngestInboundMediaUseCase, extractMediaDescriptor } from './channel/IngestInboundMedia.use-case'\nexport type { IngestInboundMediaParams, IngestInboundMediaResult } from './channel/IngestInboundMedia.use-case'\n\nexport { SettingsRepository } from './repositories/SettingsRepository'\nexport { SendMessageUseCase } from './use-cases/SendMessage.use-case'\nexport type { SendTextParams, SendMediaParams, SendTemplateParams } from './use-cases/SendMessage.use-case'\n\nexport {\n runMetaWhatsAppMigrations,\n metaWhatsAppMigrationsFolder,\n META_WHATSAPP_MIGRATIONS_TABLE,\n} from './runMigrations'\nexport type { RunMetaWhatsAppMigrationsParams } from './runMigrations'\nexport type { MetaWhatsAppDatabase, DrizzleMigrateFunction } from './database.types'\n\nexport { SessionRepository } from './repositories/SessionRepository'\nexport type { ListConversationsFilters } from './repositories/SessionRepository'\nexport { MessageRepository } from './repositories/MessageRepository'\nexport type {\n InsertMessageParams,\n ListMessagesParams as RepositoryListMessagesParams,\n} from './repositories/MessageRepository'\n\nexport { SseHub, issueSseTicket, redeemSseTicket } from './realtime/SseHub'\nexport type { SseListener, TicketStoreInterface, RealtimeRelay } from './realtime/SseHub'\n\nexport { FlowGraphRepository, OptimisticLockError, InvalidFlowGraphError } from './repositories/FlowGraphRepository'\nexport { FlowInterpreter } from './flows/FlowInterpreter'\nexport type { FlowStepInput, FlowStepResult, FlowRunResult } from './flows/FlowInterpreter'\n\nexport { TakeoverConversationUseCase } from './use-cases/TakeoverConversation.use-case'\nexport type { TakeoverConversationParams } from './use-cases/TakeoverConversation.use-case'\nexport { ReleaseConversationUseCase } from './use-cases/ReleaseConversation.use-case'\nexport type { ReleaseConversationParams } from './use-cases/ReleaseConversation.use-case'\nexport { LogMessageUseCase } from './use-cases/LogMessage.use-case'\nexport type { LogMessageParams, MessageModerator } from './use-cases/LogMessage.use-case'\nexport { ListConversationsUseCase } from './use-cases/ListConversations.use-case'\nexport type { ListConversationsParams } from './use-cases/ListConversations.use-case'\nexport { ListMessagesUseCase } from './use-cases/ListMessages.use-case'\nexport type { ListMessagesParams } from './use-cases/ListMessages.use-case'\nexport { ExportConversationUseCase } from './use-cases/ExportConversation.use-case'\nexport type { ExportConversationParams, ExportConversationResult } from './use-cases/ExportConversation.use-case'\n\nexport {\n GetFlowGraphUseCase,\n ListFlowGraphsUseCase,\n CreateFlowGraphUseCase,\n SaveFlowGraphUseCase,\n DeleteFlowGraphUseCase,\n GetLiveFlowPositionsUseCase,\n} from './use-cases/FlowGraph.use-cases'\nexport type { CreateFlowGraphParams, SaveFlowGraphParams } from './use-cases/FlowGraph.use-cases'\n","import { WhatsAppMessageProvider } from '@adatechnology/meta-whatsapp-provider'\nimport type { MetaWhatsAppDatabase } from './database.types'\nimport type {\n CatalogPort,\n ChannelAdapterInterface,\n FlowActionHandler,\n FlowActionKind,\n MetaWhatsAppHooks,\n ObjectStorageInterface,\n RealtimeNotifierInterface,\n SessionState,\n SubjectResolverInterface,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport { SessionRepository } from './repositories/SessionRepository'\nimport { MessageRepository } from './repositories/MessageRepository'\nimport { FlowGraphRepository } from './repositories/FlowGraphRepository'\nimport { SettingsRepository } from './repositories/SettingsRepository'\nimport { LogMessageUseCase, type MessageModerator } from './use-cases/LogMessage.use-case'\nimport { SendMessageUseCase } from './use-cases/SendMessage.use-case'\nimport { TakeoverConversationUseCase } from './use-cases/TakeoverConversation.use-case'\nimport { ReleaseConversationUseCase } from './use-cases/ReleaseConversation.use-case'\nimport { ListConversationsUseCase } from './use-cases/ListConversations.use-case'\nimport { ListMessagesUseCase } from './use-cases/ListMessages.use-case'\nimport { ExportConversationUseCase } from './use-cases/ExportConversation.use-case'\nimport {\n GetFlowGraphUseCase,\n ListFlowGraphsUseCase,\n CreateFlowGraphUseCase,\n SaveFlowGraphUseCase,\n DeleteFlowGraphUseCase,\n GetLiveFlowPositionsUseCase,\n} from './use-cases/FlowGraph.use-cases'\nimport { FlowInterpreter } from './flows/FlowInterpreter'\nimport { WhatsAppChannelAdapter } from './channel/WhatsAppChannelAdapter'\nimport { ReceiveWebhookUseCase } from './channel/ReceiveWebhook.use-case'\nimport { IngestInboundMediaUseCase } from './channel/IngestInboundMedia.use-case'\nimport { verifyWebhookChallenge, type NonceStoreInterface } from './channel/webhookSecurity'\n\nexport interface MetaWhatsAppModuleConfig {\n phoneNumberId: string\n accessToken: string\n webhookVerifyToken: string\n appSecret: string\n wabaId?: string\n apiVersion?: string\n // Aponta para um mock local em dev/teste; undefined usa a Graph API real.\n baseUrl?: string\n}\n\nexport interface MetaWhatsAppModuleFeatures {\n // T4.4 — desliga o motor de grafo. Um produto cuja conversa é dirigida por código próprio\n // (o QuickCart tem o seu ConversationEngine) não deve nem carregar o interpretador, e o\n // módulo não pode presumir que todo consumidor quer fluxo visual.\n flowEngine?: boolean\n}\n\nexport interface MetaWhatsAppModuleProviders {\n objectStorage?: ObjectStorageInterface\n realtime?: RealtimeNotifierInterface\n subjectResolver?: SubjectResolverInterface\n // Opcional por desenho — sem catálogo injetado o módulo sobe e funciona, só os recursos de\n // produto ficam desligados (ver .specs/features/meta-catalog-trio/spec.md §4).\n catalog?: CatalogPort\n // Marca mensagem ofensiva do cliente no transcript. Injetado, e não construído aqui, pelo mesmo\n // motivo dos outros providers: a lista de termos e o liga/desliga são de cada produto, e o módulo\n // não lê process.env. Ausente, as colunas ficam nulas — \"não avaliado\".\n moderator?: MessageModerator\n}\n\nexport interface CreateMetaWhatsAppModuleParams {\n db: MetaWhatsAppDatabase\n config: MetaWhatsAppModuleConfig\n // Cache compartilhado entre instâncias para o anti-replay do webhook.\n nonceStore: NonceStoreInterface\n // Estado inicial de uma sessão nova — o módulo não conhece a máquina de estados do produto.\n startState?: SessionState\n features?: MetaWhatsAppModuleFeatures\n providers?: MetaWhatsAppModuleProviders\n hooks?: MetaWhatsAppHooks\n}\n\n// T5.6 — costura conversa × canal. Tudo que é ambiente (db, credenciais, storage, cache,\n// realtime) entra por parâmetro: o módulo não lê process.env nem abre conexão própria, que é o\n// que permite ao host controlar pool, ciclo de vida e configuração por empresa.\nexport function createMetaWhatsAppModule(params: CreateMetaWhatsAppModuleParams) {\n const { db, config, nonceStore, providers = {}, hooks } = params\n const startState = params.startState ?? 'start'\n const flowEngineEnabled = params.features?.flowEngine ?? true\n\n const messageProvider = new WhatsAppMessageProvider({\n accessToken: config.accessToken,\n phoneNumberId: config.phoneNumberId,\n wabaId: config.wabaId,\n apiVersion: config.apiVersion,\n baseUrl: config.baseUrl,\n })\n const channel: ChannelAdapterInterface = new WhatsAppChannelAdapter(messageProvider)\n\n const sessionRepository = new SessionRepository(db)\n const messageRepository = new MessageRepository(db)\n const settingsRepository = new SettingsRepository(db)\n const flowGraphRepository = new FlowGraphRepository(db)\n\n const logMessage = new LogMessageUseCase(\n sessionRepository,\n messageRepository,\n providers.realtime,\n providers.moderator,\n )\n const sendMessage = new SendMessageUseCase(channel, sessionRepository, logMessage, providers.objectStorage)\n\n const receiveWebhook = new ReceiveWebhookUseCase({\n appSecret: config.appSecret,\n nonceStore,\n sessionRepository,\n messageRepository,\n logMessage,\n startState,\n hooks,\n realtime: providers.realtime,\n })\n\n // Só existe se a flag estiver ligada — não adianta o host \"não usar\" um interpretador que\n // ainda assim ficou instanciado e exposto na API pública do módulo.\n const flowInterpreter = flowEngineEnabled ? new FlowInterpreter() : undefined\n\n // Só existe com storage injetado — sem ele não há para onde copiar o binário, e devolver um\n // use-case que sempre falha seria pior do que a ausência ser visível no tipo.\n const ingestInboundMedia = providers.objectStorage\n ? new IngestInboundMediaUseCase(db, channel, providers.objectStorage)\n : undefined\n\n return {\n channel,\n // undefined quando providers.objectStorage não foi injetado.\n ingestInboundMedia,\n conversations: {\n log: logMessage,\n send: sendMessage,\n takeover: new TakeoverConversationUseCase(sessionRepository, providers.realtime),\n release: new ReleaseConversationUseCase(sessionRepository, providers.realtime),\n list: new ListConversationsUseCase(sessionRepository),\n listMessages: new ListMessagesUseCase(sessionRepository, messageRepository),\n export: new ExportConversationUseCase(sessionRepository),\n repository: sessionRepository,\n },\n settings: settingsRepository,\n webhook: {\n receive: receiveWebhook,\n // GET de verificação da Meta — o host liga na sua rota e devolve o retorno como texto puro.\n verifyChallenge: (query: { mode: string | null; token: string | null; challenge: string | null }) =>\n verifyWebhookChallenge({ ...query, expectedToken: config.webhookVerifyToken }),\n },\n // undefined quando features.flowEngine === false.\n flows: flowInterpreter\n ? {\n interpreter: flowInterpreter,\n registerFlowAction: (kind: FlowActionKind, handler: FlowActionHandler) =>\n flowInterpreter.registerFlowAction(kind, handler),\n get: new GetFlowGraphUseCase(flowGraphRepository),\n list: new ListFlowGraphsUseCase(flowGraphRepository),\n create: new CreateFlowGraphUseCase(flowGraphRepository),\n save: new SaveFlowGraphUseCase(flowGraphRepository),\n delete: new DeleteFlowGraphUseCase(flowGraphRepository),\n livePositions: new GetLiveFlowPositionsUseCase(flowGraphRepository),\n repository: flowGraphRepository,\n }\n : undefined,\n catalog: providers.catalog,\n }\n}\n\nexport type MetaWhatsAppModule = ReturnType<typeof createMetaWhatsAppModule>\n","import { and, desc, eq, sql } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport type {\n ConversationSummary,\n MessageDirection,\n SessionMode,\n SessionState,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport { messages, sessions, type SessionRow } from '../schema/schema'\n\nexport interface ListConversationsFilters {\n page?: number\n limit?: number\n waitingHuman?: boolean\n search?: string\n}\n\nconst DEFAULT_LIMIT = 20\n\n// Expressões correlacionadas da lista de conversas, extraídas para poderem ser renderizadas e\n// verificadas sem banco (ver SessionRepository.test.ts).\n//\n// A correlação usa `${sessions}.company_id` e NÃO `${sessions.companyId}`: dentro da lista de\n// seleção o drizzle renderiza a referência de coluna sem qualificar a tabela, e um\n// `\"company_id\"` solto dentro do subselect resolve para a coluna de `m`. O resultado seria\n// `m.company_id = m.company_id` (sempre verdadeiro) e `m.session_id = m.id` (nunca) — zero\n// linhas, NULL, e nenhum erro.\nexport const conversationSummaryProjection = {\n lastContent: sql<string | null>`(\n select m.content from ${messages} m\n where m.company_id = ${sessions}.company_id and m.session_id = ${sessions}.id\n order by m.created_at desc limit 1\n )`,\n lastDirection: sql<string | null>`(\n select m.direction from ${messages} m\n where m.company_id = ${sessions}.company_id and m.session_id = ${sessions}.id\n order by m.created_at desc limit 1\n )`,\n // Entradas do cliente depois da última leitura do atendente. Sessão nunca lida conta\n // tudo — é o comportamento esperado de uma conversa que ninguém abriu ainda.\n unread: sql<number>`(\n select count(*)::int from ${messages} m\n where m.company_id = ${sessions}.company_id\n and m.session_id = ${sessions}.id\n and m.direction = 'inbound'\n and (${sessions}.last_agent_read_at is null or m.created_at > ${sessions}.last_agent_read_at)\n )`,\n} as const\n\n// T3.2 — todo método recebe/filtra por companyId explicitamente (nunca lê de um campo do\n// payload do cliente); ver database.md \"Consistência e multiempresa\".\nexport class SessionRepository {\n constructor(private readonly db: MetaWhatsAppDatabase) {}\n\n async getContext(companyId: string, whatsappNumber: string): Promise<SessionRow | undefined> {\n const [row] = await this.db\n .select()\n .from(sessions)\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n .limit(1)\n return row\n }\n\n async getOrCreate(companyId: string, whatsappNumber: string, startState: SessionState): Promise<SessionRow> {\n const existing = await this.getContext(companyId, whatsappNumber)\n if (existing) return existing\n\n const [created] = await this.db\n .insert(sessions)\n .values({ companyId, whatsappNumber, currentState: startState })\n .onConflictDoUpdate({\n target: [sessions.companyId, sessions.whatsappNumber],\n set: { lastActivity: sql`now()` },\n })\n .returning()\n return created!\n }\n\n async setState(\n companyId: string,\n whatsappNumber: string,\n state: SessionState,\n context?: Record<string, unknown>,\n ): Promise<void> {\n await this.db\n .update(sessions)\n .set({\n currentState: state,\n ...(context !== undefined ? { context } : {}),\n lastActivity: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n // Posição no grafo de fluxo — chamado pelo host a cada transição do FlowInterpreter.\n // Passar null em ambos desliga o rastreio (ex.: conversa saiu do motor de fluxo).\n async setFlowPosition(\n companyId: string,\n whatsappNumber: string,\n flowKey: string | null,\n currentNodeId: string | null,\n ): Promise<void> {\n await this.db\n .update(sessions)\n .set({ flowKey, currentNodeId, lastActivity: sql`now()`, updatedAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n // Marca a chegada de uma mensagem DO CLIENTE. É este carimbo — e não lastActivity, que também\n // se move em ações do atendente/bot — que define a janela de 24h do WhatsApp: fora dela, só\n // template aprovado reabre a conversa (ver WindowExpiredError nos contracts).\n async touchInbound(companyId: string, whatsappNumber: string): Promise<void> {\n await this.db\n .update(sessions)\n .set({ lastInboundAt: sql`now()`, lastActivity: sql`now()`, updatedAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n // Horas desde a última mensagem do cliente — undefined se ele nunca escreveu. O canal (Fase 5)\n // usa isto para decidir entre mensagem livre e template antes de chamar a Graph API.\n async hoursSinceLastInbound(companyId: string, whatsappNumber: string): Promise<number | undefined> {\n const session = await this.getContext(companyId, whatsappNumber)\n if (!session?.lastInboundAt) return undefined\n return (Date.now() - session.lastInboundAt.getTime()) / (1000 * 60 * 60)\n }\n\n async setMode(\n companyId: string,\n whatsappNumber: string,\n mode: SessionMode,\n assignedUserId?: string | null,\n ): Promise<void> {\n await this.db\n .update(sessions)\n .set({ mode, assignedUserId: assignedUserId ?? null, updatedAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n // T3.3 — takeover: atendente assume a conversa, tirando-a do modo bot.\n async takeover(companyId: string, whatsappNumber: string, agentUserId: string): Promise<void> {\n await this.setMode(companyId, whatsappNumber, 'human', agentUserId)\n }\n\n // T3.3 — release: devolve a conversa ao bot.\n async release(companyId: string, whatsappNumber: string): Promise<void> {\n await this.setMode(companyId, whatsappNumber, 'bot', null)\n }\n\n async requestHuman(companyId: string, whatsappNumber: string): Promise<void> {\n await this.db\n .update(sessions)\n .set({ humanRequestedAt: sql`now()`, updatedAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n async markRead(companyId: string, whatsappNumber: string): Promise<void> {\n await this.db\n .update(sessions)\n .set({ lastAgentReadAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.whatsappNumber, whatsappNumber)))\n }\n\n async markAllRead(companyId: string, userId: string): Promise<number> {\n const result = await this.db\n .update(sessions)\n .set({ lastAgentReadAt: sql`now()` })\n .where(and(eq(sessions.companyId, companyId), eq(sessions.assignedUserId, userId)))\n return Number((result as unknown as { rowCount?: number }).rowCount ?? 0)\n }\n\n async listByContextFilters(companyId: string, filters: ListConversationsFilters): Promise<ConversationSummary[]> {\n const limit = filters.limit ?? DEFAULT_LIMIT\n const offset = ((filters.page ?? 1) - 1) * limit\n\n const conditions = [eq(sessions.companyId, companyId)]\n if (filters.waitingHuman) conditions.push(sql`${sessions.humanRequestedAt} is not null`)\n if (filters.search) conditions.push(sql`${sessions.whatsappNumber} ilike ${'%' + filters.search + '%'}`)\n\n const rows = await this.db\n .select({\n id: sessions.id,\n whatsappNumber: sessions.whatsappNumber,\n mode: sessions.mode,\n assignedUserId: sessions.assignedUserId,\n currentState: sessions.currentState,\n lastActivity: sessions.lastActivity,\n lastInboundAt: sessions.lastInboundAt,\n humanRequestedAt: sessions.humanRequestedAt,\n // Prévia e contagem saem de subquery correlacionada em vez de N+1 na volta: uma inbox\n // lista dezenas de conversas por página, e uma query por linha é o gargalo clássico\n // dessa tela. Ambos os campos são dados do próprio módulo — deixá-los para o host\n // obrigaria todo consumidor a reescrever o mesmo join contra tabelas que não são dele.\n ...conversationSummaryProjection,\n })\n .from(sessions)\n .where(and(...conditions))\n .orderBy(desc(sessions.lastActivity))\n .limit(limit)\n .offset(offset)\n\n return rows.map((row) => ({\n id: row.id,\n whatsappNumber: row.whatsappNumber,\n ...(row.lastContent !== null ? { lastContent: row.lastContent } : {}),\n ...(row.lastDirection !== null ? { lastDirection: row.lastDirection as MessageDirection } : {}),\n lastAt: row.lastActivity.toISOString(),\n lastInboundAt: row.lastInboundAt?.toISOString() ?? null,\n mode: row.mode as SessionMode,\n assignedUserId: row.assignedUserId,\n waitingHuman: row.humanRequestedAt !== null,\n unread: Number(row.unread),\n currentState: row.currentState,\n }))\n }\n\n // T3.3 — export: transcript completo de uma conversa, mensagens em ordem cronológica.\n async exportConversation(companyId: string, whatsappNumber: string) {\n const session = await this.getContext(companyId, whatsappNumber)\n if (!session) return null\n\n const rows = await this.db\n .select()\n .from(messages)\n .where(and(eq(messages.companyId, companyId), eq(messages.sessionId, session.id)))\n .orderBy(messages.createdAt)\n\n return { session, messages: rows }\n }\n}\n","import { sql } from 'drizzle-orm'\nimport {\n pgSchema,\n uuid,\n varchar,\n jsonb,\n timestamp,\n text,\n integer,\n boolean,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core'\n\n// Schema Postgres dedicado (T3.1/T3.5) — o módulo nunca escreve no schema `public` do host,\n// só ocupa este namespace próprio. O banco continua sendo um por produto; isto é apenas um\n// namespace dentro dele (ver rules/packages/pluggable-module.md §3).\nexport const metaWhatsAppSchema = pgSchema('meta_whatsapp')\n\nexport const sessions = metaWhatsAppSchema.table(\n 'sessions',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n // Multiempresa: toda entidade carrega companyId, vindo do contexto autenticado do host —\n // nunca de um campo livre do cliente (ver database.md \"Consistência e multiempresa\").\n companyId: uuid('company_id').notNull(),\n whatsappNumber: varchar('whatsapp_number', { length: 20 }).notNull(),\n // varchar (não enum): a máquina de estados de cada produto evolui de forma independente do módulo.\n currentState: varchar('current_state', { length: 64 }).notNull().default('start'),\n // Posição no grafo, quando o motor de fluxo está em uso — colunas separadas em vez de\n // convencionar \"flowKey:nodeId\" dentro de currentState: além de ser um contrato implícito que\n // nada garantia, a concatenação estourava o varchar(64) (flow key já é varchar(64) sozinha).\n flowKey: varchar('flow_key', { length: 64 }),\n currentNodeId: varchar('current_node_id', { length: 64 }),\n context: jsonb('context').$type<Record<string, unknown>>().notNull().default({}),\n mode: varchar('mode', { length: 12 }).notNull().default('bot'), // bot | human\n assignedUserId: uuid('assigned_user_id'),\n humanRequestedAt: timestamp('human_requested_at', { withTimezone: true }),\n lastInboundAt: timestamp('last_inbound_at', { withTimezone: true }),\n lastAgentReadAt: timestamp('last_agent_read_at', { withTimezone: true }),\n lastActivity: timestamp('last_activity', { withTimezone: true }).notNull().defaultNow(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n // Único por empresa (não globalmente) — dois hosts diferentes podem atender o mesmo número.\n uniqueIndex('idx_sessions_company_number').on(table.companyId, table.whatsappNumber),\n index('idx_sessions_company_mode').on(table.companyId, table.mode),\n // Alimenta getLiveFlowPositions (agregação por nó do grafo).\n index('idx_sessions_company_flow_node').on(table.companyId, table.flowKey, table.currentNodeId),\n ],\n)\n\nexport const messages = metaWhatsAppSchema.table(\n 'messages',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n companyId: uuid('company_id').notNull(),\n sessionId: uuid('session_id')\n .notNull()\n .references(() => sessions.id, { onDelete: 'cascade' }),\n whatsappNumber: varchar('whatsapp_number', { length: 20 }).notNull(),\n direction: varchar('direction', { length: 12 }).notNull(), // inbound | outbound\n sender: varchar('sender', { length: 12 }).notNull(), // customer | bot | agent\n agentUserId: uuid('agent_user_id'),\n // 32, não 16: os tipos da própria Meta são curtos ('text', 'interactive'), mas o host rotula\n type:\n // a saída com o subtipo que enviou ('interactive_buttons' já tem 19). Apertar aqui só\n // transfere para o consumidor a escolha entre truncar o rótulo e estourar o insert.\n varchar('type', { length: 32 }).notNull().default('text'),\n content: text('content'),\n // T5.4 — nunca base64 aqui; mídia vive no ObjectStorageInterface do host, referenciada por\n // uploadId dentro deste jsonb.\n payload: jsonb('payload').$type<Record<string, unknown>>(),\n waMessageId: varchar('wa_message_id', { length: 128 }),\n status: varchar('status', { length: 16 }),\n readAt: timestamp('read_at', { withTimezone: true }),\n // Moderação de conteúdo. `null` significa NÃO AVALIADO (moderação desligada, ou mensagem\n // anterior ao recurso) — diferente de `false`, que é avaliado e limpo. Colunas em vez de chave\n // dentro de `payload` porque \"listar o que foi sinalizado\" é consulta de operação, e índice\n // parcial sobre boolean resolve isso sem cavar jsonb.\n moderationFlagged: boolean('moderation_flagged'),\n moderationTerms: jsonb('moderation_terms').$type<string[]>(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [\n index('idx_messages_session_created').on(table.sessionId, table.createdAt),\n index('idx_messages_company_number_created').on(table.companyId, table.whatsappNumber, table.createdAt),\n // Parcial e ÚNICO: é o que de fato garante idempotência de entrega. Um SELECT-antes-de-INSERT\n // não basta — a Meta reenvia a mesma entrega e várias instâncias do host processam em\n // paralelo, então as duas passariam pela checagem e inseririam duplicado. Parcial porque\n // mensagens outbound ainda sem waMessageId (envio em curso) são legitimamente NULL, e NULLs\n // não podem competir entre si por unicidade.\n uniqueIndex('idx_messages_company_wa_message_id')\n .on(table.companyId, table.waMessageId)\n .where(sql`${table.waMessageId} is not null`),\n // Parcial: só as sinalizadas entram, então o índice fica do tamanho do problema e não do\n // tamanho do transcript.\n index('idx_messages_moderation_flagged')\n .on(table.companyId, table.createdAt)\n .where(sql`${table.moderationFlagged}`),\n ],\n)\n\n// T4.1 — um grafo por fluxo de conversa. `nodes` guarda o Record<string, FlowNodeData> inteiro\n// como jsonb (mesmo shape do editor visual em conversations-ui/flows) — não normalizado em\n// linhas, porque o grafo é sempre lido/escrito como uma unidade pelo editor e pelo interpretador.\nexport const flowGraphs = metaWhatsAppSchema.table(\n 'flow_graphs',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n companyId: uuid('company_id').notNull(),\n key: varchar('key', { length: 64 }).notNull(),\n label: varchar('label', { length: 120 }).notNull(),\n startNodeId: varchar('start_node_id', { length: 64 }).notNull(),\n nodes: jsonb('nodes').$type<Record<string, unknown>>().notNull().default({}),\n // Otimista: o editor manda a versão que carregou; salvar com versão desatualizada é\n // rejeitado pelo host (evita duas abas sobrescreverem uma a outra silenciosamente).\n version: integer('version').notNull().default(1),\n showInMenu: boolean('show_in_menu').notNull().default(false),\n menuOptionLabel: varchar('menu_option_label', { length: 64 }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (table) => [uniqueIndex('idx_flow_graphs_company_key').on(table.companyId, table.key)],\n)\n\n// T5.5 — configuração de WhatsApp POR EMPRESA, em tabela do módulo. Deliberadamente separada do\n// app_config genérico do host: são chaves que só o módulo entende, e mantê-las aqui é o que\n// permite instalar/remover a capacidade sem migrar a tabela de configuração do produto.\nexport const settings = metaWhatsAppSchema.table('settings', {\n companyId: uuid('company_id').primaryKey(),\n templateName: varchar('template_name', { length: 128 }),\n templateLanguage: varchar('template_language', { length: 16 }).notNull().default('pt_BR'),\n // Mapa posicional {{1}}, {{2}}... → token resolvido no envio (ex.: '{clientName}').\n templateVariables: jsonb('template_variables').$type<string[]>().notNull().default([]),\n welcomeMessage: text('welcome_message'),\n farewellMessage: text('farewell_message'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n})\n\nexport type SettingsRow = typeof settings.$inferSelect\nexport type NewSettingsRow = typeof settings.$inferInsert\n\nexport type SessionRow = typeof sessions.$inferSelect\nexport type NewSessionRow = typeof sessions.$inferInsert\nexport type MessageRow = typeof messages.$inferSelect\nexport type NewMessageRow = typeof messages.$inferInsert\nexport type FlowGraphRow = typeof flowGraphs.$inferSelect\nexport type NewFlowGraphRow = typeof flowGraphs.$inferInsert\n","import { and, eq, lt } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport type { MessageDirection, MessageSender, MessageStatus } from '@adatechnology/meta-whatsapp-contracts'\nimport { messages, type MessageRow, type NewMessageRow } from '../schema/schema'\n\nexport interface InsertMessageParams {\n companyId: string\n sessionId: string\n whatsappNumber: string\n direction: MessageDirection\n sender: MessageSender\n agentUserId?: string | null\n type: string\n content?: string | null\n payload?: Record<string, unknown> | null\n waMessageId?: string | null\n status?: MessageStatus | null\n /** `undefined` deixa a coluna nula: não avaliado, distinto de avaliado e limpo. */\n moderationFlagged?: boolean | null\n moderationTerms?: string[] | null\n}\n\nexport interface ListMessagesParams {\n companyId: string\n sessionId: string\n limit?: number\n before?: string\n}\n\nconst DEFAULT_LIMIT = 50\n\nexport class MessageRepository {\n constructor(private readonly db: MetaWhatsAppDatabase) {}\n\n // Idempotente por (companyId, waMessageId) — o mesmo webhook chega mais de uma vez (a Meta\n // reenvia em instabilidade) e várias instâncias do host processam em paralelo. A garantia é o\n // índice único parcial no banco + onConflictDoNothing, NÃO um SELECT prévio: duas entregas\n // concorrentes passariam as duas pela checagem e inseririam duplicado.\n // Devolve undefined quando a mensagem já existia (nada foi inserido).\n async insertMessage(params: InsertMessageParams): Promise<MessageRow | undefined> {\n const values: NewMessageRow = {\n companyId: params.companyId,\n sessionId: params.sessionId,\n whatsappNumber: params.whatsappNumber,\n direction: params.direction,\n sender: params.sender,\n agentUserId: params.agentUserId ?? null,\n type: params.type,\n content: params.content ?? null,\n payload: params.payload ?? null,\n waMessageId: params.waMessageId ?? null,\n status: params.status ?? null,\n moderationFlagged: params.moderationFlagged ?? null,\n moderationTerms: params.moderationTerms ?? null,\n }\n\n const [created] = await this.db.insert(messages).values(values).onConflictDoNothing().returning()\n return created\n }\n\n async updateMessageStatus(\n companyId: string,\n waMessageId: string,\n status: MessageStatus,\n ): Promise<MessageRow | undefined> {\n const [updated] = await this.db\n .update(messages)\n .set({ status, ...(status === 'read' ? { readAt: new Date() } : {}) })\n .where(and(eq(messages.companyId, companyId), eq(messages.waMessageId, waMessageId)))\n .returning()\n return updated\n }\n\n async listByConversation(params: ListMessagesParams): Promise<MessageRow[]> {\n const conditions = [eq(messages.companyId, params.companyId), eq(messages.sessionId, params.sessionId)]\n if (params.before) conditions.push(lt(messages.createdAt, new Date(params.before)))\n\n return this.db\n .select()\n .from(messages)\n .where(and(...conditions))\n .orderBy(messages.createdAt)\n .limit(params.limit ?? DEFAULT_LIMIT)\n }\n}\n","import { and, eq, isNotNull, sql } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport { flowGraphNodesSchema } from '@adatechnology/meta-whatsapp-contracts'\nimport type {\n FlowGraphData,\n FlowGraphSummary,\n FlowNodeData,\n LiveFlowPosition,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport { sessions, flowGraphs, type FlowGraphRow } from '../schema/schema'\n\nexport class InvalidFlowGraphError extends Error {\n constructor(\n key: string,\n public readonly validationMessage: string,\n ) {\n super(`Grafo do fluxo \"${key}\" está malformado no banco: ${validationMessage}`)\n this.name = 'InvalidFlowGraphError'\n }\n}\n\n// O jsonb `nodes` entrou pelo editor (dado de origem cliente) e volta do banco como `unknown`.\n// Validar aqui, na fronteira, faz um grafo corrompido falhar com a causa explícita em vez de\n// virar comportamento estranho lá dentro do interpretador.\nfunction toContractGraph(row: FlowGraphRow): FlowGraphData {\n const parsed = flowGraphNodesSchema.safeParse(row.nodes)\n if (!parsed.success) throw new InvalidFlowGraphError(row.key, parsed.error.message)\n\n return {\n key: row.key,\n label: row.label,\n startNodeId: row.startNodeId,\n version: row.version,\n nodes: parsed.data as Record<string, FlowNodeData>,\n }\n}\n\nexport class OptimisticLockError extends Error {\n constructor(key: string) {\n super(`Fluxo \"${key}\" foi alterado por outra sessão — recarregue antes de salvar.`)\n this.name = 'OptimisticLockError'\n }\n}\n\n// T4.2 — CRUD de grafo de fluxo, mais a consulta de posições ao vivo (quantas sessões estão\n// em cada nó agora) que alimenta o liveCount no editor visual (FlowNodeCardData).\nexport class FlowGraphRepository {\n constructor(private readonly db: MetaWhatsAppDatabase) {}\n\n async get(companyId: string, key: string): Promise<FlowGraphData | undefined> {\n const [row] = await this.db\n .select()\n .from(flowGraphs)\n .where(and(eq(flowGraphs.companyId, companyId), eq(flowGraphs.key, key)))\n .limit(1)\n return row ? toContractGraph(row) : undefined\n }\n\n async list(companyId: string): Promise<FlowGraphSummary[]> {\n const rows = await this.db.select().from(flowGraphs).where(eq(flowGraphs.companyId, companyId))\n return rows.map((row) => ({\n key: row.key,\n label: row.label,\n nodeCount: Object.keys(row.nodes as Record<string, unknown>).length,\n showInMenu: row.showInMenu,\n menuOptionLabel: row.menuOptionLabel ?? undefined,\n updatedAt: row.updatedAt.toISOString(),\n }))\n }\n\n // Valida antes de gravar: aceitar um grafo malformado e só reclamar na leitura empurraria a\n // falha para o meio de uma conversa real, em vez de barrar quem salvou.\n private assertValidNodes(key: string, nodes: unknown): void {\n const parsed = flowGraphNodesSchema.safeParse(nodes)\n if (!parsed.success) throw new InvalidFlowGraphError(key, parsed.error.message)\n }\n\n async create(\n companyId: string,\n graph: Omit<FlowGraphData, 'version'> & { showInMenu?: boolean; menuOptionLabel?: string },\n ): Promise<FlowGraphData> {\n this.assertValidNodes(graph.key, graph.nodes)\n\n const [created] = await this.db\n .insert(flowGraphs)\n .values({\n companyId,\n key: graph.key,\n label: graph.label,\n startNodeId: graph.startNodeId,\n nodes: graph.nodes,\n showInMenu: graph.showInMenu ?? false,\n menuOptionLabel: graph.menuOptionLabel,\n })\n .returning()\n return toContractGraph(created!)\n }\n\n // Lock otimista: a escrita só aplica se `expectedVersion` ainda bater com o que está salvo —\n // senão, alguém mais salvou entretanto e o editor precisa recarregar (ver comentário no schema).\n async save(companyId: string, graph: FlowGraphData, expectedVersion: number): Promise<FlowGraphData> {\n this.assertValidNodes(graph.key, graph.nodes)\n\n const rows = await this.db\n .update(flowGraphs)\n .set({\n label: graph.label,\n startNodeId: graph.startNodeId,\n nodes: graph.nodes,\n version: expectedVersion + 1,\n updatedAt: new Date(),\n })\n .where(\n and(\n eq(flowGraphs.companyId, companyId),\n eq(flowGraphs.key, graph.key),\n eq(flowGraphs.version, expectedVersion),\n ),\n )\n .returning()\n\n if (rows.length === 0) throw new OptimisticLockError(graph.key)\n return toContractGraph(rows[0]!)\n }\n\n async delete(companyId: string, key: string): Promise<void> {\n await this.db.delete(flowGraphs).where(and(eq(flowGraphs.companyId, companyId), eq(flowGraphs.key, key)))\n }\n\n // T4.2 — GetLiveFlowPositions: agrega sessões ativas por (flowKey, currentNodeId), lendo as\n // colunas dedicadas gravadas por SessionRepository.setFlowPosition. Agrega no banco (GROUP BY,\n // coberto por idx_sessions_company_flow_node) em vez de trazer toda sessão da empresa para\n // contar em memória.\n async getLiveFlowPositions(companyId: string): Promise<LiveFlowPosition[]> {\n const rows = await this.db\n .select({\n flowKey: sessions.flowKey,\n nodeId: sessions.currentNodeId,\n count: sql<number>`count(*)::int`,\n })\n .from(sessions)\n .where(\n and(\n eq(sessions.companyId, companyId),\n eq(sessions.mode, 'bot'),\n isNotNull(sessions.flowKey),\n isNotNull(sessions.currentNodeId),\n ),\n )\n .groupBy(sessions.flowKey, sessions.currentNodeId)\n\n return rows.map((row) => ({ flowKey: row.flowKey!, nodeId: row.nodeId!, count: row.count }))\n }\n}\n","import { eq } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport type { WhatsAppSettings } from '@adatechnology/meta-whatsapp-contracts'\nimport { settings, type SettingsRow } from '../schema/schema'\n\nfunction toContractSettings(row: SettingsRow): WhatsAppSettings {\n return {\n templateName: row.templateName ?? '',\n templateLanguage: row.templateLanguage,\n templateVariables: row.templateVariables,\n welcomeMessage: row.welcomeMessage ?? '',\n farewellMessage: row.farewellMessage ?? '',\n }\n}\n\nconst EMPTY_SETTINGS: WhatsAppSettings = {\n templateName: '',\n templateLanguage: 'pt_BR',\n templateVariables: [],\n welcomeMessage: '',\n farewellMessage: '',\n}\n\n// T5.5 — configuração de WhatsApp por empresa.\nexport class SettingsRepository {\n constructor(private readonly db: MetaWhatsAppDatabase) {}\n\n // Empresa sem linha de configuração é o estado normal logo após instalar o módulo — devolve\n // os defaults em vez de undefined, para o chamador não precisar tratar \"ainda não configurado\"\n // como caso especial em todo lugar.\n async get(companyId: string): Promise<WhatsAppSettings> {\n const [row] = await this.db.select().from(settings).where(eq(settings.companyId, companyId)).limit(1)\n return row ? toContractSettings(row) : EMPTY_SETTINGS\n }\n\n async save(companyId: string, update: Partial<WhatsAppSettings>): Promise<WhatsAppSettings> {\n const current = await this.get(companyId)\n const merged = { ...current, ...update }\n\n const [row] = await this.db\n .insert(settings)\n .values({\n companyId,\n templateName: merged.templateName || null,\n templateLanguage: merged.templateLanguage,\n templateVariables: merged.templateVariables,\n welcomeMessage: merged.welcomeMessage || null,\n farewellMessage: merged.farewellMessage || null,\n })\n .onConflictDoUpdate({\n target: settings.companyId,\n set: {\n templateName: merged.templateName || null,\n templateLanguage: merged.templateLanguage,\n templateVariables: merged.templateVariables,\n welcomeMessage: merged.welcomeMessage || null,\n farewellMessage: merged.farewellMessage || null,\n updatedAt: new Date(),\n },\n })\n .returning()\n\n return toContractSettings(row!)\n }\n\n // Resolve {{1}}, {{2}}... a partir do mapa configurado + o contexto da conversa. Ex.: se\n // templateVariables = ['{clientName}', '{city}'], devolve [nome, cidade] na ordem posicional\n // que a Graph API espera nos bodyParameters.\n async resolveTemplateVariables(companyId: string, context: Record<string, unknown>): Promise<string[]> {\n const { templateVariables } = await this.get(companyId)\n return templateVariables.map((token) => {\n const key = token.replace(/^\\{|\\}$/g, '')\n const value = context[key]\n return value === undefined || value === null ? '' : String(value)\n })\n }\n}\n","import type { RealtimeNotifierInterface, SessionState } from '@adatechnology/meta-whatsapp-contracts'\nimport type { InsertMessageParams, MessageRepository } from '../repositories/MessageRepository'\nimport type { SessionRepository } from '../repositories/SessionRepository'\nimport type { MessageRow } from '../schema/schema'\n\nexport type LogMessageParams = Omit<InsertMessageParams, 'sessionId'> & {\n startState: SessionState\n}\n\n/**\n * O contrato mínimo de `@adatechnology/text-moderation`, declarado aqui em vez de importado.\n *\n * Assim o módulo não ganha dependência de pacote para um recurso opcional, e — mais importante —\n * moderação não fica amarrada a WhatsApp: o que atravessa esta fronteira é texto, e qualquer canal\n * que passe a escrever no transcript por este use case herda a marcação sem tocar nada aqui.\n */\nexport type MessageModerator = {\n inspect: (text: string) => { isOffensive: boolean; matchedTerms: readonly string[] }\n}\n\n// T3.3 — ponto único de escrita no transcript: garante a sessão (getOrCreate), insere a\n// mensagem (idempotente por waMessageId) e notifica em tempo real. Usado tanto pelo canal\n// (T5.2, ao enviar) quanto pelo webhook (T5.1, ao receber).\nexport class LogMessageUseCase {\n constructor(\n private readonly sessionRepository: SessionRepository,\n private readonly messageRepository: MessageRepository,\n private readonly realtime?: RealtimeNotifierInterface,\n private readonly moderator?: MessageModerator,\n ) {}\n\n async execute(params: LogMessageParams): Promise<MessageRow | undefined> {\n const session = await this.sessionRepository.getOrCreate(params.companyId, params.whatsappNumber, params.startState)\n\n const saved = await this.messageRepository.insertMessage({\n ...params,\n sessionId: session.id,\n ...this.moderationOf(params),\n })\n // Entrega duplicada (mesmo waMessageId) — não emite evento nem re-carimba a janela.\n if (!saved) return undefined\n\n if (params.direction === 'inbound') {\n await this.sessionRepository.touchInbound(params.companyId, params.whatsappNumber)\n }\n\n this.realtime?.emit(`conv:${params.whatsappNumber}`, 'message', {\n direction: params.direction,\n sender: params.sender,\n })\n this.realtime?.emit('global', 'data-changed', {})\n\n return saved\n }\n\n // Só o que o cliente escreveu: marcar o que o próprio atendente ou o bot enviou não sinaliza\n // abuso, apenas sujaria o transcript com etiqueta na resposta de quem atende.\n private moderationOf(params: LogMessageParams): Pick<InsertMessageParams, 'moderationFlagged' | 'moderationTerms'> {\n if (!this.moderator || params.direction !== 'inbound') return {}\n\n const text = params.content?.trim()\n if (!text) return {}\n\n const verdict = this.moderator.inspect(text)\n\n return {\n moderationFlagged: verdict.isOffensive,\n moderationTerms: verdict.isOffensive ? [...verdict.matchedTerms] : null,\n }\n }\n}\n","import {\n WindowExpiredError,\n type ChannelAdapterInterface,\n type ObjectStorageInterface,\n type SessionState,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport type { SessionRepository } from '../repositories/SessionRepository'\nimport type { LogMessageUseCase } from './LogMessage.use-case'\nimport type { MessageRow } from '../schema/schema'\n\nconst WHATSAPP_WINDOW_HOURS = 24\n\nexport type SendTextParams = {\n companyId: string\n whatsappNumber: string\n body: string\n sender: 'bot' | 'agent'\n agentUserId?: string\n startState: SessionState\n}\n\nexport type SendMediaParams = {\n companyId: string\n whatsappNumber: string\n buffer: Buffer\n mimeType: string\n filename: string\n caption?: string\n sender: 'bot' | 'agent'\n agentUserId?: string\n startState: SessionState\n}\n\nexport type SendTemplateParams = {\n companyId: string\n whatsappNumber: string\n templateName: string\n languageCode?: string\n bodyParameters?: string[]\n sender: 'bot' | 'agent'\n agentUserId?: string\n startState: SessionState\n}\n\n// T5.2 — envio pelo canal + registro no transcript, num único ponto.\n//\n// A checagem de janela acontece ANTES da chamada à Graph API: a Meta também rejeitaria\n// (o adapter traduz o erro dela para WindowExpiredError), mas gastar uma ida à rede para\n// descobrir algo que o nosso próprio lastInboundAt já sabe é desperdício — e o erro local\n// carrega há quantas horas o cliente sumiu, que a resposta da Meta não informa.\nexport class SendMessageUseCase {\n constructor(\n private readonly channel: ChannelAdapterInterface,\n private readonly sessionRepository: SessionRepository,\n private readonly logMessage: LogMessageUseCase,\n private readonly objectStorage?: ObjectStorageInterface,\n ) {}\n\n private async assertWithinWindow(companyId: string, whatsappNumber: string): Promise<void> {\n const hours = await this.sessionRepository.hoursSinceLastInbound(companyId, whatsappNumber)\n // Cliente que nunca escreveu: não há janela aberta, só template pode iniciar a conversa.\n if (hours === undefined) throw new WindowExpiredError()\n if (hours >= WHATSAPP_WINDOW_HOURS) throw new WindowExpiredError(hours)\n }\n\n async sendText(params: SendTextParams): Promise<MessageRow | undefined> {\n await this.assertWithinWindow(params.companyId, params.whatsappNumber)\n\n const { externalMessageId } = await this.channel.sendText(params.whatsappNumber, params.body)\n\n return this.logMessage.execute({\n companyId: params.companyId,\n whatsappNumber: params.whatsappNumber,\n direction: 'outbound',\n sender: params.sender,\n agentUserId: params.agentUserId ?? null,\n type: 'text',\n content: params.body,\n waMessageId: externalMessageId,\n status: 'sent',\n startState: params.startState,\n })\n }\n\n async sendMedia(params: SendMediaParams): Promise<MessageRow | undefined> {\n await this.assertWithinWindow(params.companyId, params.whatsappNumber)\n\n const { externalMessageId } = await this.channel.sendMedia({\n to: params.whatsappNumber,\n buffer: params.buffer,\n mimeType: params.mimeType,\n filename: params.filename,\n caption: params.caption,\n })\n\n // T5.4 — o binário vai para o storage do host e o transcript guarda só a referência.\n // Gravar base64 no jsonb (dívida herdada do bot) inchava a tabela de mensagens, tornava\n // qualquer SELECT da conversa caro e duplicava um dado que o storage já tem.\n const uploadId = this.objectStorage\n ? (\n await this.objectStorage.upload({\n buffer: params.buffer,\n mimeType: params.mimeType,\n key: `meta-whatsapp/${params.companyId}/${Date.now()}-${params.filename}`,\n })\n ).uploadId\n : undefined\n\n return this.logMessage.execute({\n companyId: params.companyId,\n whatsappNumber: params.whatsappNumber,\n direction: 'outbound',\n sender: params.sender,\n agentUserId: params.agentUserId ?? null,\n type: mediaTypeFor(params.mimeType),\n content: params.caption ?? params.filename,\n payload: { filename: params.filename, mimeType: params.mimeType, ...(uploadId ? { uploadId } : {}) },\n waMessageId: externalMessageId,\n status: 'sent',\n startState: params.startState,\n })\n }\n\n // Template é o único envio que ignora a janela — é justamente o mecanismo que a Meta oferece\n // para reabri-la.\n async sendTemplate(params: SendTemplateParams): Promise<MessageRow | undefined> {\n const { externalMessageId } = await this.channel.sendTemplate({\n to: params.whatsappNumber,\n templateName: params.templateName,\n languageCode: params.languageCode ?? 'pt_BR',\n bodyParameters: params.bodyParameters,\n })\n\n return this.logMessage.execute({\n companyId: params.companyId,\n whatsappNumber: params.whatsappNumber,\n direction: 'outbound',\n sender: params.sender,\n agentUserId: params.agentUserId ?? null,\n type: 'template',\n content: params.templateName,\n payload: { templateName: params.templateName, bodyParameters: params.bodyParameters ?? [] },\n waMessageId: externalMessageId,\n status: 'sent',\n startState: params.startState,\n })\n }\n}\n\nfunction mediaTypeFor(mimeType: string): string {\n if (mimeType.startsWith('image/')) return 'image'\n if (mimeType.startsWith('audio/')) return 'audio'\n if (mimeType.startsWith('video/')) return 'video'\n return 'document'\n}\n","import type { SessionRepository } from '../repositories/SessionRepository'\nimport type { RealtimeNotifierInterface } from '@adatechnology/meta-whatsapp-contracts'\n\nexport type TakeoverConversationParams = {\n companyId: string\n whatsappNumber: string\n agentUserId: string\n}\n\n// T3.3 — atendente assume a conversa (tira do bot). Emite evento para os clientes de admin\n// atualizarem a UI (badge \"atendido por\", desabilitar botão de takeover) em tempo real.\nexport class TakeoverConversationUseCase {\n constructor(\n private readonly sessionRepository: SessionRepository,\n private readonly realtime?: RealtimeNotifierInterface,\n ) {}\n\n async execute(params: TakeoverConversationParams): Promise<void> {\n await this.sessionRepository.takeover(params.companyId, params.whatsappNumber, params.agentUserId)\n this.realtime?.emit(`conv:${params.whatsappNumber}`, 'mode-changed', {\n mode: 'human',\n assignedUserId: params.agentUserId,\n })\n this.realtime?.emit('global', 'data-changed', {})\n }\n}\n","import type { SessionRepository } from '../repositories/SessionRepository'\nimport type { RealtimeNotifierInterface } from '@adatechnology/meta-whatsapp-contracts'\n\nexport type ReleaseConversationParams = {\n companyId: string\n whatsappNumber: string\n}\n\n// T3.3 — devolve a conversa ao bot.\nexport class ReleaseConversationUseCase {\n constructor(\n private readonly sessionRepository: SessionRepository,\n private readonly realtime?: RealtimeNotifierInterface,\n ) {}\n\n async execute(params: ReleaseConversationParams): Promise<void> {\n await this.sessionRepository.release(params.companyId, params.whatsappNumber)\n this.realtime?.emit(`conv:${params.whatsappNumber}`, 'mode-changed', { mode: 'bot', assignedUserId: null })\n this.realtime?.emit('global', 'data-changed', {})\n }\n}\n","import type { ConversationSummary } from '@adatechnology/meta-whatsapp-contracts'\nimport type { ListConversationsFilters, SessionRepository } from '../repositories/SessionRepository'\n\nexport type ListConversationsParams = {\n companyId: string\n filters?: ListConversationsFilters\n}\n\n// T3.3 — listagem paginada/filtrada de conversas para a tela de inbox do host.\nexport class ListConversationsUseCase {\n constructor(private readonly sessionRepository: SessionRepository) {}\n\n async execute(params: ListConversationsParams): Promise<ConversationSummary[]> {\n return this.sessionRepository.listByContextFilters(params.companyId, params.filters ?? {})\n }\n}\n","import type { MessageRepository, ListMessagesParams as RepoListMessagesParams } from '../repositories/MessageRepository'\nimport type { SessionRepository } from '../repositories/SessionRepository'\nimport { SessionNotFoundError } from '@adatechnology/meta-whatsapp-contracts'\nimport type { MessageRow } from '../schema/schema'\n\nexport type ListMessagesParams = {\n companyId: string\n whatsappNumber: string\n limit?: number\n before?: string\n}\n\n// T3.3 — histórico paginado de uma conversa (resolve sessionId a partir do número antes de\n// delegar ao repositório de mensagens).\nexport class ListMessagesUseCase {\n constructor(\n private readonly sessionRepository: SessionRepository,\n private readonly messageRepository: MessageRepository,\n ) {}\n\n async execute(params: ListMessagesParams): Promise<MessageRow[]> {\n const session = await this.sessionRepository.getContext(params.companyId, params.whatsappNumber)\n if (!session) throw new SessionNotFoundError(params.whatsappNumber)\n\n const repoParams: RepoListMessagesParams = {\n companyId: params.companyId,\n sessionId: session.id,\n limit: params.limit,\n before: params.before,\n }\n return this.messageRepository.listByConversation(repoParams)\n }\n}\n","import type { SessionRepository } from '../repositories/SessionRepository'\nimport { SessionNotFoundError } from '@adatechnology/meta-whatsapp-contracts'\nimport type { MessageRow, SessionRow } from '../schema/schema'\n\nexport type ExportConversationParams = {\n companyId: string\n whatsappNumber: string\n}\n\nexport type ExportConversationResult = {\n session: SessionRow\n messages: MessageRow[]\n}\n\n// T3.3 — transcript completo para export (ex.: anexar num chamado de suporte, auditoria).\nexport class ExportConversationUseCase {\n constructor(private readonly sessionRepository: SessionRepository) {}\n\n async execute(params: ExportConversationParams): Promise<ExportConversationResult> {\n const result = await this.sessionRepository.exportConversation(params.companyId, params.whatsappNumber)\n if (!result) throw new SessionNotFoundError(params.whatsappNumber)\n return result\n }\n}\n","import type { FlowGraphData, FlowGraphSummary } from '@adatechnology/meta-whatsapp-contracts'\nimport type { FlowGraphRepository } from '../repositories/FlowGraphRepository'\n\n// T4.2 — CRUD de grafo, agrupado num único arquivo (cada use-case é uma classe fina sobre o\n// mesmo repositório; não há regra de negócio própria a proteger na sua separação em arquivos\n// individuais como fizemos para conversation/session).\n\nexport class GetFlowGraphUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: { companyId: string; key: string }): Promise<FlowGraphData | undefined> {\n return this.flowGraphRepository.get(params.companyId, params.key)\n }\n}\n\nexport class ListFlowGraphsUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: { companyId: string }): Promise<FlowGraphSummary[]> {\n return this.flowGraphRepository.list(params.companyId)\n }\n}\n\nexport type CreateFlowGraphParams = {\n companyId: string\n key: string\n label: string\n startNodeId: string\n nodes: FlowGraphData['nodes']\n showInMenu?: boolean\n menuOptionLabel?: string\n}\n\nexport class CreateFlowGraphUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: CreateFlowGraphParams): Promise<FlowGraphData> {\n return this.flowGraphRepository.create(params.companyId, params)\n }\n}\n\nexport type SaveFlowGraphParams = {\n companyId: string\n graph: FlowGraphData\n expectedVersion: number\n}\n\n// Salvar já é publicar — o motor de fluxo lê o grafo em tempo real (ver rules/packages/\n// pluggable-module.md e tasks.md T4.2). A validação de conteúdo (destinos quebrados, opções\n// duplicadas, etc.) é responsabilidade do editor (conversations-ui/flows validateGraph), não\n// deste use-case — aqui só garantimos a consistência otimista da escrita.\nexport class SaveFlowGraphUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: SaveFlowGraphParams): Promise<FlowGraphData> {\n return this.flowGraphRepository.save(params.companyId, params.graph, params.expectedVersion)\n }\n}\n\nexport class DeleteFlowGraphUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: { companyId: string; key: string }): Promise<void> {\n return this.flowGraphRepository.delete(params.companyId, params.key)\n }\n}\n\nexport class GetLiveFlowPositionsUseCase {\n constructor(private readonly flowGraphRepository: FlowGraphRepository) {}\n async execute(params: { companyId: string }) {\n return this.flowGraphRepository.getLiveFlowPositions(params.companyId)\n }\n}\n","import type {\n ChannelAdapterInterface,\n ConversationSession,\n FlowActionHandler,\n FlowActionKind,\n FlowActionResult,\n FlowGraphData,\n FlowNodeData,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport { isCrossFlowTarget, crossFlowKey } from '@adatechnology/meta-whatsapp-contracts'\n\nexport interface FlowStepInput {\n graph: FlowGraphData\n currentNodeId: string\n userAnswer?: string\n context: Record<string, unknown>\n session: ConversationSession\n channel: ChannelAdapterInterface\n}\n\nexport type FlowStepResult =\n | { kind: 'awaiting-answer'; nodeId: string; context: Record<string, unknown> }\n | { kind: 'advanced'; nodeId: string; context: Record<string, unknown> }\n | { kind: 'cross-flow'; flowKey: string; context: Record<string, unknown> }\n | { kind: 'terminal'; context: Record<string, unknown> }\n\n// Resultado de run(): nunca 'advanced' (o laço já consumiu esses), mas pode ser\n// 'max-steps-exceeded' quando o grafo tem um ciclo de nós automáticos.\nexport type FlowRunResult = (\n | { kind: 'awaiting-answer'; nodeId: string; context: Record<string, unknown> }\n | { kind: 'cross-flow'; flowKey: string; context: Record<string, unknown> }\n | { kind: 'terminal'; context: Record<string, unknown> }\n | { kind: 'max-steps-exceeded'; context: Record<string, unknown> }\n) & {\n // Caminho percorrido, do nó inicial ao final — serve para depurar um ciclo (os nós repetidos\n // aparecem aqui) e para o host registrar a trilha da conversa.\n visited: string[]\n steps: number\n}\n\nconst DEFAULT_MAX_STEPS = 50\n\nfunction evaluateCondition(node: FlowNodeData, context: Record<string, unknown>): boolean {\n if (!node.conditionContextKey || !node.conditionOperator || !node.conditionValue) return false\n const actual = context[node.conditionContextKey]\n const expected = node.conditionValue\n\n switch (node.conditionOperator) {\n case '==':\n return String(actual) === expected\n case '!=':\n return String(actual) !== expected\n case 'contains':\n return typeof actual === 'string' && actual.includes(expected)\n case '>':\n case '>=':\n case '<':\n case '<=': {\n const actualNum = Number(actual)\n const expectedNum = Number(expected)\n if (Number.isNaN(actualNum) || Number.isNaN(expectedNum)) return false\n if (node.conditionOperator === '>') return actualNum > expectedNum\n if (node.conditionOperator === '>=') return actualNum >= expectedNum\n if (node.conditionOperator === '<') return actualNum < expectedNum\n return actualNum <= expectedNum\n }\n default:\n return false\n }\n}\n\nfunction resolveNext(node: FlowNodeData, answerId: string | undefined): string | undefined {\n if (!node.next) return undefined\n if (typeof node.next === 'string') return node.next\n if (answerId && node.next.byAnswer[answerId]) return node.next.byAnswer[answerId]\n return node.next.default\n}\n\n// T4.3 — interpreta um grafo de fluxo nó a nó. `registerFlowAction` é o único jeito de um\n// `actionKind` fazer algo: o interpretador nunca conhece 'trigger_simulation' ou qualquer outro\n// caso de negócio do host — ele só invoca o handler registrado (ver providers.ts,\n// FlowActionRegistry) e segue o `next` que o handler devolver, se houver.\nexport class FlowInterpreter {\n private readonly actionHandlers = new Map<FlowActionKind, FlowActionHandler>()\n\n registerFlowAction(kind: FlowActionKind, handler: FlowActionHandler): void {\n this.actionHandlers.set(kind, handler)\n }\n\n async step(input: FlowStepInput): Promise<FlowStepResult> {\n const node = input.graph.nodes[input.currentNodeId]\n if (!node) return { kind: 'terminal', context: input.context }\n\n if (node.type === 'condition') {\n const branch = evaluateCondition(node, input.context) ? 'true' : 'false'\n return this.moveTo(input, resolveNext(node, branch))\n }\n\n if (node.type === 'action') {\n const handler = node.actionKind ? this.actionHandlers.get(node.actionKind) : undefined\n if (!handler) return { kind: 'terminal', context: input.context }\n\n // Runtime de uma função `void` é sempre `undefined` — cast seguro para poder encadear\n // `?.` (TS não deixa acessar propriedade num tipo `void | {...}` mesmo com optional chaining).\n const result = (await handler({\n node,\n session: input.session,\n channel: input.channel,\n context: input.context,\n })) as FlowActionResult | undefined\n\n const contextAfterAction = result?.context ? { ...input.context, ...result.context } : input.context\n const nextNodeId = result?.next ?? resolveNext(node, undefined)\n return this.moveTo({ ...input, context: contextAfterAction }, nextNodeId)\n }\n\n const isChoice = node.type === 'menu' || node.questionType === 'choice'\n\n // Sem resposta do usuário ainda: este é o nó atual, aguardando input — não avança.\n if (input.userAnswer === undefined) {\n return { kind: 'awaiting-answer', nodeId: input.currentNodeId, context: input.context }\n }\n\n const updatedContext = node.contextKey ? { ...input.context, [node.contextKey]: input.userAnswer } : input.context\n const answerId = isChoice ? input.userAnswer : undefined\n const nextNodeId = resolveNext(node, answerId)\n\n return this.moveTo({ ...input, context: updatedContext }, nextNodeId)\n }\n\n private moveTo(input: FlowStepInput, nextNodeId: string | undefined): FlowStepResult {\n if (!nextNodeId) return { kind: 'terminal', context: input.context }\n if (isCrossFlowTarget(nextNodeId))\n return { kind: 'cross-flow', flowKey: crossFlowKey(nextNodeId), context: input.context }\n if (!input.graph.nodes[nextNodeId]) return { kind: 'terminal', context: input.context }\n return { kind: 'advanced', nodeId: nextNodeId, context: input.context }\n }\n\n // Avança pelo grafo até parar num ponto que exige algo de fora: uma pergunta esperando resposta\n // do cliente, o fim do fluxo, ou um salto para outro fluxo. Nós de condição e ação não pedem\n // input nenhum, então parar neles obrigaria o chamador a reimplementar este laço — e sem\n // proteção, um grafo com ciclo de condições (A→B→A) o prenderia para sempre.\n //\n // O corte por `maxSteps` é uma rede de segurança de runtime, não a defesa principal: um ciclo é\n // erro de autoria e deveria ser barrado na publicação. Como validateGraph (no editor) hoje\n // detecta nó inalcançável mas NÃO detecta ciclo, um grafo cíclico é publicável — então o\n // runtime precisa sobreviver a ele em vez de travar o processo que atende o webhook.\n async run(input: FlowStepInput, options?: { maxSteps?: number }): Promise<FlowRunResult> {\n const maxSteps = options?.maxSteps ?? DEFAULT_MAX_STEPS\n const visited: string[] = [input.currentNodeId]\n\n let current: FlowStepInput = input\n let steps = 0\n\n while (steps < maxSteps) {\n const result = await this.step(current)\n steps++\n\n if (result.kind !== 'advanced') return { ...result, visited, steps }\n\n visited.push(result.nodeId)\n // Só a primeira iteração consome a resposta do cliente; daí em diante o laço percorre\n // nós automáticos (condição/ação). Manter userAnswer faria a mesma resposta ser\n // reaplicada em cada pergunta seguinte, pulando-as sem o cliente ter respondido.\n current = { ...current, currentNodeId: result.nodeId, context: result.context, userAnswer: undefined }\n }\n\n return { kind: 'max-steps-exceeded', context: current.context, visited, steps }\n }\n}\n","import { WhatsAppWindowExpiredError as ProviderWindowExpiredError } from '@adatechnology/meta-graph-core'\nimport type { WhatsAppMessageProvider } from '@adatechnology/meta-whatsapp-provider'\nimport { WindowExpiredError, type ChannelAdapterInterface } from '@adatechnology/meta-whatsapp-contracts'\n\n// T5.2 — implementa a porta ChannelAdapterInterface sobre o meta-whatsapp-provider. A camada de\n// conversa fala só com esta porta e nunca com a Graph API, o que é o que permite trocar o canal\n// (ou usar um dublê em teste) sem tocar no motor de fluxo.\n//\n// Também traduz os erros do provider (que são da família MetaGraphError) para os erros de\n// domínio do módulo — o host trata WindowExpiredError sem precisar conhecer meta-graph-core.\nexport class WhatsAppChannelAdapter implements ChannelAdapterInterface {\n constructor(private readonly messages: WhatsAppMessageProvider) {}\n\n private async translateErrors<TResult>(operation: () => Promise<TResult>): Promise<TResult> {\n try {\n return await operation()\n } catch (error) {\n if (error instanceof ProviderWindowExpiredError) throw new WindowExpiredError()\n throw error\n }\n }\n\n async sendText(to: string, body: string): Promise<{ externalMessageId: string | null }> {\n const result = await this.translateErrors(() => this.messages.sendText(to, body))\n return { externalMessageId: result.waMessageId }\n }\n\n async sendMedia(params: {\n to: string\n buffer: Buffer\n mimeType: string\n filename: string\n caption?: string\n }): Promise<{ externalMessageId: string | null }> {\n const result = await this.translateErrors(() => this.messages.sendMedia(params))\n return { externalMessageId: result.waMessageId }\n }\n\n async sendTemplate(params: {\n to: string\n templateName: string\n languageCode: string\n bodyParameters?: string[]\n }): Promise<{ externalMessageId: string | null }> {\n const result = await this.translateErrors(() => this.messages.sendTemplate(params))\n return { externalMessageId: result.waMessageId }\n }\n\n async sendInteractiveList(params: {\n to: string\n body: string\n buttonLabel: string\n rows: { id: string; title: string }[]\n }): Promise<{ externalMessageId: string | null }> {\n const result = await this.translateErrors(() =>\n this.messages.sendInteractiveList({\n to: params.to,\n bodyText: params.body,\n buttonText: params.buttonLabel,\n sections: [{ title: params.buttonLabel, rows: params.rows }],\n }),\n )\n return { externalMessageId: result.waMessageId }\n }\n\n async fetchMediaAsBase64(mediaId: string): Promise<{ data: string; mimeType: string }> {\n return this.translateErrors(() => this.messages.fetchMediaAsBase64(mediaId))\n }\n}\n","import {\n whatsAppWebhookPayloadSchema,\n type MetaWhatsAppHooks,\n type SessionState,\n type WhatsAppMessage,\n type WhatsAppStatus,\n type MessageStatus,\n type ConversationSession,\n} from '@adatechnology/meta-whatsapp-contracts'\nimport type { MessageRepository } from '../repositories/MessageRepository'\nimport type { SessionRepository } from '../repositories/SessionRepository'\nimport type { LogMessageUseCase } from '../use-cases/LogMessage.use-case'\nimport type { RealtimeNotifierInterface } from '@adatechnology/meta-whatsapp-contracts'\nimport type { SessionRow } from '../schema/schema'\nimport { verifyWebhookSignature, claimWebhookDelivery, type NonceStoreInterface } from './webhookSecurity'\n\nexport type ReceiveWebhookParams = {\n companyId: string\n rawBody: Buffer | string\n signatureHeader: string | null | undefined\n}\n\nexport type ReceiveWebhookResult = {\n // Sempre 200 para a Meta, mesmo em duplicata — ela desativa webhooks que respondem erro.\n duplicate: boolean\n messagesProcessed: number\n statusesProcessed: number\n}\n\nfunction toSessionContract(row: SessionRow): ConversationSession {\n return {\n id: row.id,\n companyId: row.companyId,\n whatsappNumber: row.whatsappNumber,\n currentState: row.currentState,\n flowKey: row.flowKey,\n currentNodeId: row.currentNodeId,\n context: row.context,\n mode: row.mode as ConversationSession['mode'],\n assignedUserId: row.assignedUserId,\n humanRequestedAt: row.humanRequestedAt?.toISOString() ?? null,\n lastInboundAt: row.lastInboundAt?.toISOString() ?? null,\n lastAgentReadAt: row.lastAgentReadAt?.toISOString() ?? null,\n lastActivity: row.lastActivity.toISOString(),\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n }\n}\n\n// Texto legível para o transcript. Uma mensagem interativa traz título E id; guardamos o título\n// (é o que o cliente viu), com o id como fallback quando a Meta não manda título.\nfunction extractContent(message: WhatsAppMessage): string | null {\n if (message.text?.body) return message.text.body\n const interactive = message.interactive\n if (interactive?.button_reply) return interactive.button_reply.title || interactive.button_reply.id\n if (interactive?.list_reply) return interactive.list_reply.title || interactive.list_reply.id\n if (message.order) {\n const items = message.order.product_items.reduce((sum, item) => sum + item.quantity, 0)\n return `Pedido: ${items} item(ns)`\n }\n return message.image?.caption ?? message.document?.caption ?? null\n}\n\n// A resposta que o motor de fluxo deve consumir: para interativo é o ID da opção (estável), não\n// o título (que muda quando alguém edita o texto do botão no editor).\nfunction extractAnswer(message: WhatsAppMessage): string | undefined {\n const interactive = message.interactive\n if (interactive?.button_reply) return interactive.button_reply.id\n if (interactive?.list_reply) return interactive.list_reply.id\n return message.text?.body\n}\n\nfunction extractPayload(message: WhatsAppMessage): Record<string, unknown> | null {\n const payload: Record<string, unknown> = {}\n if (message.interactive) payload['interactive'] = message.interactive\n if (message.order) payload['order'] = message.order\n if (message.image) payload['image'] = message.image\n if (message.audio) payload['audio'] = message.audio\n if (message.video) payload['video'] = message.video\n if (message.document) payload['document'] = message.document\n if (message.sticker) payload['sticker'] = message.sticker\n if (message.context?.referred_product) payload['referredProduct'] = message.context.referred_product\n return Object.keys(payload).length > 0 ? payload : null\n}\n\n// T5.1 — porta de entrada do canal. Ordem deliberada: assinatura → anti-replay → parse → efeito.\n// Verificar a assinatura antes de qualquer parse evita gastar CPU (e superfície do zod) com\n// corpo não autenticado.\nexport class ReceiveWebhookUseCase {\n constructor(\n private readonly params: {\n appSecret: string\n nonceStore: NonceStoreInterface\n sessionRepository: SessionRepository\n messageRepository: MessageRepository\n logMessage: LogMessageUseCase\n startState: SessionState\n hooks?: MetaWhatsAppHooks\n realtime?: RealtimeNotifierInterface\n },\n ) {}\n\n async execute(input: ReceiveWebhookParams): Promise<ReceiveWebhookResult> {\n verifyWebhookSignature({\n rawBody: input.rawBody,\n signatureHeader: input.signatureHeader,\n appSecret: this.params.appSecret,\n })\n\n const claimed = await claimWebhookDelivery({\n nonceStore: this.params.nonceStore,\n signatureHeader: input.signatureHeader!,\n })\n if (!claimed) return { duplicate: true, messagesProcessed: 0, statusesProcessed: 0 }\n\n const rawText = typeof input.rawBody === 'string' ? input.rawBody : input.rawBody.toString('utf8')\n const payload = whatsAppWebhookPayloadSchema.parse(JSON.parse(rawText))\n\n let messagesProcessed = 0\n let statusesProcessed = 0\n\n for (const entry of payload.entry) {\n for (const change of entry.changes) {\n for (const message of change.value.messages ?? []) {\n await this.handleMessage(input.companyId, message)\n messagesProcessed++\n }\n for (const status of change.value.statuses ?? []) {\n await this.handleStatus(input.companyId, status)\n statusesProcessed++\n }\n }\n }\n\n return { duplicate: false, messagesProcessed, statusesProcessed }\n }\n\n private async handleMessage(companyId: string, message: WhatsAppMessage): Promise<void> {\n const saved = await this.params.logMessage.execute({\n companyId,\n whatsappNumber: message.from,\n direction: 'inbound',\n sender: 'customer',\n type: message.type,\n content: extractContent(message),\n payload: extractPayload(message),\n waMessageId: message.id,\n status: 'received',\n startState: this.params.startState,\n })\n // Mensagem repetida (mesmo waMessageId já gravado): não dispara o hook de novo, senão a\n // regra de negócio do host rodaria duas vezes para a mesma mensagem do cliente.\n if (!saved) return\n\n const sessionRow = await this.params.sessionRepository.getContext(companyId, message.from)\n if (!sessionRow) return\n\n // Conversa em atendimento humano não é processada pelo bot — o atendente responde.\n if (sessionRow.mode === 'human') return\n\n const outcome = await this.params.hooks?.onMessageReceived?.(message, toSessionContract(sessionRow))\n // 'handled' = o host já respondeu e assumiu a mensagem; o módulo não segue com o fluxo.\n if (outcome?.outcome === 'handled') return\n\n // 'continue' (ou sem hook): quem dirige o motor de fluxo é o host via runFlowStep — o\n // webhook só entrega o sinal. Expor a resposta extraída evita que cada host reimplemente\n // a lógica de \"qual é a resposta do cliente\" para interativo vs texto.\n void extractAnswer(message)\n }\n\n private async handleStatus(companyId: string, status: WhatsAppStatus): Promise<void> {\n const updated = await this.params.messageRepository.updateMessageStatus(\n companyId,\n status.id,\n status.status as MessageStatus,\n )\n if (!updated) return\n\n this.params.realtime?.emit(`conv:${updated.whatsappNumber}`, 'message-status', {\n waMessageId: status.id,\n status: status.status,\n })\n\n const sessionRow = await this.params.sessionRepository.getContext(companyId, updated.whatsappNumber)\n await this.params.hooks?.onStatusUpdate?.(status, sessionRow ? toSessionContract(sessionRow) : null)\n }\n}\n\nexport { extractAnswer, extractContent }\n","import { createHmac, timingSafeEqual } from 'node:crypto'\nimport { InvalidWebhookSignatureError } from '@adatechnology/meta-whatsapp-contracts'\n\n// Porta mínima de armazenamento com TTL para o nonce anti-replay. Qualquer cache do host serve\n// (Redis, Memcached); precisa ser COMPARTILHADO entre instâncias — um cache em memória por\n// processo deixaria a mesma entrega passar uma vez em cada instância.\nexport interface NonceStoreInterface {\n // Deve ser atômico (SET NX): devolve true só se a chave ainda não existia. Um get-then-set\n // não serve — duas entregas simultâneas leriam \"ausente\" e ambas seguiriam.\n setIfAbsent(key: string, ttlSeconds: number): Promise<boolean>\n}\n\nexport const WEBHOOK_NONCE_TTL_SECONDS = 300\n\n// Comparação em tempo constante entre dois textos. `timingSafeEqual` exige buffers do mesmo\n// tamanho, e o próprio comprimento vaza informação — por isso comparamos digests de tamanho\n// fixo em vez dos valores crus.\nfunction safeEqualStrings(left: string, right: string): boolean {\n const leftDigest = createHmac('sha256', 'constant-time-compare').update(left).digest()\n const rightDigest = createHmac('sha256', 'constant-time-compare').update(right).digest()\n return timingSafeEqual(leftDigest, rightDigest)\n}\n\n// GET /webhook da Meta: ela envia hub.mode/hub.verify_token/hub.challenge e espera o challenge\n// de volta em texto puro. Comparação em tempo constante — o verify token é um segredo, e\n// comparar com === permitiria descobri-lo caractere a caractere pelo tempo de resposta.\nexport function verifyWebhookChallenge(params: {\n mode: string | null\n token: string | null\n challenge: string | null\n expectedToken: string\n}): string {\n if (params.mode !== 'subscribe' || !params.token || !params.challenge) {\n throw new InvalidWebhookSignatureError()\n }\n if (!safeEqualStrings(params.token, params.expectedToken)) {\n throw new InvalidWebhookSignatureError()\n }\n return params.challenge\n}\n\n// POST /webhook: a Meta assina o corpo CRU com o app secret e manda em X-Hub-Signature-256.\n// Precisa ser o buffer exato recebido — reserializar o JSON muda espaçamento/ordem e invalida\n// a assinatura.\nexport function verifyWebhookSignature(params: {\n rawBody: Buffer | string\n signatureHeader: string | null | undefined\n appSecret: string\n}): void {\n const { rawBody, signatureHeader, appSecret } = params\n if (!signatureHeader?.startsWith('sha256=')) throw new InvalidWebhookSignatureError()\n\n const expected = createHmac('sha256', appSecret).update(rawBody).digest('hex')\n const received = signatureHeader.slice('sha256='.length)\n\n // Comprimentos diferentes já reprovam, mas passamos pelo digest de tamanho fixo para não\n // transformar isso num canal de tempo.\n if (!safeEqualStrings(received, expected)) throw new InvalidWebhookSignatureError()\n}\n\n// Anti-replay: a mesma entrega assinada, reenviada por um atacante que a capturou, seria aceita\n// de novo — a assinatura continua válida. O nonce marca a entrega como já processada por uma\n// janela curta. A Meta legitimamente reenvia em caso de timeout, então isto também absorve o\n// retry dela sem duplicar efeito.\n//\n// Devolve false quando a entrega já foi vista (o chamador deve responder 200 e ignorar — a Meta\n// desativa webhooks que respondem erro).\nexport async function claimWebhookDelivery(params: {\n nonceStore: NonceStoreInterface\n signatureHeader: string\n ttlSeconds?: number\n}): Promise<boolean> {\n // A assinatura é derivada do corpo, então é única por entrega — não precisamos de um nonce\n // separado, e usá-la evita depender de um campo que a Meta não garante.\n const key = `meta-whatsapp:webhook:${params.signatureHeader}`\n return params.nonceStore.setIfAbsent(key, params.ttlSeconds ?? WEBHOOK_NONCE_TTL_SECONDS)\n}\n","import { eq, and } from 'drizzle-orm'\nimport type { MetaWhatsAppDatabase } from '../database.types'\nimport type { ChannelAdapterInterface, ObjectStorageInterface } from '@adatechnology/meta-whatsapp-contracts'\nimport { messages, type MessageRow } from '../schema/schema'\n\nexport type IngestInboundMediaParams = {\n companyId: string\n messageId: string\n // ID da mídia na Meta — a URL de download expira, então o binário precisa ser copiado para o\n // storage do host antes de sumir.\n sourceMediaId: string\n mimeType: string\n filename?: string\n}\n\nexport type IngestInboundMediaResult = {\n uploadId: string\n // true quando a mídia já estava no storage e nada foi baixado de novo.\n alreadyIngested: boolean\n}\n\n// T5.3 — copia mídia recebida da Meta para o storage do host.\n//\n// Não reimplementa S3: delega ao ObjectStorageInterface que o host injeta (object-storage-provider\n// ou o que ele usar). O módulo só orquestra \"baixa da Meta → entrega ao storage → referencia na\n// mensagem\".\n//\n// Idempotente por sourceMediaId gravado no payload da mensagem: o job pode ser reentregue (fila\n// com retry, webhook reprocessado) e baixar de novo custaria banda e geraria um segundo objeto\n// órfão no storage para o mesmo binário.\nexport class IngestInboundMediaUseCase {\n constructor(\n private readonly db: MetaWhatsAppDatabase,\n private readonly channel: ChannelAdapterInterface,\n private readonly objectStorage: ObjectStorageInterface,\n ) {}\n\n async execute(params: IngestInboundMediaParams): Promise<IngestInboundMediaResult> {\n const [message] = await this.db\n .select()\n .from(messages)\n .where(and(eq(messages.companyId, params.companyId), eq(messages.id, params.messageId)))\n .limit(1)\n\n if (!message) throw new Error(`Mensagem ${params.messageId} não encontrada para ingestão de mídia`)\n\n const payload = (message.payload ?? {}) as Record<string, unknown>\n\n // Já ingerida: devolve o uploadId existente sem tocar na rede.\n if (payload['uploadId'] && payload['sourceMediaId'] === params.sourceMediaId) {\n return { uploadId: String(payload['uploadId']), alreadyIngested: true }\n }\n\n const { data, mimeType } = await this.channel.fetchMediaAsBase64(params.sourceMediaId)\n const buffer = Buffer.from(data, 'base64')\n\n const { uploadId } = await this.objectStorage.upload({\n buffer,\n mimeType: mimeType || params.mimeType,\n key: `meta-whatsapp/${params.companyId}/inbound/${params.sourceMediaId}`,\n })\n\n // Guarda sourceMediaId junto do uploadId: é o que torna a próxima execução idempotente.\n const updatedPayload: Record<string, unknown> = {\n ...payload,\n uploadId,\n sourceMediaId: params.sourceMediaId,\n mimeType: mimeType || params.mimeType,\n ...(params.filename ? { filename: params.filename } : {}),\n }\n\n await this.db\n .update(messages)\n .set({ payload: updatedPayload })\n .where(and(eq(messages.companyId, params.companyId), eq(messages.id, params.messageId)))\n\n return { uploadId, alreadyIngested: false }\n }\n}\n\n// Extrai o descritor de mídia de uma mensagem já persistida — o webhook guarda o objeto cru da\n// Meta (image/audio/video/document/sticker) no payload, e a ingestão precisa saber qual é.\nexport function extractMediaDescriptor(\n message: MessageRow,\n): { sourceMediaId: string; mimeType: string; filename?: string } | undefined {\n const payload = (message.payload ?? {}) as Record<string, unknown>\n for (const key of ['image', 'audio', 'video', 'document', 'sticker'] as const) {\n const media = payload[key] as { id?: string; mime_type?: string; filename?: string } | undefined\n if (media?.id) {\n return {\n sourceMediaId: media.id,\n mimeType: media.mime_type ?? 'application/octet-stream',\n filename: media.filename,\n }\n }\n }\n return undefined\n}\n","import { join } from 'node:path'\nimport type { MetaWhatsAppDatabase } from './database.types'\n\n// Migrations append-only com journal próprio — nunca colide com as migrations do host, que roda\n// as delas por um caminho totalmente separado (ver rules/packages/pluggable-module.md §3.2).\n//\n// O journal vive em `drizzle.meta_whatsapp_migrations` (schema `drizzle`, o padrão do\n// drizzle-kit; o nome DA TABELA é que isola o módulo — o host usa `__drizzle_migrations` ali do\n// lado, sem conflito). Deliberadamente NÃO fica dentro de `meta_whatsapp`: a primeira migration\n// do módulo é justamente quem cria esse schema (CREATE SCHEMA), então apontar o journal para lá\n// gera uma corrida entre o migrator garantindo o schema da tabela de controle e a migration\n// tentando criá-lo de novo — o erro \"schema meta_whatsapp already exists\".\n//\n// Para resetar um banco de teste é preciso derrubar os dois: o schema meta_whatsapp E o journal\n// em drizzle.meta_whatsapp_migrations. Só dropar o schema faz o migrator pular tudo na próxima\n// execução, deixando o banco sem as tabelas e sem erro aparente.\nexport const META_WHATSAPP_MIGRATIONS_TABLE = 'meta_whatsapp_migrations'\n\n// Pasta das migrations embarcadas, para o host que prefere chamar o próprio migrator.\nexport function metaWhatsAppMigrationsFolder(): string {\n return join(__dirname, 'migrations')\n}\n\nexport type RunMetaWhatsAppMigrationsParams = {\n db: MetaWhatsAppDatabase\n // O `migrate` do driver do host — `drizzle-orm/node-postgres/migrator`,\n // `drizzle-orm/bun-sql/migrator`, etc. O módulo não escolhe driver pelo host.\n migrate: (db: never, config: { migrationsFolder: string; migrationsTable?: string }) => Promise<void>\n}\n\nexport async function runMetaWhatsAppMigrations(params: RunMetaWhatsAppMigrationsParams): Promise<void> {\n await params.migrate(params.db as never, {\n migrationsFolder: metaWhatsAppMigrationsFolder(),\n migrationsTable: META_WHATSAPP_MIGRATIONS_TABLE,\n })\n}\n","import { randomUUID } from 'node:crypto'\nimport type { RealtimeNotifierInterface } from '@adatechnology/meta-whatsapp-contracts'\n\nexport type SseListener = (event: string, payload: Record<string, unknown>) => void\n\n// Porta mínima de cache para o ticket de conexão SSE — qualquer TTL cache do host serve\n// (Redis, in-memory); o módulo não assume Redis especificamente.\nexport interface TicketStoreInterface {\n set(key: string, value: string, ttlSeconds: number): Promise<void>\n get(key: string): Promise<string | null>\n delete(key: string): Promise<void>\n}\n\n// Publica localmente e, se um relay for injetado (ex.: Redis pub/sub), propaga entre processos\n// — várias instâncias do host atrás de um load balancer recebem o mesmo evento (T3.4).\nexport interface RealtimeRelay {\n publish(channel: string, message: string): Promise<void>\n subscribe(channel: string, onMessage: (message: string) => void): Promise<() => void>\n}\n\nconst TICKET_TTL_SECONDS = 60\n\n// T3.4 — hub de tempo real atrás da porta RealtimeNotifierInterface (contracts). O host pode\n// trocar por WebSocket ou desligar sem o resto do módulo saber — ver providers.ts.\ntype RelayEnvelope = {\n originId: string\n event: string\n payload: Record<string, unknown>\n}\n\nexport class SseHub implements RealtimeNotifierInterface {\n private readonly localListeners = new Map<string, Set<SseListener>>()\n // Uma assinatura de relay por CANAL, compartilhada por todos os listeners locais dele — uma\n // por listener faria o relay entregar N cópias num canal com N conexões abertas.\n private readonly relaySubscriptions = new Map<string, Promise<() => void>>()\n // Identifica esta instância do processo para descartar o próprio eco (pub/sub entrega a\n // mensagem a todos os inscritos, inclusive quem publicou).\n private readonly instanceId = randomUUID()\n\n constructor(private readonly relay?: RealtimeRelay) {}\n\n emit(channel: string, event: string, payload: Record<string, unknown>): void {\n this.dispatchLocal(channel, event, payload)\n if (this.relay) {\n const envelope: RelayEnvelope = { originId: this.instanceId, event, payload }\n this.relay.publish(channel, JSON.stringify(envelope)).catch(() => undefined)\n }\n }\n\n private dispatchLocal(channel: string, event: string, payload: Record<string, unknown>): void {\n const listeners = this.localListeners.get(channel)\n if (!listeners) return\n for (const listener of listeners) listener(event, payload)\n }\n\n async subscribe(channel: string, listener: SseListener): Promise<() => void> {\n if (!this.localListeners.has(channel)) this.localListeners.set(channel, new Set())\n this.localListeners.get(channel)!.add(listener)\n\n if (this.relay && !this.relaySubscriptions.has(channel)) {\n this.relaySubscriptions.set(\n channel,\n this.relay.subscribe(channel, (message) => {\n try {\n const envelope = JSON.parse(message) as RelayEnvelope\n // Eco do que nós mesmos publicamos: dispatchLocal já entregou em emit().\n if (envelope.originId === this.instanceId) return\n this.dispatchLocal(channel, envelope.event, envelope.payload)\n } catch {\n // mensagem de relay malformada — ignora, não derruba a conexão\n }\n }),\n )\n }\n\n return () => {\n const listeners = this.localListeners.get(channel)\n listeners?.delete(listener)\n // Último listener do canal saiu: solta a assinatura do relay e a chave do Map, senão\n // canais efêmeros (conv:<numero>) se acumulam pelo uptime inteiro do processo.\n if (listeners && listeners.size === 0) {\n this.localListeners.delete(channel)\n const pendingUnsubscribe = this.relaySubscriptions.get(channel)\n this.relaySubscriptions.delete(channel)\n pendingUnsubscribe?.then((unsubscribe) => unsubscribe()).catch(() => undefined)\n }\n }\n }\n}\n\n// EventSource do browser não manda headers customizados — o ticket vai na query string da URL\n// de conexão e expira em 60s, curto o bastante para não valer a pena capturar e reusar depois.\nexport async function issueSseTicket(\n store: TicketStoreInterface,\n companyId: string,\n whatsappNumber: string,\n): Promise<string> {\n const ticket = randomUUID()\n await store.set(`sse:ticket:${ticket}`, JSON.stringify({ companyId, whatsappNumber }), TICKET_TTL_SECONDS)\n return ticket\n}\n\nexport async function redeemSseTicket(\n store: TicketStoreInterface,\n ticket: string,\n): Promise<{ companyId: string; whatsappNumber: string } | null> {\n const raw = await store.get(`sse:ticket:${ticket}`)\n if (!raw) return null\n await store.delete(`sse:ticket:${ticket}`)\n return JSON.parse(raw) as { companyId: string; whatsappNumber: string }\n}\n"],"mappings":"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","names":["import_drizzle_orm","metaWhatsAppSchema","pgSchema","sessions","table","id","uuid","primaryKey","defaultRandom","companyId","notNull","whatsappNumber","varchar","length","currentState","default","flowKey","currentNodeId","context","jsonb","$type","mode","assignedUserId","humanRequestedAt","timestamp","withTimezone","lastInboundAt","lastAgentReadAt","lastActivity","defaultNow","createdAt","updatedAt","uniqueIndex","on","index","messages","sessionId","references","onDelete","direction","sender","agentUserId","type","content","text","payload","waMessageId","status","readAt","moderationFlagged","boolean","moderationTerms","where","sql","flowGraphs","key","label","startNodeId","nodes","version","integer","showInMenu","menuOptionLabel","settings","templateName","templateLanguage","templateVariables","welcomeMessage","farewellMessage","DEFAULT_LIMIT","conversationSummaryProjection","lastContent","sql","messages","sessions","lastDirection","unread","SessionRepository","db","getContext","companyId","whatsappNumber","row","select","from","where","and","eq","limit","getOrCreate","startState","existing","created","insert","values","currentState","onConflictDoUpdate","target","set","lastActivity","returning","setState","state","context","update","undefined","updatedAt","setFlowPosition","flowKey","currentNodeId","touchInbound","lastInboundAt","hoursSinceLastInbound","session","Date","now","getTime","setMode","mode","assignedUserId","takeover","agentUserId","release","requestHuman","humanRequestedAt","markRead","lastAgentReadAt","markAllRead","userId","result","Number","rowCount","listByContextFilters","filters","offset","page","conditions","waitingHuman","push","search","rows","id","orderBy","desc","map","lastAt","toISOString","exportConversation","sessionId","createdAt","import_drizzle_orm","DEFAULT_LIMIT","MessageRepository","db","insertMessage","params","values","companyId","sessionId","whatsappNumber","direction","sender","agentUserId","type","content","payload","waMessageId","status","moderationFlagged","moderationTerms","created","insert","messages","onConflictDoNothing","returning","updateMessageStatus","updated","update","set","readAt","Date","where","and","eq","listByConversation","conditions","before","push","lt","createdAt","select","from","orderBy","limit","import_drizzle_orm","InvalidFlowGraphError","Error","key","validationMessage","name","toContractGraph","row","parsed","flowGraphNodesSchema","safeParse","nodes","success","error","message","label","startNodeId","version","data","OptimisticLockError","FlowGraphRepository","db","get","companyId","select","from","flowGraphs","where","and","eq","limit","undefined","list","rows","map","nodeCount","Object","keys","length","showInMenu","menuOptionLabel","updatedAt","toISOString","assertValidNodes","create","graph","created","insert","values","returning","save","expectedVersion","update","set","Date","delete","getLiveFlowPositions","flowKey","sessions","nodeId","currentNodeId","count","sql","mode","isNotNull","groupBy","import_drizzle_orm","toContractSettings","row","templateName","templateLanguage","templateVariables","welcomeMessage","farewellMessage","EMPTY_SETTINGS","SettingsRepository","db","get","companyId","select","from","settings","where","eq","limit","save","update","current","merged","insert","values","onConflictDoUpdate","target","set","updatedAt","Date","returning","resolveTemplateVariables","context","map","token","key","replace","value","undefined","String","LogMessageUseCase","sessionRepository","messageRepository","realtime","moderator","execute","params","session","getOrCreate","companyId","whatsappNumber","startState","saved","insertMessage","sessionId","id","moderationOf","undefined","direction","touchInbound","emit","sender","text","content","trim","verdict","inspect","moderationFlagged","isOffensive","moderationTerms","matchedTerms","import_meta_whatsapp_contracts","WHATSAPP_WINDOW_HOURS","SendMessageUseCase","channel","sessionRepository","logMessage","objectStorage","assertWithinWindow","companyId","whatsappNumber","hours","hoursSinceLastInbound","undefined","WindowExpiredError","sendText","params","externalMessageId","body","execute","direction","sender","agentUserId","type","content","waMessageId","status","startState","sendMedia","to","buffer","mimeType","filename","caption","uploadId","upload","key","Date","now","mediaTypeFor","payload","sendTemplate","templateName","languageCode","bodyParameters","startsWith","TakeoverConversationUseCase","sessionRepository","realtime","execute","params","takeover","companyId","whatsappNumber","agentUserId","emit","mode","assignedUserId","ReleaseConversationUseCase","sessionRepository","realtime","execute","params","release","companyId","whatsappNumber","emit","mode","assignedUserId","ListConversationsUseCase","sessionRepository","execute","params","listByContextFilters","companyId","filters","import_meta_whatsapp_contracts","ListMessagesUseCase","sessionRepository","messageRepository","execute","params","session","getContext","companyId","whatsappNumber","SessionNotFoundError","repoParams","sessionId","id","limit","before","listByConversation","import_meta_whatsapp_contracts","ExportConversationUseCase","sessionRepository","execute","params","result","exportConversation","companyId","whatsappNumber","SessionNotFoundError","GetFlowGraphUseCase","flowGraphRepository","execute","params","get","companyId","key","ListFlowGraphsUseCase","list","CreateFlowGraphUseCase","create","SaveFlowGraphUseCase","save","graph","expectedVersion","DeleteFlowGraphUseCase","delete","GetLiveFlowPositionsUseCase","getLiveFlowPositions","import_meta_whatsapp_contracts","DEFAULT_MAX_STEPS","evaluateCondition","node","context","conditionContextKey","conditionOperator","conditionValue","actual","expected","String","includes","actualNum","Number","expectedNum","isNaN","resolveNext","answerId","next","undefined","byAnswer","default","FlowInterpreter","actionHandlers","Map","registerFlowAction","kind","handler","set","step","input","graph","nodes","currentNodeId","type","branch","moveTo","actionKind","get","result","session","channel","contextAfterAction","nextNodeId","isChoice","questionType","userAnswer","nodeId","updatedContext","contextKey","isCrossFlowTarget","flowKey","crossFlowKey","run","options","maxSteps","visited","current","steps","push","import_meta_whatsapp_contracts","WhatsAppChannelAdapter","messages","translateErrors","operation","error","ProviderWindowExpiredError","WindowExpiredError","sendText","to","body","result","externalMessageId","waMessageId","sendMedia","params","sendTemplate","sendInteractiveList","bodyText","buttonText","buttonLabel","sections","title","rows","fetchMediaAsBase64","mediaId","import_meta_whatsapp_contracts","import_meta_whatsapp_contracts","WEBHOOK_NONCE_TTL_SECONDS","safeEqualStrings","left","right","leftDigest","createHmac","update","digest","rightDigest","timingSafeEqual","verifyWebhookChallenge","params","mode","token","challenge","InvalidWebhookSignatureError","expectedToken","verifyWebhookSignature","rawBody","signatureHeader","appSecret","startsWith","expected","received","slice","length","claimWebhookDelivery","key","nonceStore","setIfAbsent","ttlSeconds","toSessionContract","row","id","companyId","whatsappNumber","currentState","flowKey","currentNodeId","context","mode","assignedUserId","humanRequestedAt","toISOString","lastInboundAt","lastAgentReadAt","lastActivity","createdAt","updatedAt","extractContent","message","text","body","interactive","button_reply","title","list_reply","order","items","product_items","reduce","sum","item","quantity","image","caption","document","extractAnswer","extractPayload","payload","audio","video","sticker","referred_product","Object","keys","length","ReceiveWebhookUseCase","params","execute","input","verifyWebhookSignature","rawBody","signatureHeader","appSecret","claimed","claimWebhookDelivery","nonceStore","duplicate","messagesProcessed","statusesProcessed","rawText","toString","whatsAppWebhookPayloadSchema","parse","JSON","entry","change","changes","value","messages","handleMessage","status","statuses","handleStatus","saved","logMessage","from","direction","sender","type","content","waMessageId","startState","sessionRow","sessionRepository","getContext","outcome","hooks","onMessageReceived","updated","messageRepository","updateMessageStatus","realtime","emit","onStatusUpdate","import_drizzle_orm","IngestInboundMediaUseCase","db","channel","objectStorage","execute","params","message","select","from","messages","where","and","eq","companyId","id","messageId","limit","Error","payload","sourceMediaId","uploadId","String","alreadyIngested","data","mimeType","fetchMediaAsBase64","buffer","Buffer","upload","key","updatedPayload","filename","update","set","extractMediaDescriptor","media","mime_type","undefined","createMetaWhatsAppModule","params","db","config","nonceStore","providers","hooks","startState","flowEngineEnabled","features","flowEngine","messageProvider","WhatsAppMessageProvider","accessToken","phoneNumberId","wabaId","apiVersion","baseUrl","channel","WhatsAppChannelAdapter","sessionRepository","SessionRepository","messageRepository","MessageRepository","settingsRepository","SettingsRepository","flowGraphRepository","FlowGraphRepository","logMessage","LogMessageUseCase","realtime","moderator","sendMessage","SendMessageUseCase","objectStorage","receiveWebhook","ReceiveWebhookUseCase","appSecret","flowInterpreter","FlowInterpreter","undefined","ingestInboundMedia","IngestInboundMediaUseCase","conversations","log","send","takeover","TakeoverConversationUseCase","release","ReleaseConversationUseCase","list","ListConversationsUseCase","listMessages","ListMessagesUseCase","export","ExportConversationUseCase","repository","settings","webhook","receive","verifyChallenge","query","verifyWebhookChallenge","expectedToken","webhookVerifyToken","flows","interpreter","registerFlowAction","kind","handler","get","GetFlowGraphUseCase","ListFlowGraphsUseCase","create","CreateFlowGraphUseCase","save","SaveFlowGraphUseCase","delete","DeleteFlowGraphUseCase","livePositions","GetLiveFlowPositionsUseCase","catalog","META_WHATSAPP_MIGRATIONS_TABLE","metaWhatsAppMigrationsFolder","join","__dirname","runMetaWhatsAppMigrations","params","migrate","db","migrationsFolder","migrationsTable","import_node_crypto","TICKET_TTL_SECONDS","SseHub","localListeners","Map","relaySubscriptions","instanceId","randomUUID","relay","emit","channel","event","payload","dispatchLocal","envelope","originId","publish","JSON","stringify","catch","undefined","listeners","get","listener","subscribe","has","set","Set","add","message","parse","delete","size","pendingUnsubscribe","then","unsubscribe","issueSseTicket","store","companyId","whatsappNumber","ticket","redeemSseTicket","raw"]}